/
request.go
242 lines (209 loc) · 6.32 KB
/
request.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
package request
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"net/url"
"time"
"github.com/alibaba/pouch/client"
"github.com/alibaba/pouch/test/environment"
)
var defaultTimeout = time.Second * 10
// Option defines a type used to update http.Request.
type Option func(*http.Request) error
// WithContext sets the ctx of http.Request.
func WithContext(ctx context.Context) Option {
return func(r *http.Request) error {
r2 := r.WithContext(ctx)
*r = *r2
return nil
}
}
// WithHeader sets the Header of http.Request.
func WithHeader(key string, value string) Option {
return func(r *http.Request) error {
r.Header.Add(key, value)
return nil
}
}
// WithQuery sets the query field in URL.
func WithQuery(query url.Values) Option {
return func(r *http.Request) error {
r.URL.RawQuery = query.Encode()
return nil
}
}
// WithRawData sets the input data with raw data
func WithRawData(data io.ReadCloser) Option {
return func(r *http.Request) error {
r.Body = data
return nil
}
}
// WithJSONBody encodes the input data to JSON and sets it to the body in http.Request
func WithJSONBody(obj interface{}) Option {
return func(r *http.Request) error {
b := bytes.NewBuffer([]byte{})
if obj != nil {
err := json.NewEncoder(b).Encode(obj)
if err != nil {
return err
}
}
r.Body = ioutil.NopCloser(b)
r.Header.Set("Content-Type", "application/json")
return nil
}
}
// DecodeBody decodes body to obj.
func DecodeBody(obj interface{}, body io.ReadCloser) error {
defer body.Close()
return json.NewDecoder(body).Decode(obj)
}
// Delete sends request to the default pouchd server with custom request options.
func Delete(endpoint string, opts ...Option) (*http.Response, error) {
apiClient, err := newAPIClient(environment.PouchdAddress, environment.TLSConfig)
if err != nil {
return nil, err
}
fullPath := apiClient.BaseURL() + apiClient.GetAPIPath(endpoint, url.Values{})
req, err := newRequest(http.MethodDelete, fullPath, opts...)
if err != nil {
return nil, err
}
return apiClient.HTTPCli.Do(req)
}
// Debug sends request to the default pouchd server to get the debug info.
//
// NOTE: without any version information.
func Debug(endpoint string) (*http.Response, error) {
apiClient, err := newAPIClient(environment.PouchdAddress, environment.TLSConfig)
if err != nil {
return nil, err
}
fullPath := apiClient.BaseURL() + endpoint
req, err := newRequest(http.MethodGet, fullPath)
if err != nil {
return nil, err
}
return apiClient.HTTPCli.Do(req)
}
// Head sends head request to pouchd.
func Head(endpoint string, opts ...Option) (*http.Response, error) {
apiClient, err := newAPIClient(environment.PouchdAddress, environment.TLSConfig)
if err != nil {
return nil, err
}
fullPath := apiClient.BaseURL() + apiClient.GetAPIPath(endpoint, url.Values{})
req, err := newRequest(http.MethodHead, fullPath, opts...)
if err != nil {
return nil, err
}
return apiClient.HTTPCli.Do(req)
}
// Get sends request to the default pouchd server with custom request options.
func Get(endpoint string, opts ...Option) (*http.Response, error) {
apiClient, err := newAPIClient(environment.PouchdAddress, environment.TLSConfig)
if err != nil {
return nil, err
}
fullPath := apiClient.BaseURL() + apiClient.GetAPIPath(endpoint, url.Values{})
req, err := newRequest(http.MethodGet, fullPath, opts...)
if err != nil {
return nil, err
}
return apiClient.HTTPCli.Do(req)
}
// Post sends post request to pouchd.
func Post(endpoint string, opts ...Option) (*http.Response, error) {
apiClient, err := newAPIClient(environment.PouchdAddress, environment.TLSConfig)
if err != nil {
return nil, err
}
fullPath := apiClient.BaseURL() + apiClient.GetAPIPath(endpoint, url.Values{})
req, err := newRequest(http.MethodPost, fullPath, opts...)
if err != nil {
return nil, err
}
// By default, if Content-Type in header is not set, set it to application/json
if req.Header.Get("Content-Type") == "" {
WithHeader("Content-Type", "application/json")(req)
}
return apiClient.HTTPCli.Do(req)
}
// newAPIClient return new HTTP client with tls.
//
// FIXME: Could we make some functions exported in alibaba/pouch/client?
func newAPIClient(host string, tls client.TLSConfig) (*client.APIClient, error) {
commonAPIClient, err := client.NewAPIClient(host, tls)
if err != nil {
return nil, err
}
apiClient := commonAPIClient.(*client.APIClient)
return apiClient, nil
}
// newRequest creates request targeting on specific host/path by method.
func newRequest(method, url string, opts ...Option) (*http.Request, error) {
req, err := http.NewRequest(method, url, nil)
if err != nil {
return nil, err
}
for _, opt := range opts {
err := opt(req)
if err != nil {
return nil, err
}
}
return req, nil
}
// Hijack posts hijack request.
func Hijack(endpoint string, opts ...Option) (*http.Response, net.Conn, *bufio.Reader, error) {
apiClient, err := newAPIClient(environment.PouchdAddress, environment.TLSConfig)
if err != nil {
return nil, nil, nil, err
}
fullPath := apiClient.BaseURL() + apiClient.GetAPIPath(endpoint, url.Values{})
req, err := newRequest(http.MethodPost, fullPath, opts...)
if err != nil {
return nil, nil, nil, err
}
req.Host = environment.PouchdUnixDomainSock
conn, err := net.DialTimeout("unix", req.Host, defaultTimeout)
if err != nil {
return nil, nil, nil, err
}
clientconn := httputil.NewClientConn(conn, nil)
defer clientconn.Close()
// For hijack request, pouchd server will return 200 status or
// 101 status for switching protocol. For the 200 status code, http
// proto has definied:
//
// If there is no Content-Length or chunked Transfer-Encoding on
// a *Response and the status is not 1xx, 204 or 304, then the
// body is unbounded.
//
// For this case, the response is always terminated by the first
// empty line after the header fields.
// More details in RFC 2616, section 4.4.
//
// "persistent connection closed" is expectd error for 200 status.
resp, err := clientconn.Do(req)
if err != httputil.ErrPersistEOF {
if err != nil {
return nil, nil, nil, err
}
if resp.StatusCode != http.StatusSwitchingProtocols {
resp.Body.Close()
return nil, nil, nil, fmt.Errorf("unable to upgrade proto, got http status: %v", resp.StatusCode)
}
}
rwc, br := clientconn.Hijack()
return resp, rwc, br, nil
}