Skip to content

Commit a6bbbcc

Browse files
committed
Доработки:
* корректный возврат http.Response, вместо ReadCloser * упрощен дефолтный клиент * небольшой рефакторинг
1 parent f93e9e0 commit a6bbbcc

File tree

4 files changed

+42
-68
lines changed

4 files changed

+42
-68
lines changed

.gitignore

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,4 @@
88
# Test binary, build with `go test -c`
99
*.test
1010
test*
11-
# Output of the go coverage tool, specifically when used with LiteIDE
12-
*.out
13-
config.yaml
14-
*.pdf
15-
*.json
11+
tests

apiClient.go

Lines changed: 35 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@ import (
44
"bytes"
55
"compress/gzip"
66
"context"
7-
"crypto/rand"
8-
"crypto/tls"
97
"errors"
108
"io"
11-
"net"
129
"net/http"
1310
"runtime"
1411
"strconv"
@@ -20,22 +17,6 @@ var (
2017
DefaultClient = &http.Client{
2118
Transport: &http.Transport{
2219
Proxy: http.ProxyFromEnvironment,
23-
TLSClientConfig: &tls.Config{
24-
MinVersion: tls.VersionTLS12,
25-
MaxVersion: tls.VersionTLS13,
26-
Rand: rand.Reader,
27-
Time: time.Now,
28-
},
29-
DialContext: (&net.Dialer{
30-
Timeout: time.Minute,
31-
KeepAlive: time.Minute,
32-
}).DialContext,
33-
TLSHandshakeTimeout: 30 * time.Second,
34-
MaxIdleConns: 0,
35-
IdleConnTimeout: time.Minute,
36-
ForceAttemptHTTP2: true,
37-
ReadBufferSize: 256 << 10,
38-
WriteBufferSize: 256 << 10,
3920
},
4021
Timeout: time.Minute,
4122
}
@@ -45,8 +26,6 @@ var (
4526

4627
// NewClient - обертка над http.Client для удобной работы с API v3
4728
func NewClient(domain, token string, opts ...ClientOption) (c *ClientV3) {
48-
// обмен трафиком идёт очень активный, поэтому целесообразно использовать http2 + KeepAlive
49-
// бэкэнд мегаплана корректно умеет работать с http и KeepAlive, что экономит время и ресурсы на соединение
5029
c = &ClientV3{
5130
client: DefaultClient,
5231
domain: domain,
@@ -64,7 +43,7 @@ type ClientV3 struct {
6443
defaultHeaders http.Header
6544
}
6645

67-
// Do - http.Do + установка обязательных заголовков
46+
// Do - http.Do + установка обязательных заголовков + декомпрессия ответа, если ответ сжат
6847
func (c *ClientV3) Do(req *http.Request) (*http.Response, error) {
6948
const ct = "Content-Type"
7049
for h := range c.defaultHeaders {
@@ -73,11 +52,18 @@ func (c *ClientV3) Do(req *http.Request) (*http.Response, error) {
7352
if req.Header.Get(ct) == "" {
7453
req.Header.Set(ct, "application/json")
7554
}
76-
return c.client.Do(req)
55+
res, err := c.client.Do(req)
56+
if err != nil {
57+
return nil, err
58+
}
59+
if err := unzipResponse(res); err != nil {
60+
return nil, err
61+
}
62+
return res, nil
7763
}
7864

7965
// DoRequestAPI - т.к. в v3 параметры запроса для GET (json маршализируется и будет иметь вид: "*?{params}=")
80-
func (c ClientV3) DoRequestAPI(method string, endpoint string, search QueryParams, body io.Reader) (rc io.ReadCloser, err error) {
66+
func (c ClientV3) DoRequestAPI(method string, endpoint string, search QueryParams, body io.Reader) (*http.Response, error) {
8167
var args string // параметры строки запроса
8268
if search != nil {
8369
args = search.QueryEscape()
@@ -88,15 +74,11 @@ func (c ClientV3) DoRequestAPI(method string, endpoint string, search QueryParam
8874
}
8975
request.URL.Path = endpoint
9076
request.URL.RawQuery = args
91-
response, err := c.Do(request)
92-
if err != nil {
93-
return nil, err
94-
}
95-
return unzipResponse(response)
77+
return c.Do(request)
9678
}
9779

9880
// DoRequestAPI - т.к. в v3 параметры запроса для GET (json маршализируется и будет иметь вид: "*?{params}=")
99-
func (c ClientV3) DoCtxRequestAPI(ctx context.Context, method string, endpoint string, search QueryParams, body io.Reader) (rc io.ReadCloser, err error) {
81+
func (c ClientV3) DoCtxRequestAPI(ctx context.Context, method string, endpoint string, search QueryParams, body io.Reader) (*http.Response, error) {
10082
var args string // параметры строки запроса
10183
if search != nil {
10284
args = search.QueryEscape()
@@ -107,11 +89,7 @@ func (c ClientV3) DoCtxRequestAPI(ctx context.Context, method string, endpoint s
10789
}
10890
request.URL.Path = endpoint
10991
request.URL.RawQuery = args
110-
response, err := c.Do(request)
111-
if err != nil {
112-
return nil, err
113-
}
114-
return unzipResponse(response)
92+
return c.Do(request)
11593
}
11694

11795
// ErrUnknownCompressionMethod - неизвестное значение в заголовке "Content-Encoding"
@@ -120,30 +98,35 @@ func (c ClientV3) DoCtxRequestAPI(ctx context.Context, method string, endpoint s
12098
var ErrUnknownCompressionMethod = errors.New("unknown compression method")
12199

122100
// unzipResponse - распаковка сжатого ответа
123-
func unzipResponse(response *http.Response) (rc io.ReadCloser, err error) {
101+
func unzipResponse(response *http.Response) (err error) {
124102
if response.Uncompressed {
125-
return response.Body, nil
103+
return nil
126104
}
127-
body, err := io.ReadAll(response.Body)
128-
if err != nil {
129-
return nil, err
130-
}
131-
if err := response.Body.Close(); err != nil {
132-
return nil, err
133-
}
134-
var r = bytes.NewReader(body)
135105
switch response.Header.Get("Content-Encoding") {
136106
case "":
137-
// кейс, когда запрашивалось сжатие, но сервер не поддерживает запрашиваемый вид сжатия
138-
// response.Uncompressed будет в значении false, но в тело ответа будет не сжато и заголовок "Content-Encoding" отсутствует
139-
rc = io.NopCloser(r)
107+
return nil
140108
case "gzip":
141-
rc, err = gzip.NewReader(r)
109+
gz, err := gzip.NewReader(response.Body)
110+
if err != nil {
111+
return err
112+
}
113+
b, err := io.ReadAll(gz)
114+
if err != nil {
115+
return err
116+
}
117+
if err := response.Body.Close(); err != nil {
118+
return err
119+
}
120+
if err := gz.Close(); err != nil {
121+
return err
122+
}
123+
response.Body = io.NopCloser(bytes.NewReader(b))
124+
response.Header.Del("Content-Encoding")
125+
response.Uncompressed = true
126+
return nil
142127
default:
143-
rc = io.NopCloser(r)
144-
err = ErrUnknownCompressionMethod
128+
return ErrUnknownCompressionMethod
145129
}
146-
return
147130
}
148131

149132
// SetOptions - применить опции

apiRequest.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,9 @@ func SetRawField(field string, value interface{}) QueryBuildingFunc {
8888
}
8989

9090
// UploadFile - загрузка файла, возвращает обычный http.Response, в ответе стандартная структура ответа + данные для базовой сущности
91-
func (c *ClientV3) UploadFile(filename string, fileRader io.Reader) (io.ReadCloser, error) {
92-
var buf = bytes.NewBuffer(nil) // default 1024 bytes buffer
93-
defer buf.Reset()
94-
var mw = multipart.NewWriter(buf)
91+
func (c *ClientV3) UploadFile(filename string, fileRader io.Reader) (*http.Response, error) {
92+
var buf bytes.Buffer // default 1024 bytes buffer
93+
var mw = multipart.NewWriter(&buf)
9594
fw, err := mw.CreateFormFile("files[]", filename)
9695
if err != nil {
9796
return nil, err
@@ -102,15 +101,11 @@ func (c *ClientV3) UploadFile(filename string, fileRader io.Reader) (io.ReadClos
102101
if err := mw.Close(); err != nil {
103102
return nil, err
104103
}
105-
request, err := http.NewRequest(http.MethodPost, c.domain, buf)
104+
request, err := http.NewRequest(http.MethodPost, c.domain, &buf)
106105
if err != nil {
107106
return nil, err
108107
}
109108
request.URL.Path = "/api/file"
110109
request.Header.Set("Content-Type", mw.FormDataContentType())
111-
response, err := c.Do(request)
112-
if err != nil {
113-
return nil, err
114-
}
115-
return unzipResponse(response)
110+
return c.Do(request)
116111
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module github.com/stvoidit/megaplan/v3
22

3-
go 1.16
3+
go 1.19

0 commit comments

Comments
 (0)