• Почему так происходит и как делать правильно?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Чтобы получить тот результат, который вы ожидаете, нужно использовать select вместо switch.
    Обратите внимание на хороший пример https://tour.golang.org/concurrency/6
    Работающий пример вашей реализации

    package main
    
    import (
    	"log"
    	"time"
    )
    
    func add(ch chan<- string) {
    	for {
    		time.Sleep(time.Second * 2)
    		ch <- "hi"
    	}
    }
    
    func exit(ch chan<- string) {
    	time.Sleep(time.Second * 5)
    	ch <- "close"
    }
    
    func main() {
    	ch := make(chan string, 1000)
    
    	go add(ch)
    
    	go exit(ch)
    
    LOOP:
    	for {
    		select {
    		case val := <- ch:
    			switch val {
    			case "close":
    				log.Println("Close")
    				break LOOP
    			case "hi":
    				log.Println("Hi")
    			}
    		default:
    			log.Println("Default")
    			time.Sleep(time.Second * 1)
    		}
    	}
    }


    https://play.golang.org/p/m1R8CXNKEYq
    Ответ написан
    2 комментария
  • Как сделать одну структуру для разных источников json?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Можно сделать unmarshal в map[string]string и уже извлечь данные от туда.
    А дальше, например, можно сделать такую функцию
    func NewPostFromIncomingPost(incomingPost map[string]string) (*Post, error) {
    	post := &Post{}
    
    	if title, exists := incomingPost[`name`]; exists {
    		post.Title = title
    	} else if title, exists := incomingPost[`title`]; exists {
    		post.Title = title
    	} else {
    		return nil, errors.New(`title not found`)
    	}
    
    	if text, exists := incomingPost[`text`]; exists {
    		post.Text = text
    	} else if text, exists := incomingPost[`description`]; exists {
    		post.Text = text
    	} else {
    		return nil, errors.New(`text not found`)
    	}
    	
    	return post, nil
    }

    Вот тут работающий пример https://play.golang.org/p/gizeAA8uWKt
    Текст работающего примера

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"errors"
    )
    
    type Post struct {
    	Title string
    	Text  string
    }
    
    func NewPostFromIncomingPost(incomingPost map[string]string) (*Post, error) {
    	post := &Post{}
    
    	if title, exists := incomingPost[`name`]; exists {
    		post.Title = title
    	} else if title, exists := incomingPost[`title`]; exists {
    		post.Title = title
    	} else {
    		return nil, errors.New(`title not found`)
    	}
    
    	if text, exists := incomingPost[`text`]; exists {
    		post.Text = text
    	} else if text, exists := incomingPost[`description`]; exists {
    		post.Text = text
    	} else {
    		return nil, errors.New(`text not found`)
    	}
    	
    	return post, nil
    }
    
    func main() {
    	jsonSource1 := []byte(`{"title": "Title", "text": "Text"}`)
    	jsonSource2 := []byte(`{"name": "Title", "description": "Text"}`)
    
    	var incomingPost1 map[string]string
    	if err := json.Unmarshal(jsonSource1, &incomingPost1); err != nil {
    		panic(err)
    	}
    
    	post1, err := NewPostFromIncomingPost(incomingPost1)
    	fmt.Printf("post1: %+v\nerr: %+v\n\n", post1, err)
    
    	var incomingPost2 map[string]string
    	if err := json.Unmarshal(jsonSource2, &incomingPost2); err != nil {
    		panic(err)
    	}
    
    	post2, err := NewPostFromIncomingPost(incomingPost2)
    	fmt.Printf("post2: %+v\nerr: %+v\n\n", post2, err)
    }

    Ответ написан
    3 комментария
  • Как в golang получение From из Header письма (imap)?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Тип заголовка, если я не ошибаюсь, должен быть MIMEHeader, т.е. это обычный map[string][], где ключ - это заголовок + методы Get, Values

    Вот так вы увидите то, что вообще там есть (все ключи и значения map)
    log.Printf("entity: %+v\n", entity.Header)

    А вот так можете получить первое значение заголовка
    log.Printf("entity: %+v\n", entity.Header.Get("From"))


    Или все значения заголовка
    log.Printf("entity: %+v\n", entity.Header.Values("To"))


    Подробнее о методах можно посмотреть тут https://godoc.org/net/textproto#MIMEHeader
    Ответ написан
    3 комментария
  • Как создать и объединить аудио в golang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Если говорить про реализацию на Go - как вариант...

    1. создаёте файл и encoder как тут https://github.com/viert/go-lame

    2. в цикле читаете ваши файлы при помощи mp3 decoder'a, например как тут https://github.com/hajimehoshi/go-mp3/blob/master/...
    и пишете их содержимое в encoder

    Там, где нужна пустота - попробуйте записать/сгенерировать 1сек или меньше тишины и когда нужно - пишите её в encoder.

    Если задачу нужно просто решить и использовать Go не обязательно - её можно решить при помощи консольной команды `ffmpeg` примерно так:
    ffmpeg file1.mp3 file2.mp3 silence1sec.mp3 silence1sec.mp3 output.mp3


    Если всё же нужно реализовать на Go - то есть, как минимум две обёртки
    https://github.com/giorgisio/goav
    https://github.com/xfrr/goffmpeg

    Можете сначала сделать чтобы из консоли всё заработало, а потом уже "покрутить" обёртки
    Ответ написан
    Комментировать
  • Как получить файл из письма на GoLang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    package main
    
    import (
        "io"
        "io/ioutil"
        "log"
    
        "github.com/emersion/go-imap"
        "github.com/emersion/go-imap/client"
        "github.com/emersion/go-message"
    )
    
    func main() {
        log.Println("Connecting to server...")
    
        c, err := client.DialTLS("xxxxxxxxx", nil)
        if err != nil {
            log.Fatal(err)
        }
    
        log.Println("Connected")
    
        defer c.Logout()
    
        if err := c.Login("xxxxxxxxxxxxx", "xxxxxxxxxx"); err != nil {
            log.Fatal(err)
        }
        log.Println("Logged in")
    
        mbox, err := c.Select("INBOX", false)
        if err != nil {
            log.Fatal(err)
        }
    
        seqset := new(imap.SeqSet)
        seqset.AddRange(1, mbox.Messages)
    
        messages := make(chan *imap.Message, 10)
        done := make(chan error, 1)
        go func() {
            done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchRFC822}, messages)
        }()
    
        for msg := range messages {
            for _, r := range msg.Body {
                entity, err := message.Read(r)
                if err != nil {
                    log.Fatal(err)
                }
    
                multiPartReader := entity.MultipartReader()
    
                for e, err := multiPartReader.NextPart(); err != io.EOF; e, err = multiPartReader.NextPart() {
                    kind, params, cErr := e.Header.ContentType()
                    if cErr != nil {
                        log.Fatal(cErr)
                    }
    
                    if kind != "image/png" && kind != "image/gif" {
                        continue
                    }
    
                    c, rErr := ioutil.ReadAll(e.Body)
                    if rErr != nil {
                        log.Fatal(rErr)
                    }
    
                    log.Printf("Dump file %s", params["name"])
    
                    if fErr := ioutil.WriteFile("/tmp/"+params["name"], c, 0777); ferr != nil {
                        log.Fatal(fErr)
                    }
                }
            }
        }
    
        if err := <-done; err != nil {
            log.Fatal(err)
        }
    
        log.Println("Done")
    }

    Пример взял тут https://stackoverflow.com/questions/55203878/how-t...
    Ответ написан
    1 комментарий
  • Как извлечь GET-параметр (токен) из URL?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Чтобы функция Query работала так, как вам нужно - URL должен быть вида ?param1=value1&param2=value2, тогда вы сможете извлекать значения из map по ключам param1, param2 и т.д.

    Думаю в вашем случае лучше воспользоваться просто Split или другой подходящей функцией из пакета strings.
    Вот работающий пример со Split.
    package main
    
    import (
        "fmt"
        "strings"
    )
    
    func main() {
        url := `https://site.com/regLink?$2a$10$/VTT.GtRslGWLhZL5d`
        result := strings.SplitN(url, `?`, 2)
        fmt.Printf("token: %s\n", result[1])
    }
    Ответ написан
    3 комментария
  • Как динамически создать гиперссылку (на Golang)?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вам нужно %d заменить на %s, у вас тип данных строковый, а %d - это число
    Полностью рабочий пример
    package main
    
    import "fmt"
    
    func main() {
       var myLink = `https://site.com/Link?`
       var tokenString = string(`hash....`)
       var dataRespon = fmt.Sprintf(`Данные сохранены. Нажмите <a href="%s%s">ссылку</a>.`, myLink, tokenString)
       fmt.Printf(dataRespon)
    }
    Ответ написан
    2 комментария
  • Как реализовать свой API на домене?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Можно.

    Регистрируете домен и прописываете в его настройках IP вашего сервера.

    Далее у вас два варианта.

    Если у вас всего один сервис на сервере - можете слушать сразу 80й порт, вместо 1234.
    И это уже заработает.

    Если доменов на сервере будет много -
    - cтавите веб сервер, например nginx, который слушает 80й порт на вашем IP
    - настраиваете в nginx виртуальный хост для нужного вам домена и там прописываете, чтобы запросы, перенаправлялись на ваш сервис IP:порт(1234)

    Пример конфига nginx (виртуальный хост)

    server {
        listen *:80;
        server_name тут_ваш_домен;
    
        location / {
            proxy_pass http://тут_ip_вашего_сервиса:1234;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }

    Ответ написан
    5 комментариев
  • Как запустить программу как демон в Go?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Если сделать вот так ./main & - тогда при завершении ssh сессии отвалится и сам демон.

    Всё действительно сильно зависит от ОС.

    Если говорить о Linux и о самом простом способе, то можно вот так
    nohup ./main > error.log 2>&1 &
    Этот способ подходит для любых бинарников, которые не делают fork, т.е. ведут себя также, как и Go'шный http ListenAndServe.

    Второй вариант - это запуск сервиса через systemd или init.d в зависимости от того, какой в вашем Linux.
    Я предпочитаю именно этот вариант, особенно, когда нельзя использовать Docker (например на виртуальных машинах с Virtuozzo).
    Для systemd (например на CentOS) можно создать файл /etc/systemd/system/yourservice.conf примерно такого содержания
    [Unit]
    Description=YourServie
    After=network.target
    After=syslog.target
    
    [Service]
    User=nobody # user ID под которым должен работать ваш демон
    Group=nobody
    Type=simple
    WorkingDirectory=/opt/yourservice
    ExecStart=/opt/yourservice/yourservice >> /var/log/yourservice.log 2>&1
    Restart=always
    
    [Install]
    WantedBy=multi-user.target


    Третий вариант - использовать Docker, инструкций много в интернете, если этот вариант актуален - напишите, я помогу найти нормальную.

    Четвёртый вариант, который обычно используется в ПО типа Nginx и т.д.
    В общем случае нужно:
    - породить новый процесс, т.е. сделать fork (вызвать системный вызов)
    - настроить вывод stdout, stderr в файлы (опционально)
    - "отвязаться от сессии" вызвать системный вызов setsid
    - сделать chdir куда нужно
    - настроить обработку сигналов SIGINT, SIGTERM, чтобы иметь возможность корректно завершать работу или перечитывать конфиги

    Возможно я что-то упустил в этом списке, давно уже демонов таким образом не делал, использую или Docker или systemd/init.d, так намного проще :)

    Могу еще добавить, что 4й вариант для Go считается антипаттерном.

    Вот тут https://socketloop.com/tutorials/golang-daemonizin... есть пример, правда он не очень удачный, там очень много нюансов.
    Ответ написан
    Комментировать
  • Как правильно вынести map/массив в отдельный пакет?

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

    Рабочий пример вашего кода может выглядеть вот так:
    ./localization/localization.go
    package localization
    
    var L = map[string]string{
      "start_message": "Привет. Я - Бот",
    }


    main.go
    package main
    
    import (
      "fmt"
      local "./localization"
    )
    
    func main() {
        fmt.Printf(local.L["start_message"])
    }
    Ответ написан
    Комментировать
  • Как сохранить картинку полученную с Frontend в папку?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вот тут есть пример и backend на Go, и frontend на чистом HTML
    https://tutorialedge.net/golang/go-file-upload-tut...
    Ответ написан
    Комментировать
  • Как округлить число в большую сторону в golang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Используйте функцию Ceil из пакета math
    fmt.Println(math.Ceil(11.3)) // 12
    Полный пример

    package main
    
    import (
    	"fmt"
    	"math"
    )
    
    func main() {
    	fmt.Println(math.Ceil(11.3))
    }

    Ответ написан
    Комментировать
  • Как правильно в go структуру из базы в шаблон html в gin?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Итерировать нужно слайс, а не структуру, т.е. []Users.
    Вот работающий пример.
    rows, err := conn.Queryx("SELECT id, name FROM users")
    if err != nil {
          log.Fatalln(err)
    }
    defer rows.Close()
    
    users := []User{}
    
    for rows.Next() {
        if rows.StructScan(&user) != nil {
          log.Fatalln(err)
        }
        users = append(users, user)
    }
    
    ctx.HTML(http.StatusOK, "users/index.tmpl",  gin.H{"values": users})
    Ответ написан
    Комментировать
  • Почему крашится программа в linux?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    обработайте ошибку Exec, скорее всего там ошибка, а res в этом случае nil, по этому и panic
    res, err := p.Exec(js)
    if err != nil {
       log.Panic(err)
    }
    Ответ написан
    4 комментария
  • Какая есть хорошая книги про микросервисную архитектуру?

    EvgenyMamonov
    @EvgenyMamonov
    Senior software developer, system architect
    Лучшее, что я читал по микросервисам - "Крис Ричардсон - Микросервисы. Паттерны разработки и рефакторинга", но там немногим больше 500 страниц :)

    Эта книга поможет избежать массы ошибок проектирования, распределённый монолит намного хуже, чем просто монолит :)

    Правда там примеры на Java, но легко "заходят".

    Ну и сайт автора книги https://microservices.io/ - там почти всё тоже, что и в книге.

    Примеры кода из книги https://github.com/microservices-patterns/ftgo-app...
    Ответ написан
    1 комментарий
  • Почему анонимная функция работает только с последним значением переменной?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Тут суть в том, что в момент, когда анонимная функция начнёт выполняться - выполнение цикла к тому моменту уже закончится и i будет равна 10.
    Т.е. шедулер не успевает запустить горутины, они фактически запускаются уже после цикла, но часть из них может успеть и во время цикла запуститься...

    Самый простой способ получить желаемый результат передать значение как параметр
    go func(idx int) {
        ...
    }(i)


    Полный пример:
    package main
    
    import (
        "sync"
    )
    
    const N = 10
    
    func main() {
        m := make(map[int]int)
        wg := &sync.WaitGroup{}
        mu := &sync.Mutex{}
        wg.Add(N)
        for i := 0; i < N; i++ {
            k := 5
            go func(idx int) {
                defer wg.Done()
                mu.Lock()
                println(idx, k)
                m[idx] = k
                mu.Unlock()
            }(i)
        }
        wg.Wait()
        println(len(m))
        println(m[0])
    }
    Ответ написан
    4 комментария
  • Чему равна переменная r после второго вызова panic?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Выполнение функции f() завершается после первого panic, потом отрабатывает defer.
    Второй panic выполнен не будет, т.к. выполнение функции уже завершилось и до него очередь не дойдёт.
    Можно добавить немного log.Println в ваш пример и проследить ход выполнения, например вот так
    package main
    
    import "log"
    
    func f() {
      defer func() {
        if r := recover(); r != nil {
          log.Printf("recover:%#v", r)
        }
      }()
      log.Println("Start first panic")
      panic(1)
      log.Println("After first panic")
      panic(2)
      log.Println("After second panic")
    }
    
    func main() {
      f()
      log.Println("After f()")
    }

    Вывод будет таким
    2020/07/20 23:14:07 Start first panic
    2020/07/20 23:14:07 recover:1
    2020/07/20 23:14:07 After f()
    Ответ написан
    4 комментария
  • Почему одна функция возвращает true, а вторая false?

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

    Сделайте fmt.Printf("Type: %T\n", InitEfaceType()) и вы увидите тип "main.S"

    Чтобы иметь возможность проверить интерфейс на nil можете сделать вот так:
    func InitEfaceType() interface{} {
      var s *S
      return s
    }
    ...
    fmt.Println(InitEfaceType() == (*S)(nil))

    полный код

    package main

    import (
    "fmt"
    )
    type S struct{}

    func (s S) F() {}

    func InitPointer() *S {
    var s *S
    return s
    }

    func InitEfaceType() interface{} {
    var s *S
    return s
    }

    func InitType() S {
    var s S
    return s
    }

    func main() {
    fmt.Println(InitPointer() == nil)
    fmt.Println(InitEfaceType() == (*S)(nil))
    //fmt.Println(InitType() == nil)
    }

    Ответ написан
    5 комментариев
  • Почему в результате выводятся различные адреса?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Это происходит потому, что вы храните значения, а не указатели и, соответственно, вот тут for i, a := range t.array в `a` - создаётся копия value.
    Чтобы адреса были одинаковые нужно хранить указатели, а не значения, т.е. нужно делать вот так
    type Test struct {
    	array []*Array
    }

    Поправил ваш код в песочнице, теперь адреса одинаковые
    Ответ написан
    4 комментария
  • Какую часть сервера лучше писать на PHP/Java/Go/C#/Rust вместо Node.js?

    EvgenyMamonov
    @EvgenyMamonov
    Senior software developer, system architect
    Максим, всё описанное ниже я пишу исходя из личного опыта, с некоторыми моими утверждениями можно поспорить :)

    Так или иначе, но самая ресурсоёмкая задача, почти всегда, это работа с базой.

    Несколько лет назад я делал бенчмарки Python, PHP, Node, Go.
    Для меня были важны две вещи:
    1 - скорость ответа сервера/кол-во запросов в секунду
    2 - объём сервиса в памяти, т.к. от этого зависит стоимость ресурсов

    На тесте, где сервисы не делали запросы в базу - из всех четверых лучше всего отработал Go с приличным отрывом, цифры, к сожалению, уже не помню.

    Но вся эта разница сошла на нет, как только добавился всего один простой SQL запрос в базу, в таблицу с 10 строками. И на этом фоне разница по скорости ответа была меньше 10%.

    Иными словами если ваш сервис работает с базой - критической разницы по скорости работы между Go/Rust/PHP/Node/Java, увы, не будет.

    Но другое дело если для нас важно сколько памяти "съедает" каждый сервис.

    Например у вас пошли нагрузки и вам нужно горизонтально масштабирватся, т.е. запустить, скажем 100-10000 эксемпляров вашего сервиса.

    Вот тут уже становится интереснее :)

    Один экземпляр Go занимал в памяти порядка 6мб, при том, что Pytho+Django порядка 60мб.
    Node уже не помню сколько, но что-то близкое к Python'у.
    Вот тут уже, когда серверов у вас будет много - количество серверов с Go у вас будет в 10 раз меньше :)
    Знаю случаи, когда с почти 40 серверов с API на Node перешли на 2 сервера с Go.

    От части по этому и еще по ряду причин, последние несколько лет я использую Go и Python.
    Лично я просто в мега восторге от Go :)
    Еще Go идеально подходит для написания сетевых сервисов, CLI утилит и т.д.
    Например Docker, Kubernetes и еще куча всего написаны на Go.
    Я делал подобные вещи на разных языках, и ни на чём, как на Go не получался такой красивый и понятный код, который при этом работает достаточно хорошо.

    Про PHP, если знаете его, можете писать на нём, если не знаете - учить не советую, как минимум потому, что есть Python, на нём код куда более читабельный получается, возможностей, вакансий с нормальной з\п больше.

    На C# писать не довелось, сказать о нём ничего не могу.

    Писал на Java несколько лет, но она мне очень не нравится :) Особенно, когда есть Go :)
    Если будете учить Java - готовьтесь к тому, что вы будете обслуживать Legacy код, или скорее ...нокод :)
    Но вакансий с хорошей з/п по Java тоже много. Как минимум без денег не останетесь точно.

    Rust имеет смысл использовать, когда у вас очень большая нагрузка и для вас критична latency.
    Например для показа рекламы нужно ответить за 100мс иначе вашу рекламу просто не покажут.
    Вот тут Rust выиагает у Go за счёт того, что у Go будут периодические "провалы" во время сборки мусора.
    В остальном, по моему мнению, Rust проиграет за счёт большего времени по разработке, худшей читаемости кода.

    С другой стороны, если у вас есть рабочий сервис на Node, то вместо перехода на Rust, явно буде лучше сделать просто модуль на C/C++ для Node и всё будет летать + полный контроль выделения и освобождения памяти.

    Я такую схему с модулями на C/С++ не раз использовал на Perl'е.
    Очень помогало особенно там, где нужно было чётко освобождать память, чтобы скрипты со временем не пухли.

    Надеюсь вам это как то поможет определиться :)
    Ответ написан
    4 комментария