-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhttp.go
146 lines (128 loc) · 3.64 KB
/
http.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package main
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
"os"
"strings"
"sync"
"github.com/charmbracelet/log"
json "github.com/json-iterator/go"
)
// bufferPool is a pool of reusable buffers. Similar to sync.Pool, except it
// reliably keeps objects around forever, contrary to sync.Pool which has a
// tendency of deallocating them much too soon, which means that buffers would
// need to be reallocated and grow from their initial size much too often.
type bufferPool struct {
elems []*bytes.Buffer
mu sync.Mutex
}
func (p *bufferPool) Get() *bytes.Buffer {
p.mu.Lock()
defer p.mu.Unlock()
if len(p.elems) == 0 {
// start with a reasonably large buffer, as we can expect responses
// to be quite big
return bytes.NewBuffer(make([]byte, 0, 64*1024))
}
elem := p.elems[len(p.elems)-1]
p.elems = p.elems[:len(p.elems)-1]
return elem
}
func (p *bufferPool) Put(elem *bytes.Buffer) {
p.mu.Lock()
defer p.mu.Unlock()
p.elems = append(p.elems, elem)
}
type httpClient struct {
*http.Client
baseURL string
bufPool bufferPool
}
func (d *dumper) certPool() *x509.CertPool {
cp, err := x509.SystemCertPool()
if err != nil {
log.Warn("unable to load system cert pool", "err", err)
cp = x509.NewCertPool()
}
if d.verify != "" {
data, err := os.ReadFile(d.verify)
if err != nil {
log.Warn("unable to read CA cert", "file", d.verify, "err", err)
} else {
cp.AppendCertsFromPEM(data)
}
}
return cp
}
func (d *dumper) initHTTPClient() {
transport := http.DefaultTransport.(*http.Transport).Clone()
if d.noCompression {
transport.DisableCompression = true
}
transport.TLSClientConfig = &tls.Config{}
if d.verify == "no" {
log.Info("skipping TLS verification")
transport.TLSClientConfig.InsecureSkipVerify = true
} else {
transport.TLSClientConfig.RootCAs = d.certPool()
}
d.cl = httpClient{
Client: &http.Client{
Timeout: d.httpTimeout,
Transport: transport,
},
baseURL: d.baseURL,
}
}
// Do sends the request. If dst is non-nil, and the response is 200 OK, the
// body of the response will be unmarshalled into it, and the returned byte
// array will NOT be returned (i.e. will be nil), in order for the buffer to be
// re-used for subsequent requests. If the response is anything other than 200,
// the byte array of the raw response body will be returned.
func (cl *httpClient) Do(ctx context.Context, method, path string, body string, dst any) (int, []byte, error) {
var bodyRdr io.Reader
if body != "" {
bodyRdr = strings.NewReader(body)
}
req, err := http.NewRequestWithContext(ctx, method, cl.baseURL+path, bodyRdr)
if err != nil {
return 0, nil, fmt.Errorf("creating request: %w", err)
}
if body != "" {
req.Header.Set("Content-Type", "application/json")
}
resp, err := cl.Client.Do(req)
if err != nil {
return 0, nil, fmt.Errorf("sending request: %w", err)
}
defer resp.Body.Close()
buf := cl.bufPool.Get()
_, err = buf.ReadFrom(resp.Body)
if err != nil {
return 0, nil, fmt.Errorf("reading response body: %w", err)
}
bs := buf.Bytes()
if resp.StatusCode != http.StatusOK {
return resp.StatusCode, bs, nil
}
if dst != nil {
err = json.Unmarshal(bs, &dst)
if err != nil {
return 0, nil, fmt.Errorf("unmarshaling response body %s: %w", string(bs), err)
}
}
buf.Reset()
cl.bufPool.Put(buf)
return resp.StatusCode, nil, nil
}
func (cl *httpClient) Get(ctx context.Context, path string, body string, dst any) (int, []byte, error) {
return cl.Do(ctx, http.MethodGet, path, body, dst)
}
func (cl *httpClient) Delete(ctx context.Context, path string, body string, dst any) (int, []byte, error) {
return cl.Do(ctx, http.MethodDelete, path, body, dst)
}