|
|
@@ -0,0 +1,163 @@
|
|
|
+// package store_client -- HTTP-клиент для выполнения запросов на сервер
|
|
|
+package store_client
|
|
|
+
|
|
|
+import (
|
|
|
+ "context"
|
|
|
+ "encoding/json"
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "log"
|
|
|
+ "net"
|
|
|
+ "net/http"
|
|
|
+ "os"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "git.p78su.freemyip.com/svi/gostore/internal/store_user"
|
|
|
+)
|
|
|
+
|
|
|
+// IStoreClient -- HTTP-клиент для выполнения запросов на сервер
|
|
|
+type IStoreClient interface {
|
|
|
+ // Get -- получить значени ключа
|
|
|
+ Get(key string) ([]byte, error)
|
|
|
+ // Put -- разместить ключ на сервере
|
|
|
+ Put(key string, val []byte) error
|
|
|
+ // Del -- удаляет запись по ключу
|
|
|
+ Del(key string)
|
|
|
+ // Find -- найти ключи по префиксу
|
|
|
+ Find(prefixKey string) ([]string, error)
|
|
|
+ // Time -- возвращает время сервера
|
|
|
+ Time() (string, error)
|
|
|
+}
|
|
|
+
|
|
|
+// StoreClient -- HTTP-клиент для выполнения запросов на сервер
|
|
|
+type StoreClient struct {
|
|
|
+ login string
|
|
|
+ pass string
|
|
|
+ url string
|
|
|
+ urlParam string // Сумма логина и пароль
|
|
|
+ client *http.Client
|
|
|
+}
|
|
|
+
|
|
|
+// NewStoreClient -- возвращает новый HTTP-клиент для выполнения запросов на сервер
|
|
|
+func NewStoreClient() (IStoreClient, error) {
|
|
|
+ user, err := store_user.NewStoreUser()
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("NewStoreClient(): in create IStoreUser, err=\n\t%w", err)
|
|
|
+ }
|
|
|
+ url := os.Getenv("STORE_URL")
|
|
|
+ if url == "" {
|
|
|
+ return nil, fmt.Errorf("NewStoreClient(): env STORE_URL not set")
|
|
|
+ }
|
|
|
+ sf := &StoreClient{
|
|
|
+ login: user.Login(),
|
|
|
+ pass: user.Pass(),
|
|
|
+ url: url,
|
|
|
+ client: &http.Client{
|
|
|
+ Transport: &http.Transport{
|
|
|
+ Dial: (&net.Dialer{
|
|
|
+ Timeout: 3 * time.Second,
|
|
|
+ KeepAlive: 6 * time.Second,
|
|
|
+ }).Dial,
|
|
|
+ IdleConnTimeout: 10 * time.Second,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ sf.urlParam = "/" + sf.login + "/" + sf.pass + "/"
|
|
|
+ if _, err := sf.Time(); err != nil {
|
|
|
+ return nil, fmt.Errorf("NewStoreClient(): in check connect, err=\n\t%w", err)
|
|
|
+ }
|
|
|
+ go sf.ping()
|
|
|
+ return sf, nil
|
|
|
+}
|
|
|
+
|
|
|
+type ListValRequest struct {
|
|
|
+ Val_ []string `json:"val" form:"val"`
|
|
|
+}
|
|
|
+
|
|
|
+// Find -- поиск ключей по префиксу
|
|
|
+func (sf *StoreClient) Find(prefixKey string) ([]string, error) {
|
|
|
+ ctx, fnCancel := context.WithTimeout(context.Background(), time.Second*6)
|
|
|
+ defer fnCancel()
|
|
|
+
|
|
|
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, sf.url+sf.urlParam+prefixKey, nil)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("StoreClient.Find(): in create HTTP-request, err=\n\t%w", err)
|
|
|
+ }
|
|
|
+ req.Header.Set("Content-Type", "application/json")
|
|
|
+ resp, err := sf.client.Do(req)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("StoreClient.Find(): in exec HTTP-request, err=\n\t%w", err)
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+ if resp.StatusCode != http.StatusOK {
|
|
|
+ return nil, fmt.Errorf("StoreClient.Find(): bad status HTTP-request, code=%v", resp.Status)
|
|
|
+ }
|
|
|
+ binBody, err := io.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("StoreClient.Find(): in read body HTTP-request, err\n\t%w", err)
|
|
|
+ }
|
|
|
+ _resp := &ListValRequest{
|
|
|
+ Val_: []string{},
|
|
|
+ }
|
|
|
+ err = json.Unmarshal(binBody, _resp)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("StoreClient.Find(): in unmarshal body HTTP-response, err\n\t%w", err)
|
|
|
+ }
|
|
|
+ return _resp.Val_, nil
|
|
|
+}
|
|
|
+
|
|
|
+// Del -- удаляет ключ с сервера
|
|
|
+func (sf *StoreClient) Del(key string) {
|
|
|
+ ctx, fnCancel := context.WithTimeout(context.Background(), time.Second*6)
|
|
|
+ defer fnCancel()
|
|
|
+
|
|
|
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, sf.url+sf.urlParam+key, nil)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("StoreClient.Del(): in create HTTP-request, err=\n\t%w", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ req.Header.Set("Content-Type", "application/json")
|
|
|
+ resp, err := sf.client.Do(req)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("StoreClient.Del(): in exec HTTP-request, err=\n\t%w", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+ if resp.StatusCode != http.StatusOK {
|
|
|
+ log.Printf("StoreClient.Del(): bad status HTTP-request, code=%v", resp.Status)
|
|
|
+ return
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Time -- проверяет время сервера
|
|
|
+func (sf *StoreClient) Time() (string, error) {
|
|
|
+ ctx, fnCancel := context.WithTimeout(context.Background(), time.Second*6)
|
|
|
+ defer fnCancel()
|
|
|
+
|
|
|
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, sf.url+"/time", nil)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println(err)
|
|
|
+ return "", fmt.Errorf("StoreClient.Time(): in create HTTP-request, err=\n\t%w", err)
|
|
|
+ }
|
|
|
+ resp, err := sf.client.Do(req)
|
|
|
+ if err != nil {
|
|
|
+ return "", fmt.Errorf("StoreClient.Time(): in exec HTTP-request, err=\n\t%w", err)
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+ if resp.StatusCode != http.StatusOK {
|
|
|
+ return "", fmt.Errorf("StoreClient.Time(): bad status HTTP-request, code=%v", resp.Status)
|
|
|
+ }
|
|
|
+ binBody, err := io.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ return "", fmt.Errorf("StoreClient.Time(): in read body HTTP-request, err\n\t%w", err)
|
|
|
+ }
|
|
|
+ return string(binBody), nil
|
|
|
+}
|
|
|
+
|
|
|
+// Пингует в фоне сервер, чтобы не оборвался коннект
|
|
|
+func (sf *StoreClient) ping() {
|
|
|
+ for {
|
|
|
+ _, _ = sf.Time()
|
|
|
+ time.Sleep(time.Second * 7)
|
|
|
+ }
|
|
|
+}
|