Ответы пользователя по тегу Go
  • Как настроить subroute в gorilla mux golang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Надо убрать '/' из example.com/, т.е. должно быть router.Host("example.com").Subrouter()
    Ответ написан
    1 комментарий
  • Где лучше хранить глобальные структуры?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Если брать стандартную структуру проекта - я бы отталкивался от того есть ли смысл эту структуру делать доступной. Если да - тогда смотрел бы в сторону /pkg, если нет /internal.

    По хорошему, я бы сделал отдельный пакет для этой структуры и в зависимости от того используется ли она для внутренних целей или нет - положил бы пакет в /pkg или в /internal

    Для чего используется ваша глобальная структура?
    Ответ написан
    Комментировать
  • Как разрешить вызов api контроллера, только одному серверу по ip (который отправляет вебхук)?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Чаще всего, в таких случаях, используют секретный ключ, который передают или в параметрах запроса (так лучше не делать, т.к. секретный ключ будет виден в логах промежуточных прокси и сервера) или в HTTP заголовке (этот вариант безопаснее, чем передача в параметрах запроса).

    Реже используют вариант с цифровой подписью запроса. Этот вариант ощутимо безопаснее, чем передавать секретный ключ в открытом виде. При таком варианте вы не только знаете о том, что запрос отправил нужный вам сервер, но и о том, что запрос не был изменён никем (например прокси сервером).

    Но оба эти варианта требуют доработки со стороны отправляющего запрос сервера.
    Как я понимаю для вас этот вариант не доступен.

    В принципе вариант защиты по IP адресу вполне нормальный.
    Самый простой способ - настроить firewall, т.е. закрыть доступ всем к вашему сервису и открыть только для IP сервера, который делает запросы.

    Если вариант с firewall не подходит - можно обеспечивать защиту на стороне Go.
    IP можно получать из запроса HTTP https://pkg.go.dev/net/http#Request

    Вот готовый пример
    package main
    
    import (
        "fmt"
        "log"
        "net/http"
    )
    
    func handler(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Your IP is %s", r.RemoteAddr)
    }
    
    func main() {
        http.HandleFunc("/", handler)
        log.Fatal(http.ListenAndServe(":8080", nil))
    }

    Иногда (зависит от настроек веб сервера) реальный IP может приходить в HTTP заголовках запроса
    ip := r.Header.Get("X-Forwarded-For")
    // или
    ip := r.Header.Get("X-Real-IP")
    Ответ написан
    3 комментария
  • Куда и как добавить асинхронность в телеграм бота?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Можно сделать работу бота через webhook'и, тогда каждый запрос будет изначально обрабатываться в отдельной горутине.
    Плюс при таком подходе можно горизонтально масштабировать нагрузку, т.е. запустить бот на нескольких серверах и через какой нибудь балансировщик раскидывать на них запросы.
    Ответ написан
  • Как сделать, чтобы в редактируемом циклом значении сохранялись результаты итераций?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    попробуйте такой вариант
    var fileWork = ioutil.ReadFile("testFile.txt")
    // создаём переменную которую будем перезаписывать, сразу наполняем данными и сразу приводим к string чтобы было удобно работать
    new_fileWork := string(fileWork)
    for _, link := range arrLinks {        
        var regul = link + ".txt"
        myRegexp, err := regexp.Compile(regul)
        // new_fileWork передаем как параметр, его же и обновляем, за счёт этого при каждой итерации данные не теряются как в вашем примере
        new_fileWork = myRegexp.ReplaceAllString(new_fileWork , link)   // Редактируем.
    }
    var new_fileWorkB = []byte(new_fileWork)

    Если не поможет - сделайте песочницу чтобы можно было посмотреть полный код и напишите какой результат ожидаете получить.
    Ответ написан
  • Когда использование Cgo оправданно?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Основной недостаток использования Cgo - это снижение производительности.
    Вызовы C/C++ достаточно затратны по ресурсам, т.к. C ничего не знает о данных в Go и для вызова C необходимо полностью сохранять все регистры и переключать стек, за счёт этого и возрастают накладные расходы, соответственно снижается производительность.

    Использование Cgo имеет смысл, когда есть объёмные библиотеки написанные на C/C++, которые можно использовать. При этом написание кода на чистом Go намного затратнее, чем использование этих библиотек с Cgo.

    > в каких кейсах следует использовать cgo для улучшения производительности
    На сколько я понимаю при вызове простых функций производительность не улучшится, а наоборот, скорее ухудшится.
    Но не исключаю, что есть кейсы, когда есть серьёзные расчёты/жёсткое управление памятью (частые выделения/освобождения), когда за счёт того, что в этом случае не будет использован сборщик мусора можно получить увеличения производительности.

    У меня был подобный кейс на Perl, но принцип тот же.
    При скачивании HTML страниц размер занимаемой RAM скриптом постоянно увеличивался и в итоге "съедал" всю память на сервере.

    Задача скрипта была скачивать HTML страницы, извлекать из них все ссылки на внешние ресурсы.
    Я принял решение и написал функцию на С, которая выкачивала страницу, извлекала ссылки, очищала память и возвращала в Perl уже готовый список ссылок. Скрипты перестали постоянно "пухнуть", их можно было запустить в несколько раз больше по количеству на том же сервере + производительность стала явно выше.

    В общем всё сильно зависит от задачи, но, думаю, более 90% кейсов будет связано с тем, что намного дешевле использовать готовую библиотеку C/C++ с Cgo, чем переписать эту библиотеку на чистом Go.
    Ответ написан
    Комментировать
  • Почему при отправке письма выдает ошибку?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Подозреваю, что ошибка в этой строке, у вас указывается порт (:25), надо его убрать.
    // у вас host = "https://smtp.mail.ru:25", а должен быть "smtp.mail.ru"
    auth := smtp.PlainAuth("", from, password, host)


    На сколько я вижу вы разбираете пример из официального пакета https://pkg.go.dev/net/smtp
    Добавил туда пару комментариев
    import (
    	"log"
    	"net/smtp"
    )
    
    func main() {
    	// Set up authentication information.
            // тут порт не указан, скорее всего ругается именно в этой строке
    	auth := smtp.PlainAuth("", "user@example.com", "password", "mail.example.com")
    
    	// Connect to the server, authenticate, set the sender and recipient,
    	// and send the email all in one step.
    	to := []string{"recipient@example.net"}
    	msg := []byte("To: recipient@example.net\r\n" +
    		"Subject: discount Gophers!\r\n" +
    		"\r\n" +
    		"This is the email body.\r\n")
            // тут порт указан, т.е. host:port, но не указывается протокол типа https, тут протокол SMTP
    	err := smtp.SendMail("mail.example.com:25", auth, "sender@example.org", to, msg)
    	if err != nil {
    		log.Fatal(err)
    	}
    }
    Ответ написан
  • Доступ к определенному значению данной библиотеки?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вот так можно добраться
    package main
    
    import (
        "fmt"
    
        php "github.com/kovetskiy/go-php-serialize"
    )
    
    func main() {
        s := `a:6:{s:7:"__flash";a:1:{s:3:"mes";s:5:"Hello";}s:4:"__id";s:36:"3c5035d9-aea1-4f08-8325-9e598921e2a9";}`
        val, err := php.Decode(s)
        if err != nil {
            fmt.Println(err)
            return
        }
    
        session := val.(map[interface {}]interface {})
        flash := session["__flash"].(map[interface {}]interface{})
    
        fmt.Println("id", session["__id"])
        fmt.Println("mes", flash["mes"])
    }

    По хорошему надо проверять прошло ли нормально приведение типов, чтобы небыло panic
    session, ok := val.(map[interface {}]interface {})
        if !ok {
            // с val явно что-то не так, оно не соответствует типу map[interface {}]interface {}
        }
    
        flash, ok := session["__flash"].(map[interface {}]interface{})
        if !ok {
            // ...
        }
    Ответ написан
    Комментировать
  • Как сделать нормальное выключение Websocket сервера?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Если я правильно понял задачу - вам нужно корректно закрыть все соединения и завершить работу WebSocket сервера.

    По сути аккуратное закрытие клиентского WebSocket соединения выглядит так:
    - сервер отправляет в сокет frame с типом Close
    - клиент при получении frame с типом Close формирует ответ с типом Close
    - сервер ждёт ответ от клиента с типом Close (клиент подтверждает закрытие соединения)
    - сервер со своей стороны закрываете сокет, клиент со своей стороны закрывает сокет

    Т.е. вам просто нужно в цикле во все открытые сокеты отправить frame'ы с типом Close, дождаться ответа с типом Close, после чего закрыть сокет, или по таймауту закрыть сокет.

    Для работы с websocket'ами я использовал вот этот пакет https://pkg.go.dev/github.com/gobwas/ws

    Пример отправки frame'а с типом Close
    closeFrame := ws.NewCloseFrame([]byte{})
    // отправляем 
    err := ws.WriteFrame(wsconn, closeFrame)


    Пример чтения ответа от клиента
    header, err := ws.ReadHeader(wsconn)
    if header.OpCode == ws.OpClose {
        // клиент подтвердил закрытие, соединение можно закрывать
        wsconn.Close()
    }
    Ответ написан
    3 комментария
  • Как понять когда ставить указатель?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Указатель, по сути, хранит адрес каких то данных (переменной, структуры, слайса и т.д.).
    Иными словами он "указывает" на область данных.

    Чтобы понять как им пользоваться - сначала нужно понять для чего он вообще используется.
    Представьте что у вас есть структура, которая содержит много разных полей, в которых содержится большой объём данных.

    Например:
    type BigStruct struct {
        field1 int
        filed2 string
        field3 uint
        field4 []byte
        ...
        field50 string
    }

    Предположим, что после создания этой структуры и заполнения всех её полей она занимает в памяти 300мб.

    Если вы сделаете функцию, которая будет принимать такую структуру как агрумент, например вот так
    func Report(s BigStruct)
    то при каждом вызове этой функции вся структура (300мб) каждый раз будут копироваться.
    Пример:
    s := BigStruct{}
    // заполняем поля
    Report(s)


    Чтобы избежать такой мега нагрузки - можно передавать не копию данных, а указатель, т.е. адрес в памяти, где хранится сама структура.
    Для этого нужно объявить агрумент функции как указатель, т.е. ставим *.
    func Report(s *BigStruct)
    А код уже будет выглядеть вот так.
    s := BigStruct{}
    // заполняем поля
    Report(&s) // тут добавился & - берём адрес структуры, а не саму структуру

    Или второй вариант
    // создаём переменную s сразу с типом указатель на BigStruct
    s := &BigStruct{}
    // заполняем поля
    Report(s) // поскольку s уже является указателем - & тут не нужен


    В общем * используется:
    - когда нужно объявить переменную
    var s *BigStruct
    - когда нужно прочитать/записать значение, которое храниться по адресу указателя
    var i *int
        i = new(int)
        *i = 10 // пишем значение
    
        fmt.Printf("i: %v\n", i)
        fmt.Printf("*i: %v\n", *i)

    Вывод будет примерно таким
    i: 0xc0000160d8 (это адрес памяти, где лежит значение переменной i)
    *i: 10 (а это её значение)


    & (амперсанд) используется когда нужно получить адрес переменный.

    Еще один вариант применения - если нужно иметь возможность модифицировать данные у параметра функции. Если нужны примеры - дайте знать, я напишу.
    Ответ написан
    12 комментариев
  • Почему файлы открываются нестабильно?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Подозреваю, что не работает os.OpenFile, а ioutil.ReadFile работает.
    И скорее всего дело в правах os.OpenFile("file.txt", os.O_RDWR, 0666).

    Начните с того, что обработайте ошибку, чтобы можно было понять что именно не так.
    f, err := os.OpenFile("file.txt", os.O_RDWR, 0666)
    if err != nil {
        log.Fatalln(err)
    }


    PS: ioutil.ReadFile("file.txt") для открытия файла вызывает функцию OpenFile(name, O_RDONLY, 0)
    Ответ написан
  • Как выполняется валидация покупок?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Судя по методам интерфейса Storage нужен для того, чтобы эта библиотека имела возможность сохранять в вашу базу данные подписки/оплаты.

    Можете начать с того, что сделаете пустую реализацию, посмотрите какие параметры вам будут приходить/какие будут нужны и там уже разберётесь как вам удобнее сохранить данные.
    Допустим вы будете хранить данные в MySQL... тогда реализация может выглядеть вот так
    type MySQLStorage struct {
        db *sql.DB
    }
    
    func NewMySQLStorage(db *sql.DB) *MySQLStorage {
        return &MySQLStorage{
            db: db,
        }
    }
    
    func (sg *MySQLStorage) StorePurchases(ctx context.Context, sp []*Purchase) ([]*Purchase, error) {
        for _, p := range sp {
            fmt.Printf("%+v\n", p)
        }
    
        return sp, nil
    }
    
    func (sg *MySQLStorage) StoreSubscriptionPurchases(ctx context.Context, sp []*SubscriptionPurchase) ([]*SubscriptionPurchase, error) {
        for _, p := range sp {
            fmt.Printf("%+v\n", p)
        }
    
        return sp, nil
    }

    т.е. вызов может быть таким
    db := connectToDB...
    sg := NewMySQLStorage(db)
    NewValidate(sg  "",  googleConfig)
    Ответ написан
    Комментировать
  • Как правильно повторно измерить время обхода директори?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Скорее все каждый раз измерение показывает верные результаты несмотря на такую разницу.
    Думаю дело в кеше данных с диска, за счёт кеширования повторные обходы работают быстрее.
    Чтобы можно было точнее сказать - нужно видеть ваш код.
    Ответ написан
  • Test in golang: simple function?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Как вариант можно что-то типа такого
    package main
    
    import "testing"
    
    func Test_sum(t *testing.T) {
    
        tt := []struct {
            args GetArgs
            sum int
        }{
            {Args {2, 4}, 6},
            {Args {2, -2}, 0},
        }
    
        for _, tc := range tt {
            tci := tc.args.(GetArgs)
            s := Sum(tci)
            if s != tc.sum {
                t.Errorf("sum of %v and %v should be %v, received- %v", tc.args.GetA(), tc.args.GetB(), tc.sum, s)
            }
        }
    }
    Ответ написан
    Комментировать
  • Почему при открытии файла срабатывает ошибка "The system cannot find the file specified"?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Предполагаю, что после замены os.Open(fileDir.Name()) на os.Open(path.Join(DirFiles, fileDir.Name())) всё заработает.

    Думаю, что проблема вот в чём.
    Скорее всего у вас структура файлов близка к этой
    ./files/file1
    main.go
    Вы получаете список файлов из папки ./files/, и в os.Open передаётся только название файла (file1.go), т.е. os.Open пытается открыть его в текущей папке, а не в папке ./files/, по этому и не может его открыть.
    А если передать в os.Open("./files/file1") - тогда всё заработает (при условии, что есть права на это)
    Ответ написан
  • Сколько занимает в памяти Struct{}{}?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Всё верно, она занимает 0 байт.
    Можно проверить:
    package main
    
    import (
        "fmt"
        "unsafe"
    )
    
    type S1 struct {
        f1 int
    }
    
    func main() {
        s1 := S1{}
        s2 := struct{}{}
    
        fmt.Printf("s1 size: %v\n", unsafe.Sizeof(s1))
        fmt.Printf("s2 size: %v\n", unsafe.Sizeof(s2))
    }

    Вывод
    s1 size: 8
    s2 size: 0


    Там, где в мапе нужны только ключи - я всегда делаю значение struct{}{}
    Ответ написан
    4 комментария
  • Как привести тип os.FileInfo к типу string?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    ioutil.ReadDir возвращает []fs.FileInfo
    Это интерфейс, выглядит вот так
    type FileInfo interface {
    	Name() string       // base name of the file
    	Size() int64        // length in bytes for regular files; system-dependent for others
    	Mode() FileMode     // file mode bits
    	ModTime() time.Time // modification time
    	IsDir() bool        // abbreviation for Mode().IsDir()
    	Sys() interface{}   // underlying data source (can return nil)
    }


    Т.е. в вашем случае filepath.Ext(string(fileDir)) можно заменить на filepath.Ext(fileDir.Name())
    Это то, что нужно или нужно что-то другое?
    По примеру кода не совсем понятно для чего вам нужно приведение к os.FileInfo/string.

    Посмотрел os.FileInfo
    Он определён как type FileInfo = fs.FileInfo
    Т.е. грубо это тот же интерфейс, методы которого можно вызывать точно также.
    Ответ написан
  • Как сделать поиск файлов только в одной папке; указать текущую директорию?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Чтобы указать текущую папку - можно указать "точку" в качестве имени папки, чтобы не было рекурсии - воспользуйтесь функцией ioutil.ReadDir.
    package main
    
    import (
        "fmt"
        "io/ioutil"
        "log"
    )
    
    func main() {
        // текущая папка
        files, err := ioutil.ReadDir(".")
        if err != nil {
            log.Fatal(err)
        }
    
        for _, file := range files {
            fmt.Println(file.Name(), file.IsDir())
        }
    }
    Ответ написан
    5 комментариев
  • Поиск по байтовому массиву?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Думаю это сильно зависит от того, как часто вам нужно делать поиск в этом файле.
    Если не часто - тогда можно просто вычитывать данные порциями, и искать последовательность в них.
    Если же очень часто - нужно понять что там за данные и думать как сделать индекс для поиска в этом файле.

    Как вариант, можно запустить несколько горутин.
    Т.е. пока одна горутина читает одну порцию данных из файла в память - другая ищет в предыдущей порции нужную вам последовательность.

    Еще, как вариант, можно сделать простенький индекс (упрощаю, чтобы было легче писать понятный пример)

    Допустим у нас есть файл с таким содержимым
    0x02 0x09 0x02 0x05 0x09 0x09, 0xf1 0xfa 0x02

    Найти нужно последовательность 0x02 0x05

    Строим индекс [массив байт от 0x00 до 0xff][слайс адресов этого байта в файле]
    т.е.
    [значение байта][адрес этого байта в файле1, адрес этого байта в файле2, адрес этого байта в файле3...]
    Для указанного файла индекс получится вот такой
    [0x00][]
    [0x01][]
    [0x02][0x00, 0x02, 0x08]
    ...
    [0x05][0x03]
    ...
    [0x09][0x01, 0x04, 0x05]
    ...
    [0xf1][0x06]
    ...
    [0xfa][0x07]
    ...

    Первый байт в искомой последовательности 0x02 0x05 - будет 0x02.
    Мы берём слайс в массиве по индексу 0x02 и получаем список адресов где в файле есть 0x02 - [0x00, 0x02, 0x08]
    А дальше вы проверяете вашу последовательность только начиная с полученных смещений
    Т.е. проверяем следующий байт по адресу 0x00 + 1 == 0x05 (второму символу последовательности) или нет.
    Если нет - значит берём след. смещение 0х02 и опять проверяем равен ли след. символ 0x05.
    Равен, значит нашли последовательность.
    Ответ написан
  • Ошибка ERROR: column "order" is of type json[] but expression is of type record go gorm что делать?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Скорее всего должно быть как то так
    type Order struct {
      ID uint `gorm:"primaryKey;autoIncrement;unique"json:"id"`
      Owner string `json:"owner"`
      Status string `gorm:"default:ordered"json:"status"`
      Phone int `json:"phone"`
      Order []OrderProduct `gorm:"foreignKey:OrderId"json:"products"`
    }
    
    type OrderProduct struct {
      ID uint `gorm:"primaryKey;autoIncrement;unique"json:"id"`
       // добавляем это поле, чтобы была связь с таблицей Orders
      OrderID uint `json:"-"`
      Brand string `json:"brand"`
      ItemId int `json:"item_id"`
      Img string `json:"img"`
      ItemTotal int `json:"item_total"`
      Price string `json:"price"`
      Quantity int `json:"quantity"`
      Title string `json:"title"`
      Type string `json:"type"`
    }
    Ответ написан