package vcontrolsdk import ( "bytes" "context" "crypto/tls" "fmt" "io" "net/http" "net/url" "strings" "sync" "time" "github.com/google/go-querystring/query" "repository.basistech.ru/BASIS/dynamix-standard-go-sdk/config" "repository.basistech.ru/BASIS/dynamix-standard-go-sdk/internal/constants" api "repository.basistech.ru/BASIS/dynamix-standard-go-sdk/pkg" ) // VControlClient is HTTP-client for platform type VControlClient struct { URL string client *http.Client cfg config.Config expiryTime time.Time mutex *sync.Mutex } // client builder func New(cfg config.Config) *VControlClient { if cfg.Retries == 0 { cfg.Retries = 5 } var expiryTime time.Time if cfg.Token != "" { expiryTime = time.Now().AddDate(0, 0, 1) } return &VControlClient{ URL: cfg.VControlURL, client: &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: cfg.SSLSkipVerify, }, }, }, cfg: trimConfig(&cfg), expiryTime: expiryTime, mutex: &sync.Mutex{}, } } // API builder func (ldc *VControlClient) API() *api.API { return api.New(ldc) } // ApiCall method for sending requests to the platform func (ldc *VControlClient) ApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) { // get token if err := ldc.getToken(ctx); err != nil { return nil, err } var body *bytes.Buffer byteSlice, ok := params.([]byte) if ok { body = bytes.NewBuffer(byteSlice) } else { values, err := query.Values(params) if err != nil { return nil, err } body = bytes.NewBufferString(values.Encode()) } req, err := http.NewRequestWithContext(ctx, method, ldc.URL+constants.API+url, body) if err != nil { return nil, err } // perform request respBytes, err := ldc.do(req) if err != nil { return nil, err } return respBytes, err } func (ldc *VControlClient) getToken(ctx context.Context) error { ldc.mutex.Lock() defer ldc.mutex.Unlock() // new token is not needed if ldc.cfg.Token != "" || !time.Now().After(ldc.expiryTime) { return nil } // set up request headers and body body := fmt.Sprintf("login=%s&password=%s", url.QueryEscape(ldc.cfg.Username), url.QueryEscape(ldc.cfg.Password)) bodyReader := strings.NewReader(body) req, _ := http.NewRequestWithContext(ctx, "POST", ldc.cfg.VControlURL+constants.API+constants.APIv0+"/auth", bodyReader) // request token resp, err := ldc.client.Do(req) if err != nil { return fmt.Errorf("cannot get token: %w", err) } defer resp.Body.Close() var respBodyBytes []byte respBodyBytes, err = io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("cannot get token: %w", err) } if resp.StatusCode != 200 { return fmt.Errorf("cannot get token: %s", respBodyBytes) } var token string cookies := resp.Cookies() for _, c := range cookies { if c.Name == "api_token" { token = c.Value } } // save token in config ldc.cfg.Token = token ldc.expiryTime = time.Now().AddDate(0, 0, 1) return nil } // do method performs request and returns response as an array of bytes and nil error in case of response status code 200. // In any other cases do returns nil response and error. // Retries are implemented in case of connection reset errors. func (ldc *VControlClient) do(req *http.Request) ([]byte, error) { req.Header.Set("Cookie", fmt.Sprintf("api_token=%s", ldc.cfg.Token)) req.Header.Set("Accept", "application/json") buf, err := io.ReadAll(req.Body) if err != nil { return nil, err } req.Body.Close() req.Body = io.NopCloser(bytes.NewBuffer(buf)) resp, err := ldc.client.Do(req) if resp != nil { defer resp.Body.Close() } // handle http request errors if err != nil { return nil, err } if resp == nil { return nil, fmt.Errorf("got empty response without error") } // handle successful request respBytes, _ := io.ReadAll(resp.Body) if resp.StatusCode == 200 { return respBytes, nil } // handle errors with status code other than 200 err = fmt.Errorf("%s", respBytes) return nil, fmt.Errorf("could not execute request: %w", err) } func trimConfig(cfg *config.Config) config.Config { cfg.VControlURL = strings.TrimSuffix(cfg.VControlURL, "/") return *cfg }