Просмотр исходного кода

SVI Разработка клиента

SVI 2 лет назад
Родитель
Сommit
578a27a87e
3 измененных файлов с 167 добавлено и 1 удалено
  1. 3 0
      README.md
  2. 163 0
      pkg/store_client/store_client.go
  3. 1 1
      pkg/types/istore_disk.go

+ 3 - 0
README.md

@@ -20,6 +20,9 @@
 * `/find/:login/:pass/:key` (POST, искать ключи по префиксу).
   * [x] на диске
 * [x] `/time` (GET, возвращает текущее время на сервере; для поддержания `KeepAlive`).
+* [ ] HTTP-клиент (Автоматически выполняет все запросы к хранилищу)
+
+Покрытие кода тестами: **0.0%**
 
 Запросы сделанными классическими специально, чтобы гарантировать правильную работу через прокси.
 

+ 163 - 0
pkg/store_client/store_client.go

@@ -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)
+	}
+}

+ 1 - 1
pkg/types/istore_disk.go

@@ -5,4 +5,4 @@ type IStoreDisk interface {
 	IStoreBase
 	// Find -- найти ключи по префиксу
 	Find(prefixKey string) ([]string, error)
-}
+}