• Как вывести всю HTML страницу в go?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    У вас проблема в том, что не отображается статика (изображения, CSS).
    Эту проблему можно решить, как минимум, двумя способами.
    Обслуживать статику вебсервером, например Nginx.
    Или сделать handler на Go, который будет отдавать статику.
    Вот тут есть хороший пример как это можно сделать

    https://www.alexedwards.net/blog/serving-static-si...

    Если кратко, как вариант:
    1. создайте папку static и переложите туда файлы `style.css`, `dog2.jpg`
    2. добавьте в URL у картинок и CSS "/static/", чтобы было вот так
    <link rel="stylesheet" href="/static/style.css"/>
    <img src="/static/sob2.jpg" />

    3. сделайте обработчик /static
    // добавьте эти строки
    fs := http.FileServer(http.Dir("./static"))
    http.Handle("/static/", http.StripPrefix("/static/", fs))
    // перед этой строкой
    http.HandleFunc("/", viewHandler)


    Если настраивать через Nginx, тогда минимальные рабочие настройки будут примерно такими
    server {
        listen *:80;
    
        server_name localhost;
    
        # root /тут_путь_к_вашей_папке/static;
        # для Windows будет что-то типа такого
        root D:/GoExam/static;
    
        location / {
            try_files $uri @process;
        }
    
        location @process {
            proxy_pass http://127.0.0.1:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_send_timeout 30;
            proxy_read_timeout 30;
        }
    }

    добавлять их нужно в файл nginx.conf, в секцию http
    пример

    http {
      # тут идут настройки Nginx
      ....
      # тут дописываете код, что я выше написал, грубо перед закрывающей "}"
    }

    Ответ написан
  • Почему ошибка в выводе (golang)?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Он не выводит %
    % - это скорее всего уже ваш шел выводит.
    Добавьте код перевода строки "\n" в конец вот так fmt.Printf ("Hello %v\n", x) и будет то, что вы хотите
    Ответ написан
    Комментировать
  • Как сделать два пула соединений (с разными БД)?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Именно так, как вы и написали
    db1, err := sql.Open("mysql", DSN1) // Сначала к одной БД подключение.
    db2, err := sql.Open("mysql", DSN2) // Ниже к другой БД подключение.
    
    // настраиваете пулы как вам нужно
    db1.SetMaxIdleConns(10)
    db1.SetMaxOpenConns(100)
    
    db2.SetMaxIdleConns(10)
    db2.SetMaxOpenConns(100)
    Полный пример кода

    package main
    
    import (
        "database/sql"
        "log"
    
        _ "github.com/go-sql-driver/mysql"
    )
    
    func main() {
        db1, err := sql.Open("mysql", `user1:password1@/dbname1`) // Сначала к одной БД подключение.
        if err != nil {
            log.Fatal(err)
        }
    
        db2, err := sql.Open("mysql", `user2:password2@/dbname2`) // Ниже к другой БД подключение.
        if err != nil {
            log.Fatal(err)
        }
    
        // настраиваете пулы как вам нужно
        db1.SetMaxIdleConns(10)
        db1.SetMaxOpenConns(100)
    
        db2.SetMaxIdleConns(10)
        db2.SetMaxOpenConns(100)
    }

    Ответ написан
  • Как работают современные мессенджеры?

    EvgenyMamonov
    @EvgenyMamonov
    Senior software developer, system architect
    как сервер за столь короткое время умеет прошерстить базу данных, найти сообщения конкретного пользователя и отдать их

    Я бы делал немного иначе, например пользователь отправляет сообщение, которое принимает сервер.
    В случае, если получатель сообщения также подключен - сервер параллельно доставляет получателю сообщение, за счёт этого имеем быструю доставку сообщений, и параллельно сохраняет сообщение в базу.

    Что касается быстрой загрузки истории сообщений - то тут нужно сохранять сообщения таким образом, чтобы сообщения одного пользователя всегда были на одном и том же сервере (если используется шардинг). Тогда обычный SELECT из базы по user_id будет вполне себе быстро работать даже на огромной базе. Также таблицы можно еще партицировать, чтобы еще быстрее загружать последние сообщения в истории.

    Что касается безопасности, если не использовать e2e шифрование, как вариант, можно использовать обычные RSA ключи. Например на сервере генерируем два ключа, открытый отправляем кленту, он шифрует им сообщение и передает сообщение на сервер. Вы на сервере его расшифровываете при помощи закрытого ключа. Для отправки сообщения клиенту, можно сделать тоже самое. Клиент также генерирует два RSA ключа и открытый ключ отправляет серверу. Когда серверу нужно доставить сообщение клиенту, он шифрует сообщение открытым ключём, который ему высылал клиент и отправляет ему.

    Мне тоже интересно узнать другие варианты решения этой задачи, буду следить за этой темой :)

    Хорошее дополнение по хранению сообщения и организации поиска от Ильи
    Распределение нагрузки решается шардингом — получается много небольших БД вместо одной огромной. Скорее всего каждое сообщение разбивается по словам/частям слов и сохраняется в поисковый индекс типа слово—message_id и такой индекс строится для каждого пользователя и тоже шардируется. При поиске сначала получаем идентификаторы подходящих сообщений из поискового индекса, потом выгружаем сообщения из БД с сообщениями.

    Дополнение от Stalker_RED
    только не "сообщения одного пользователя всегда были на одном и том же сервере" а сообщения из одного чата/канала/группы (включая чаты, в котором всего два участника). То-же самое касается построения индекса.
    Ответ написан
    7 комментариев
  • Какой тип канала ставить?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Должно быть немного иначе, первое что нужно изменить - это объявление самой структуры, оно должно быть таким
    type Event struct {
            EventID int    `json:"eventId"`
            Type    string `json:"type"`
            ....
    }


    Судя по комментарию "//Делаем longpoll запрос на обновления" - логика должна быть немного иной, вы должны сделать один запрос и потом постоянно декодировать (по одному) пришедшие порции данных.
    Если я понял верно - тогда делать нужно вот так
    // канал создаёте вот так, т.к. вы хотите по одному событию передавать данные, а не целыми слайсами сразу
    EventsCh = make(chan Event, 100)
    go mailing(EventsCh)
    
    // делаете HTTP запрос
    response, responseErr := http.PostForm(...)
    
    // создаёте decoder
    decoder := json.NewDecoder(response.Body)
    
    for {
        event := Event{}
        decoderErr := decoder.Decode(&event) // если данных нет - тут выполнение программы блокируется
        if decodeErr != nil { // тут может быть ошибка io.EOF (просто данные закончились, сервер корректно закрыл соединение)
           break
        }
        EventsCh <- event  // отправляете данные в канал, из которого их читает горутина
    }


    Функция mailing должна быть такой
    func mailing(eventsCh chan Event){									//Получает события из канала
        for{
            event, ok := <- eventsChan
            if !ok {
                // канал закрыт
                break
            }
            log.Print("Получено ", event)
        }
    }
    Ответ написан
  • Как проверить длину массива в html шаблонах Golang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вот так можно
    {{ $length := len .YourArray }}
    {{ if eq $length 0 }}
        array has zero length
    {{ end }}

    Просто вывести размер можно так
    {{ len .YourArray }}
    Ответ написан
    Комментировать
  • Как объединить массивы байтов в один массив?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Можно использовать bytes.Buffer (самая быстрая конкатенация) для Go, например так:
    messageReturn := bytes.NewBufferString("Your message is: ")
    messageReturn.Write(message)
    conn.WriteMessage(websocket.TextMessage, messageReturn.Bytes())
    Ответ написан
  • Как исправить ошибку при установке webview библиотеки в Golang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вам нужно установить "devel" пакеты, что-то типа gtk+-devel и libwebkit2gtk-4.0-dev.
    Для разных дистрибутивов Линукса названия могут отличаться.

    Причина ошибки в том, что помимо установки самих пакетов нужны еще и пакеты, в которых, как минимум, есть заголовки (.h), чтобы была возможность скомпилировать ваш проект/webview.
    Ответ написан
    Комментировать
  • Как распарсить JSON на golang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Пример
    // AddUserRequest структура с параметрами запроса.
    type AddUserRequest struct {
        Name string `json:"name"`
        Login string `json:"login"`
        Password string `json:"password"`
    }
    
    func AddUserHandlerFunc(w http.ResponseWriter, r *http.Request) {
            var req AddUserRequest
            err := json.NewDecoder(r.Body).Decode(&req);
            if err != nil {
            /// ....
            }
            // тут у вас будет заполненная структура req 
    }

    Запрос в формате JSON должен быть таким
    {
        "name": "Иван",
        "login": "Ivan",
        "password": "123"
    }


    Если структура JSON заранее не известна, можно использовать вот эту библиотеку
    https://github.com/valyala/fastjson
    Ответ написан
  • Как избежать большого количества аргументов в функциях?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    В идеале переделать.
    Если переделать нет возможности - структуры будут однозначно лучше.
    Еще есть вариант "функциональные опции" https://golang-blog.blogspot.com/2019/10/functiona...
    Ответ написан
    Комментировать
  • Почему у меня при тестировании в Golang FAIL?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вы три раза вызываете t.Errorf, как следствие получаете FAIL, чтобы тест был PASS - вызовова t.Errorf не должно быть. Т.е. вызывать t.Errorf нужно только в случае возникновения ошибки.

    Должно быть приблизительно так
    type twoValue struct {
        x int
        y int
        expected int
    }
    
    var tests = []twoValue{
        { 1,2, 1 },
        { 2,1, 2 },
        { 2,2, 0 },
    }
    
    func TestQuestion(t *testing.T) {
    
      for _, value := range tests {
        res := Question(value.x, value.y)
        if res != value.expected {
          t.Errorf("при сравнении %v с %v получили, %v, а должно быть %v", value.x, value.y, res, value.expected)
        }
      }
    }
    Ответ написан
  • Как ограничить отправку запросов из горутин?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    import "time"
    
    rate := time.Second / 10
    throttle := time.Tick(rate)
    for req := range requests {
      <-throttle  // rate limit our Service.Method RPCs
      go client.Call("Service.Method", req, ...)
    }

    Более реальный пример
    import "time"
    
    rate := time.Second / 10
    burstLimit := 100
    tick := time.NewTicker(rate)
    defer tick.Stop()
    throttle := make(chan time.Time, burstLimit)
    go func() {
      for t := range tick.C {
        select {
          case throttle <- t:
          default:
        }
      }  // does not exit after tick.Stop()
    }()
    for req := range requests {
      <-throttle  // rate limit our Service.Method RPCs
      go client.Call("Service.Method", req, ...)
    }


    Пример взял тут
    https://github.com/golang/go/wiki/RateLimiting
    Ответ написан
    Комментировать
  • Как сделать bulk insert в GORM?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Можно использовать библиотеку https://github.com/t-tiger/gorm-bulk-insert

    Если не хочется тянуть лишние зависимости в проект - можно обернуть цикл в транзакцию, это даст выигрыш в производительности близкий к нормальным bulk insert.

    Примерно вот так:
    tx.Begin()
    for {
       DB.Create
    }
    tx.Commit()

    Советую присмотреться вот к этой библиотеке https://jmoiron.github.io/sqlx/
    Я её использую почти во всех проектах.
    Ответ написан
    Комментировать
  • Как приспособить программу к многочисленным изменениям?

    EvgenyMamonov
    @EvgenyMamonov
    Senior software developer, system architect
    Если я верно понял задачу, то у вас база в одном формате, а данные приходят в разных.
    Если это так, тогда вам нужно просто сделать 3 класса:
    1 - преобразовывает данные в формате 1 во внутреннюю структуру (пригодную для сохранения в базу)
    2 - преобразовывает данные в формате Х во внутреннюю структуру
    3 - сохраняет данные из внутренней структуры в базу
    Это стандартная практика для подобных задач.
    Если вам будет нужно сохранять данные не в базу, а куда то еще, например в облако - вы просто добавите еще один класс, который умеет сохранять данные в нужном формате/на облако.
    Ответ написан
    1 комментарий
  • Почему массив объектов, удовлетворяющих интерфейсу, не приводится к массиву этих интерфейсов?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вот так можете сделать
    items := make([]logged, 0)
    items = append(items, A{10})
    print(maxLogID(items))
    Ответ написан
  • Как научиться проектировать программы в ооп стиле?

    EvgenyMamonov
    @EvgenyMamonov
    Senior software developer, system architect
    Посмотрите в сторону предметно ориентированного проектирования (DDD).

    - https://habr.com/ru/post/269589/
    - https://habr.com/ru/post/269893/
    - https://habr.com/ru/post/267125/

    Хорошие книги по этой теме:
    - Роберт Мартин "Чистый код Создание, анализ и рефакторинг"
    - Роберт Мартин "Чистая архитектура"
    - Стив Макконнелл "Совершенный код"

    Если сможете найти исходники похожих на ваш проектов - изучите их,
    посмотрите как там решались подобные задачи.

    Еще очень хороший вариант - найти опытного разработчика и попросить его сделать review вашего кода, чтобы он объяснил вам как вот тут лучше сделать, а как там можно было сделать и почему именно так будет лучше.
    Ответ написан
    4 комментария
  • Django как сделать редирект на страницу с пагинацией?

    EvgenyMamonov
    @EvgenyMamonov
    Senior software developer, system architect
    Использовать HTTP_REFERER не вариант, это, как минимум не надёжно.

    Вы можете при переходе из страницы с группой фото запоминать номер страницы с которой вы перешли.
    Для этого можете использовать cookies, или добавить параметр в paginator' например grp_page_num (чтобы не потерять его при переходах).

    Вообще я бы вам посоветовал несколько изменить подход.
    Если у вас не стоит задача иметь отдельные страницы так, как вы описываете - тогда лучше не делать переход на страницу с всеми фото группу, а просто догружать фото при скроле страницы так, как это делают, например соц.сети. У них же ленты без paginator'а, они просто загружают новую порцию постов когда вы доскролили до конца. Как вариант можно сделать кнопку "еще" и загружать следующую порцию фото.
    Ответ написан
  • Как настроить Postgresql на виртуалке Ubuntu в облаке Яндекс?

    EvgenyMamonov
    @EvgenyMamonov
    Senior software developer, system architect
    проверьте запущен ли PostgreSQL, проверить можно, например, так
    netstat -n|grep -i postmaster
    Вывод будет примерно таким
    tcp        0      0 127.0.0.1:5432          0.0.0.0:*               LISTEN      2414/postmaster

    Подключаемся в этом случае таким образом
    psql -h 127.0.0.1 -p 5432
    Если у вас не запущен PostgreSQL - надо его запустить :)
    Ответ написан
    Комментировать
  • Как в Django оганичить доступ позьзователю некоторое время?

    EvgenyMamonov
    @EvgenyMamonov
    Senior software developer, system architect
    Как вариант - сделайте таблицу в базе (модель) с полями:
    user_id, page_id, can_access_till (дата до которой у пользователя есть доступ к этой странице)

    При просмотре какой то закрытой страницы делайте проверку есть ли в этой таблице данные текущего пользователя и делайте проверку чтобы can_access_till был меньше текущего времени.

    Ну и устаревшие записи из этой таблицы можно по крону удалять, скажем, раз в день, ночью.
    Ответ написан
    7 комментариев
  • Где плотно используется Python в вебе?

    EvgenyMamonov
    @EvgenyMamonov
    Senior software developer, system architect
    Например Python+Django используется в Instagram
    https://instagram-engineering.com/web-service-effi...

    YouTube, DropBox, Uber, Pinterest, Яндекс, Facebook - все они используют Python.

    Знаменитая фраза разработчиков Google: «Python where we can, C++ where we must», переводится как: используем «Python ― где можем, C++ ― где должны»

    Да, переписывать части на Си можно, делается это достаточно редко.

    Если будете писать под большие нагрузки - можно посмотреть в сторону Go lang.
    Ответ написан
    5 комментариев