• Я усложняю или так правильно?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    Вы можете использовать любые правила которые считаете удобными.

    Например:
    1. вы можете группировать всё по объектам (так появляются каталоги users/v1)
    2. вы можете группировать всё по слоям (хранилища отдельно, сервисы с хендлерами отдельно, команды отдельны и так далее)

    Вам стоит выбрать правила по которым происходит импорт, иначе может сложиться ситуация когда users/v1 ссылается на groups/v1 а groups/v1 ссылается на users/v1 и приводит к не возможности это скомпилировать.

    Вот пример второго варианта:
    tree .
    .
    ├── Dockerfile
    ├── Makefile
    ├── README.md
    ├── api
    ├── dbconfig.yml
    ├── docker-compose.override.yml
    ├── docker-compose.yml
    ├── env
    │   ├── postgres.ci.env
    │   ├── postgres.env
    │   ├── postgres.example.env
    │   ├── testing.ci.env
    │   ├── testing.env
    │   └── testing.example.env
    ├── examples
    │   └── config.user.add.yaml
    ├── go.mod
    ├── go.sum
    ├── internal
    │   ├── commands
    │   │   ├── cli
    │   │   │   ├── command.go
    │   │   │   └── users
    │   │   │       └── command.go
    │   │   └── server
    │   │       └── command.go
    │   ├── constant
    │   ├── service
    │   │   └── service.go
    │   ├── services
    │   │   └── grpc
    │   │       └── users
    │   │           └── service.go
    │   └── storages
    │       ├── storages.go
    │       └── users
    │           ├── interface.go
    │           ├── postgres
    │           │   ├── add.go
    │           │   ├── storage.go
    │           │   └── storage_test.go
    │           ├── redis
    │           │   ├── add.go
    │           │   ├── storage.go
    │           │   └── storage_test.go
    │           ├── tests
    │           │   ├── add.go
    │           │   ├── delete.go
    │           │   └── generage.go
    │           └── user.go
    ├── main.go
    └── migrations
        ├── 20231217192045-schema.sql.sql
        └── 20231217192051-other.sql.sql
    
    19 directories, 35 files
    Ответ написан
    Комментировать
  • Как шарить экземпляр класса между http хендлерами используя контекст?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    В https://golang.org/pkg/net/http/#Handler находим интерфейс Handler:

    type Handler interface {
            ServeHTTP(ResponseWriter, *Request)
    }


    Создаём реализацию:
    package handlers
    
    import (
            "project/context"
    )
    
    type AddItemsRequestData struct {
            Name string `json:"name"`
    }
    
    type AddItems {
            ctx *context.Context
    }
    
    func NewAddItems(ctx *context.Context) (*AddItems, error) {
            return &AddItems{
                    ctx: ctx,
            }, nil
    }
    
    func (h *AddItems) ServeHTTP(res http.ResponseWriter, req *http.Request) {
            ...
            h.ctx.Profiler.Get("name")   <-- здесь для каждого запроса пользователя вызывается метод Get() заранее инициализированного объекта
            ...
    }


    package main
    
    import (
            "log"
    
            "project/context"
            "project/handlers"
    )
    
    func main() {
            ...
            ctx, err := context.New()
            if err != nil {
                    log.Fatalf(err)
            }
            ...
            http.Handle("/profiler/", CheckHandler(handlers.NewAddItem(ctx)))
            ...
            err := http.ListenAndServe(":80", nil)
            log.Fatalf(err)
    }
    
    // CheckHandler проверяет создание хандлера
    func CheckHandler(handler http.Handler, err error) http.Handler {
            if err != nil {
                    log.Fatalf("handler error: %v", err)
            }
            if handler == nil {
                    log.Fatalf("empty handler")
            }
            return handler
    }


    Это если нужно передать данные в хандлер на этапе инициализации. Если же при каждом запросе нужно что-то инициализировать - то это можно сделать в самом хандлере. Например:

    package handlers
    
    import (
            "project/context"
            "project/profiler"
    )
    
    type AddItemsRequestData struct {
            Name string `json:"name"`
    }
    
    type AddItems {
            ctx *context.Context
    }
    
    func NewAddItems(ctx *context.Context) (*AddItems, error) {
            return &AddItems{
                    ctx: ctx,
            }, nil
    }
    
    func (h *AddItems) ServeHTTP(res http.ResponseWriter, req *http.Request) {
            ...
            pr := profiler.New() <-- здесь для каждого запроса пользователя создаётся новый экземпляр
            pr.SetName("name")
            ...
    }


    В качестве project/context используется обычный пакет со структурой в которой хранятся любые объекты. Например:
    package context
    
    import (
            "project/profiler"
    )
    
    // Context содержит необходимые библиотеки, такие как хранилища например
    type Context struct {
            Profiler *profiler.Profiler
    }
    
    // New возвращает новый Context
    func New(
            profiler *profiler.Profiler
    ) (
            *Context,
    ) {
            context := &Context{
                    Profiler: profiler,
            }
            return context
    }
    Ответ написан
    1 комментарий
  • Как записать число дробью в Go?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    Сделайте структуру, передавайте в неё текст, дальше сделайте из этого пакет и добавьте методы по управлению, конвертации и так далее...

    package main
    
    import (
    	"fmt"
    	"strings"
    	"errors"
    )
    
    func main() {
    	fmt.Println("Hello, playground")
    	
    	d := New()
    	d.Parse("1/4")
    	fmt.Println(d)
    }
    
    type Shot struct {
    	i string                   // number
    	f string                   // fraction
    }
    
    func New() (s *Shot) {
    	s = new(Shot)
    	return
    }
    
    func (s *Shot) Parse(text string) (err error) {
    	v := strings.Split(text, "/")
    	if len(v) != 2 {
    		err = errors.New("Error parse shot")
    		return	
    	}
    	s.i = v[0]
    	s.f = v[1]
    	return
    }
    Ответ написан
    Комментировать
  • Как парсить время на Go?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    alst161: можете так же посмотреть библиотеку https://github.com/mantyr/newtime , специально для того что бы кодировать в более привычном виде сделал. Внутри time и ряд функций для работы в объектном стиле.

    Например так:
    package main
    
    import (
        "github.com/mantyr/newtime"
        "fmt"
    )
    
    func main() {
        date := newtime.NewTime()
        date.Parse("16-08-2016 15:11:06", "dd-mm-yyyy HH:ii:ss")
        fmt.Println(date.Format("YYYY-mm-dd HH:ii:ss"))  // "2016-08-16 15:11:06"
    }
    Ответ написан
    Комментировать
  • Go. Почему перенаправляет на "/"?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    https://ru.wikipedia.org/wiki/Список_кодов_состоян...

    • 301 Moved Permanently — запрошенный документ был окончательно перенесен на новый URI, указанный в поле Location заголовка. Некоторые клиенты некорректно ведут себя при обработке данного кода. Появился в HTTP/1.0.
    • 302 Found, 302 Moved Temporarily — запрошенный документ временно доступен по другому URI, указанному в заголовке в поле Location. Этот код может быть использован, например, при управляемом сервером согласовании содержимого. Некоторые клиенты некорректно ведут себя при обработке данного кода. Введено в HTTP/1.0.
    • 303 See Other — документ по запрошенному URI нужно запросить по адресу в поле Location заголовка с использованием метода GET несмотря даже на то, что первый запрашивался иным методом. Этот код был введён вместе с 307-ым для избежания неоднозначности, чтобы сервер был уверен, что следующий ресурс будет запрошен методом GET. Например, на веб-странице есть поле ввода текста для быстрого перехода и поиска. После ввода данных браузер делает запрос методом POST, включая в тело сообщения введённый текст. Если обнаружен документ с введённым названием, то сервер отвечает кодом 303, указав в заголовке Location его постоянный адрес. Тогда браузер гарантировано его запросит методом GET для получения содержимого. В противном случае сервер просто вернёт клиенту страницу с результатами поиска. Введено в HTTP/1.1.
    • 304 Not Modified — сервер возвращает такой код, если клиент запросил документ методом GET, использовал заголовок If-Modified-Since или If-None-Match и документ не изменился с указанного момента. При этом сообщение сервера не должно содержать тела. Появился в HTTP/1.0.
    • 305 Use Proxy — запрос к запрашиваемому ресурсу должен осуществляться через прокси-сервер, URI которого указан в поле Location заголовка. Данный код ответа могут использовать только исходные HTTP-сервера (не прокси). Введено в HTTP/1.1.
    • 306 (зарезервировано) — использовавшийся раньше код ответа, в настоящий момент зарезервирован. Упомянут в RFC 2616 (обновление HTTP/1.1).
    • 307 Temporary Redirect — запрашиваемый ресурс на короткое время доступен по другому URI, указанный в поле Location заголовка. Метод запроса (GET/POST) менять не разрешается. Например, POST запрос должен быть отправлен по новому URI тем же методом POST. Этот код был введён вместе с 303 вместо 302-го для избежания неоднозначности. Введено в RFC 2616 (обновление HTTP/1.1).


    Если в крадце, то разница лишь в нюансах.
    • 303 сделает временный редирект и уберёт POST запрос заменив его на GET
    • 307 сделает POST запрос на новый адрес если изначально был именно POST


    Для себя сделал вот так:
    package tmpl
    
    import (
        "net/http"
    )
    
    func Redirect(w http.ResponseWriter, address string) {
        if address == "" {
            address = "/"
        }
        w.Header().Set("Location", address)
        w.Header().Set("Cache-Control", "private, no-store, max-age=0, must-revalidate")
        w.WriteHeader(303)                                                                      // see https://ru.wikipedia.org/wiki/Список_кодов_состояния_HTTP#303
    }
    Ответ написан
    Комментировать
  • PHP как передавать POST большие файлы быстро?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    Что бы не упираться в ограничения:
    • разбивать файл на чанки равной длины
    • загружать чанки параллельно на несколько разных суб.доменов (st1.example.com, st2.example.com ... stN.example.com)
    • использовать http2 соединение
    • передавать файлы (и чанки в том числе) в бинарном виде


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

    Замерять скорость:
    • сети между серверами и клиентом
    • пропускную способность веб-сервера
    • общую нагрузку на сервер
    • io диска куда складываются данные


    Возможно есть проблема:
    • со скоростью работы диска, может быть он сбоит и потерял 80% своей скорости
    • с загрузкой канала между сервером и клиентом
    • слишком много запросов к веб-серверу и он просто блокируется постоянно
    • браузер перегружен плагинами или слишком много js кода который убивает отзывчивость


    Так же попробуйте отправить файл на другой сервер похожим образом и сравнить.

    Более подробно про отправку файла чанками:
    sendFile: function(file) {
                if (file.size > this.maxFileSize) {
                    this.alert('Файл слишком большой!');
                    return false;
                }
    
                var fd = new FormData();
                      fd.append("file", file);
    
                // Создаем запрос
                var xhr = new XMLHttpRequest();
                xhr.upload.addEventListener('progress', context({obj: this}, this.uploadProgress), false);
                xhr.onreadystatechange = context({obj: this, html: html}, obj.uploadFinish);
                xhr.open('POST', this.handler);
                xhr.send(fd);
            },


    Тут мы видим xhr.send(fd), теперь идём в один из примеров, например https://learn.javascript.ru/xhr-resume и видим вариант с отправкой части файла:
    var slice = file.slice(10, 100); // прочитать байты с 10-го по 99-й включительно
    xhr.send(slice); // ... и отправить эти байты в запросе.


    Если у вас получится сделать механизм многопоточной загрузки то можете контрибьютить в этот репозитарий: https://github.com/mantyr/js-drag-n-drop-file-uplo... так как он чуть более объектный чем классический dropzone
    Ответ написан
    2 комментария
  • Как отправить текст на почту?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    Всё что до секции
    <?php
    нее выполняется в PHP, а значит у вас нет переменной outArray, тем более вы не можете написать так:
    $work=(outArray);
    .

    Сверху у вас javascript который выполняется в браузере, снизу у вас PHP который выполняется на сервере (до того как страница доберётся до браузера). Дальше сами.
    Ответ написан
    Комментировать
  • Стоит ли подготовить все запросы в init?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    Прежде чем использовать подготовленные запросы прочитайте это:
    - go-database-sql.org/prepared.html
    - go-database-sql.org/surprises.html

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

    Если вам важно увеличить пропускную способность простых INSERT то посмотрите в сторону HandlerSocket, уверен под Golang оно уже должно было появиться, если нету - сможете реализовать самостоятельно, протокол не особо сложный.
    Ответ написан
    Комментировать
  • Как удалить первый елемент из map?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    Обычный map не гарантирует порядок. Варианты:
    • можно сделать ordered map, но с этим окуратнее, куча подводных камней
    • можно хранить в элементах время и либо ходить по нему через range удаляя все устаревшие элементы либо делать проверку при доступе к данным, если данные устарели - удаляем.


    Можно сделать чуть проще:
    • делаем ch := make(chan string, 100000)
    • далее время от времени смотрим len(ch) и если он выше ну скажем половины от того что туда может влезть (подберите имперически исходя из количества и скорости пополнения данными или высчитывайте гибко) и удаляете тот элемент что первым читается из ch


    https://play.golang.org/p/sopEgcwRZC
    package main
    
    import (
    	"fmt"
    	"sync"
    )
    
    type Map struct {
        sync.RWMutex
        ch chan string
        d map[string]string
    }
    
    func NewMap() (m *Map) {
        m = new(Map)
        m.ch = make(chan string, 10000)
        m.d  = make(map[string]string)
        return
    }
    
    func (m *Map) Add(key, value string) {
        m.Lock()
        defer m.Unlock()
        m.check(50, true)
        m.d[key] = value
        m.ch <- key
    }
    
    func (m *Map) Get(key string) string {
        m.RLock()
        defer m.RUnlock()
        if v, ok := m.d[key]; ok {
            return v
        }
        return ""
    }
    
    func (m *Map) Check() {
        m.check(50, false)
    }
    
    func (m *Map) check(len_max int, is_lock bool) {
        ch_len := len(m.ch)
        if ch_len > len_max {
            if !is_lock {
                m.Lock()
                defer m.Unlock()
            }
    
            ch_len = len(m.ch)
            for i := 0; i < ch_len - len_max; i++ {
                delete(m.d, <- m.ch)
            }
        }
    }
    
    func (m *Map) Len() int {
        return len(m.ch)
    }
    
    func main() {
    	m := NewMap()
    	for i := 0; i < 1000; i++ {
                m.Add(fmt.Sprintf("key_%s", i), fmt.Sprintf("value_%s", i))	
    	}
    	fmt.Println(m.Len())
    }

    • храним не более len_max элементов+1 в map, при этом удаляем сначала самые старые в порядке их добавления
    • есть проблема - если ключ был добавлен несколько раз то программа не видит что элемент обновился и всё равно его удалит как старый
    • что бы этого не происходило нужно добавлять дату в ключь и проверять, если ключ имеет другую дату (или вместо даты инкрементный счётчик) то такой элемент пропускается (мы знаем что он уже где-то есть дальше в m.ch)
    Ответ написан
  • Как правильно грузить данные на клиент?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    Разделите на три части:
    • первый экран - critical css, critical js, critical data - всё необходимое что бы показать первый экран в браузере максимально быстро
    • основной контент - то что в любом случае понадобится для второго экрана и вспомогательных элементов
    • всё остальное - рекламные баннеры, графики, аналитика и прочее


    Более подробно посмотрите здесь:


    Так же можно добавить 4 часть - это то что возможно понадобится потому что это горячие данные (например новости, список активности друзей и так далее)
    Ответ написан
    Комментировать
  • Как удалить уменьшенные копии картинок в WordPress?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    Посмотрите все адреса уменьшенных картинок в таблице wp_postmeta, выглядит так:

    mysql> show columns  from wp_postmeta;
    +------------+---------------------+------+-----+---------+----------------+
    | Field      | Type                | Null | Key | Default | Extra          |
    +------------+---------------------+------+-----+---------+----------------+
    | meta_id    | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
    | post_id    | bigint(20) unsigned | NO   | MUL | 0       |                |
    | meta_key   | varchar(255)        | YES  | MUL | NULL    |                |
    | meta_value | longtext            | YES  |     | NULL    |                |
    +------------+---------------------+------+-----+---------+----------------+


    mysql> select * from wp_postmeta where meta_key = '_wp_attachment_metadata' limit 0,1
    | meta_id | post_id | meta_key                | meta_value
    |    5804 |    1962 | _wp_attachment_metadata  | a:5:{s:5:"sizes";a:2:{s:6:"medium";a:4:{s:9:"mime-type";s:9:"image/png";s:4:"file";s:76:"300x240/99c3f32407680f486bc6991db142828c36c1c91fffbfd88d2c6a1990035bb0a3.png";s:5:"width";i:300;s:6:"height";i:240;}s:9:"thumbnail";a:4:{s:5:"width";i:150;s:6:"height";i:150;s:9:"mime-type";s:9:"image/png";s:4:"file";s:76:"150x150/99c3f32407680f486bc6991db142828c36c1c91fffbfd88d2c6a1990035bb0a3.png";}}s:10:"image_meta";a:12:{s:13:"shutter_speed";s:1:"0";s:11:"orientation";s:0:"";s:8:"keywords";a:0:{}s:8:"aperture";s:1:"0";s:7:"caption";s:0:"";s:9:"copyright";s:0:"";s:12:"focal_length";s:1:"0";s:3:"iso";s:1:"0";s:6:"credit";s:0:"";s:6:"camera";s:0:"";s:17:"created_timestamp";s:0:"";s:5:"title";s:0:"";}s:4:"file";s:75:"images/99c3f32407680f486bc6991db142828c36c1c91fffbfd88d2c6a1990035bb0a3.png";s:5:"width";i:1000;s:6:"height";i:631;}  |


    Как не сложно догадаться:
    • пути у картинок относительные от wp-content/upload
    • в meta_value находится php serialize - php.net/manual/ru/function.serialize.php , php.net/manual/ru/function.unserialize.php
    • в post_id находится идентификатор не поста, а картинки (select * from wp_posts where id = 28;)
    • в meta_id просто идентификатор конкретной записи мета-данных, по нему потом можно будет удалить запись о размерах


    Как удалить файлы "в идеологии" WordPress не подскажу, но вы с этим и сами справитесь зная где данные.
    Ответ написан
    Комментировать
  • Как правильно искать фрилансера в стартап?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    Спросите у потенциального исполнителя как он будет это решать, попросите его предложить вам решение. В рамках вашей задачи, конечно.

    Если человек сможет объяснить как строятся те вещи которые вам нужны и сможет ответить на все или большую часть ваших вопросов которые возникнут по ходу диалога - можете смело нанимать. Конечно если не будет каких-то других отталкивающих факторов.
    Ответ написан
    Комментировать
  • Golang Benchmark почему разные результаты?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    Во время подсчёта "покрытия" часть ресурсов тратится на эту задачу и она не обязательно должна быть эффективной. По этому бенчмарк в это время ресурсов недополучает и вообще может показывать не корректные замеры так как фоновые процедуру могут выполняться и нагружать CPU не равномерно.

    При бенчмарке желательно:
    • что бы нигде ничего не было запущено
    • что бы нигде ничего не работало в фоне
    • что бы не нужные куски кода были отделены от таймера (например не нужные инициализации, подготовка данных)

    Нужны ли бенчмарки? Бенчмарки нужны что бы отбраковать совсем плохие результаты или выбрать между двумя очень похожими. Так же они дают представление о том сколько тратится на каждую итерацию того кода что вы тестируете.

    Пример простого бенчмарка с инициализацией вынесеной за таймер:

    package conf // <-- здесь название библиотеки для которой предназначены бенчмарки
    
    import (
        "testing"
    )
    
    func BenchmarkLoadGo(b *testing.B) {
        conf := NewConfig()
        conf.SetDefaultFile("properties")
        conf.SetDefaultCatalog("./testdata") // default "./configs"
    
        b.ResetTimer() // <-- обнуляем таймер что бы вся "сложная" и "единомоментная" работа не попала в подсчёт
    
        for i := 0; i < b.N; i++ {
            go conf.GetSection("server_1", "storage") // <-- в данном случае тестируется сценарий в конкурентном виде, но чаще всего этого не требуется и конкретно в том коде откуда взят бенчмарк есть отдельный бенч для не конкурентного доступа и сравнивая их можно иметь представление о том как оно себя поведёт в обоих сценариях.
        }
    }
    Ответ написан
    Комментировать
  • Стоит ли так делать?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    len([]byte("string")) // <-- количество байт

    Другое дело что базы данных часто:
    • сжимают данные (особенно текстовые), по этому картинки и не рекомендуют складывать в базу данных
    • хранят множество различных индексов на каждую запись, что бы было удобно доставать данные
    • хранят указатели на данные
    • хранят логи (бинарные или текстовые)
    • имеют кеш в памяти
    • имеют лог транзакций (или не имеют, в зависимости от базы)
    • много чего еще

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

    Из in-memory баз данных с гарантированным сохранением на диск сейчас активно пиарят Tarantool, там как раз все данные в памяти, но при записи они становятся доступными только когда произошла запись в лог на диск. Если есть достаточное количество оперативки то хороший выбор, на нём живёт множество сервисов "большого" русского интернета.

    Если оперативки мало (500 мегабайт например), то больше подойдёт классическая база данных с кешированием горячих данных.
    Ответ написан
    Комментировать
  • Как бы вы организовали кодогенерацию сайта?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    Берите уже PHP, что уж там:)
    Ответ написан
  • Как юзать WebSocket?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    package main
    
    import (
        "github.com/gorilla/websocket"
        "net/http"
    )
    
    var upgrader = websocket.Upgrader{
        ReadBufferSize:  4096,
        WriteBufferSize: 4096,
        CheckOrigin: func(r *http.Request) bool {
            return true
        },
    }
    
    func wsHandler(w http.ResponseWriter, r *http.Request) {
        // вот тут вы можете проверить login/password и прочее или же сделать для этого отдельный метод /login, а тут проверять лишь есть ли у него сессия
        ws, err := upgrader.Upgrade(w, r, nil)
        if _, ok := err.(websocket.HandshakeError); ok {
            http.Error(w, "Not a websocket handshake", 400)
            return
        } else if err != nil {
            return
        }
    
        ws_list.Append(ws, r) // вот тут вы уже знаете что сокет вам подходит и от какого он пользователя и дальше заносите его в какое-то хранилище которое их хранит и по которому потом рассылаете данные.
    }


    И опять таки, читайте документацию, https://godoc.org/github.com/gorilla/websocket
    Ответ написан
  • Как элегантно реализовать задания на сайте?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    https://play.golang.org/p/NJ-X38UrWC

    В дополнение к комментарию на другой ответ.
    package events
    
    import (
      "sync"
    )
    
    var events *Events
    
    type Events struct {
        sync.RWMutex
        list map[string]func(ps []string)
    }
    
    func NewEventsList() (list *Events) {
        list = new(Events)
        list.list = make(map[string]func(ps []string))
        return list
    }
    
    func init() {
      events = NewEventsList()
    }
    
    func Add(name string, callback func(ps []string)) {
        events.Add(name, callback)
    }
    
    func Call(name string, ps []string) bool {
        return events.Call(name, ps)
    }
    
    
    // Подписка на событие
    func (l *Events) Add(name string, callback func(ps []string)) {
        l.Lock()
        defer l.Unlock()
        l.list[name] = callback
    }
    
    // Вызов события
    func (l *Events) Call(name string, ps []string) bool {
        l.RLock()
        defer l.RUnlock()
        if f, ok := l.list[name]; ok {
            go f(ps)
            return true
        }
        return false
    }
    Ответ написан
    Комментировать
  • Как продавать ПО?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    Если выдаёте ключ - значит лицензия на использование. Если делаете кастомизацию, настройку или установку - добавляете ещё и услугу.
    Ответ написан
    4 комментария
  • Как на php запретить различным процессам одновременно читать файл?

    @mantyr
    Пишу много Golang кода с удовольствием:)
    Почитайте про блокировку файлов. php.net/manual/ru/function.flock.php

    Пример из документации
    <?php
    $fp = fopen("/tmp/lock.txt", "r+");
    
    if (flock($fp, LOCK_EX)) { // выполняем эксклюзивную блокировку
        ftruncate($fp, 0); // очищаем файл
        fwrite($fp, "Что-нибудь пишем сюда\n");
        fflush($fp);        // очищаем вывод перед отменой блокировки
        flock($fp, LOCK_UN); // отпираем файл
    } else {
        echo "Не удалось получить блокировку !";
    }
    fclose($fp);
    ?>
    Ответ написан
    Комментировать
  • Как распарсить JavaScript Object (не валидный JSON) в Golang?

    @mantyr Автор вопроса
    Пишу много Golang кода с удовольствием:)
    Я пока не сравнивал два эти варианта по производительности, но в будущих задачах предпочту использовать первый вариант.

    Вариант №1
    На stackoverflow посоветовали взглянуть на библиотеку https://godoc.org/launchpad.net/rjson#Unmarshal однако в ней не оказалось поддержки значений в одинарных ковычках.

    В результате сделал форк и добавил поддержку одинарных ковычек.
    package main
    
    import (
        "fmt"
        "github.com/mantyr/rjson"
    )
    
    func main() {
        data := []byte(`{
            middle : {
                src: "pictures/product/123.jpg",
                place : '#preview-img',                // Added support for JavaScript object parsing
                title: "title"
            }
        }`)
    
        var v struct {
            Middle struct {
                Src string
                Place string
                Title string
            }
        }
        err := rjson.Unmarshal(data, &v)
        fmt.Println(v)   // print {{pictures/product/123.jpg #preview-img title}}
        fmt.Println(err) // print <nil>
    }

    https://github.com/mantyr/rjson
    Оригинальный пакет от автора тут: https://github.com/rogpeppe/rjson

    Если у вас есть пожелания по дополнению rjson - напишите мне или предложите pool request.

    Вариант №2
    Можно запустить VM https://github.com/robertkrimen/otto и передав ей немного дополненный текст получить из неё нужное поле.
    package main
    
    import (
        "testing"
        "github.com/robertkrimen/otto"
    )
    
    func BenchmarkVMGet(b *testing.B) {
        vm := otto.New()
    
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            st := `
                {
                        middle : {
                        src: 'pictures/product/middle/14906_middle.jpg',
                        place : '#preview-img',
                        title: '343880-090-slantsy-nike-benassi-just-do-it'
                    }
                }
            `
    
            vm.Run(`
                obj = `+st+`
                src = obj.middle.src;
            `)
            src, err := vm.Get("src")    // <-----
            _ = src
            _ = err
        }
    }


    # go test -bench=".*" ottotest
    testing: warning: no tests to run
    PASS
    BenchmarkVMGet	   30000	     58103 ns/op
    ok  	ottotest	2.367s


    Однако есть минусы:
    • может прилететь произвольный кусок текста и выполнять его бездумно не правильно (вспомним eval в php)
    • достаточно медленно
    • используется очень большая библиотека - плохая зависимость


    Плюсы:
    • всё таки достаточно быстро
    • можно не останавливать VM, а пушить туда через Run, но если пушить бесконечно остаётся вопрос - умрёт однажды или не умрёт - без хорошего знания внутренностей библиотеки сказать этого заранее нельзя


    Я так же ищу другие варианты, чисто под задачу распарсить object и не выполнять лишних операций.
    Ответ написан
    Комментировать