123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- // 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 // Базовый адрес сервера
- urlPing 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,
- urlPing: 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
- }
- // Ping -- пингует указанный сервис в реальном времени
- func (sf *ClientAnonym) Ping() error {
- if sf.isClosed() {
- return fmt.Errorf("ClientAnonym.Ping(): client is closed")
- }
- resp, err := sf.client.Post(sf.urlPing, "application/x-www-form-urlencoded", nil)
- if err != nil {
- return fmt.Errorf("ClientAnonym.Ping(): in get url(%q), err=\n\t%w", sf.urlPing, err)
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- return fmt.Errorf("ClientAnonym.Ping(): url=`%v`, err=%s", sf.urlPing, resp.Status)
- }
- binBody, err := io.ReadAll(resp.Body)
- if err != nil {
- return fmt.Errorf("ClientAnonym.Ping(): 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()
- }
|