// package client_anonym -- HTTP-клиент для выполнения анонимных запросов на сервер package client_anonym import ( "fmt" "io" "net" "net/http" "net/url" "strings" "sync" "time" "git.p78su.freemyip.com/svi/gostore/pkg/msg_net" "git.p78su.freemyip.com/svi/gostore/pkg/types" ) // ClientAnonym -- HTTP-клиент для выполнения анонимных запросов на сервер type ClientAnonym struct { servCtx types.IServCtx client *http.Client url string // Базовый адрес сервера urlTime string // URL для получения времени urlPut string // URL для записи данных urlRead string // URL для чтения данных urlDelete string // URL для удаления данных urlFind string // URL для поиска данных block sync.RWMutex // Блокировка проверки работы клиента isWork bool // Признак работы клиента } // NewClientAnonym -- конструктор func NewClientAnonym(serv types.IServCtx, url string) (*ClientAnonym, error) { { // Предусловия if serv == nil { return nil, fmt.Errorf("NewClientAnonym(): IServCtx==nil") } if url == "" { return nil, fmt.Errorf("NewClientAnonym(): url is empty") } } sf := &ClientAnonym{ servCtx: serv, client: &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: 5 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, ForceAttemptHTTP2: false, MaxIdleConns: 10, IdleConnTimeout: 30 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 5 * time.Second, }, }, url: url, urlTime: url + "anonym/time", urlPut: url + "anonym/put", urlRead: url + "anonym/get", urlDelete: url + "anonym/del", urlFind: url + "anonym/find", } sf.servCtx.Wg().Add(1) go sf.close() sf.isWork = true return sf, nil } // Проверка на признак работы клиента func (sf *ClientAnonym) isClosed() bool { sf.block.RLock() defer sf.block.RUnlock() return !sf.isWork } // Find -- ищет ключи по префиксу func (sf *ClientAnonym) Find(prefixKey string) ([]string, error) { if sf.isClosed() { return nil, fmt.Errorf("ClientAnonym.Find(): client is closed") } if prefixKey == "" { return nil, fmt.Errorf("ClientAnonym.Find(): prefixKey is empty") } formPost := url.Values{ "key": {prefixKey}, } resp, err := sf.client.Post(sf.urlFind, "application/x-www-form-urlencoded", strings.NewReader(formPost.Encode())) if err != nil { return nil, fmt.Errorf("ClientAnonym.Find(): in get url(%q), err=\n\t%w", sf.urlFind, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("ClientAnonym.Find(): err=%s", resp.Status) } binBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("ClientAnonym.Find(): in read body, err=\n\t%w", err) } req, err := msg_net.UnmarshalFindRequest(prefixKey, binBody) if err != nil { return nil, fmt.Errorf("ClientAnonym.Find(): in unmarshal find request, err=\n\t%w", err) } return req.Values(), nil } // Delete -- удаление ключа из хранилища func (sf *ClientAnonym) Delete(key string) error { if sf.isClosed() { return fmt.Errorf("ClientAnonym.Delete(): client is closed") } if key == "" { return fmt.Errorf("ClientAnonym.Delete(): key is empty") } formPost := url.Values{ "key": {key}, } resp, err := sf.client.Post(sf.urlDelete, "application/x-www-form-urlencoded", strings.NewReader(formPost.Encode())) if err != nil { return fmt.Errorf("ClientAnonym.Delete(): in get url(%q), err=\n\t%w", sf.urlDelete, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("ClientAnonym.Delete(): err=%s", resp.Status) } return nil } // Read -- получает значение по ключу func (sf *ClientAnonym) Read(key string) (string, error) { if sf.isClosed() { return "", fmt.Errorf("ClientAnonym.Read(): client is closed") } if key == "" { return "", fmt.Errorf("ClientAnonym.Read(): key is empty") } formPost := url.Values{ "key": {key}, } resp, err := sf.client.Post(sf.urlRead, "application/x-www-form-urlencoded", strings.NewReader(formPost.Encode())) if err != nil { return "", fmt.Errorf("ClientAnonym.Read(): in get url(%q), err=\n\t%w", sf.urlRead, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("ClientAnonym.Read(): err=%s", resp.Status) } binBody, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("ClientAnonym.Read(): err=\n\t%w", err) } strResp := string(binBody) return strResp, nil } // Put -- помещает в хранилище ключ и значение func (sf *ClientAnonym) Put(key string, binData []byte) (string, error) { if sf.isClosed() { return "", fmt.Errorf("ClientAnonym.Ping(): client is closed") } if key == "" { return "", fmt.Errorf("ClientAnonym.Put(): key is empty") } postForm := url.Values{ "key": {key}, "val": {string(binData)}, } resp, err := sf.client.Post(sf.urlPut, "application/x-www-form-urlencoded", strings.NewReader(postForm.Encode())) if err != nil { return "", fmt.Errorf("ClientAnonym.Put(): in get url(%q), err=\n\t%w", sf.url, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("ClientAnonym.Put(): err=%s", resp.Status) } binBody, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("ClientAnonym.Put(): err=\n\t%w", err) } strResp := string(binBody) return strResp, nil } // Time -- пингует указанный сервис в реальном времени func (sf *ClientAnonym) Time() error { if sf.isClosed() { return fmt.Errorf("ClientAnonym.Time(): client is closed") } resp, err := sf.client.Get(sf.urlTime) if err != nil { return fmt.Errorf("ClientAnonym.Time(): in get url(%q), err=\n\t%w", sf.urlTime, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("ClientAnonym.Time(): err=%s", resp.Status) } binBody, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("ClientAnonym.Time(): err=\n\t%w", err) } _ = binBody return err } // Ожидает закрытия сервиса и освобождает сервис func (sf *ClientAnonym) close() { <-sf.servCtx.Ctx().Done() sf.block.Lock() defer sf.block.Unlock() if !sf.isWork { return } sf.isWork = false sf.servCtx.Wg().Done() }