1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package chttp
16
17 import (
18 "bytes"
19 "compress/gzip"
20 "context"
21 "encoding/json"
22 "errors"
23 "fmt"
24 "io"
25 "net/http"
26 "net/url"
27 "runtime"
28 "strings"
29 "sync"
30
31 "github.com/go-kivik/kivik/v4"
32 "github.com/go-kivik/kivik/v4/driver"
33 internal "github.com/go-kivik/kivik/v4/int/errors"
34 )
35
36 const typeJSON = "application/json"
37
38
39 const (
40 userAgent = "Kivik"
41 )
42
43
44 type Client struct {
45
46
47 UserAgents []string
48
49 *http.Client
50
51 rawDSN string
52 dsn *url.URL
53 basePath string
54 authMU sync.Mutex
55
56
57 noGzip bool
58 }
59
60
61
62
63
64
65
66 func New(client *http.Client, dsn string, options driver.Options) (*Client, error) {
67 dsnURL, err := parseDSN(dsn)
68 if err != nil {
69 return nil, err
70 }
71 user := dsnURL.User
72 dsnURL.User = nil
73 c := &Client{
74 Client: client,
75 dsn: dsnURL,
76 basePath: strings.TrimSuffix(dsnURL.Path, "/"),
77 rawDSN: dsn,
78 }
79 var auth authenticator
80 if user != nil {
81 password, _ := user.Password()
82 auth = &cookieAuth{
83 Username: user.Username(),
84 Password: password,
85 }
86 }
87 opts := map[string]interface{}{}
88 options.Apply(opts)
89 options.Apply(c)
90 options.Apply(&auth)
91 if auth != nil {
92 if err := auth.Authenticate(c); err != nil {
93 return nil, err
94 }
95 }
96 return c, nil
97 }
98
99 func parseDSN(dsn string) (*url.URL, error) {
100 if dsn == "" {
101 return nil, &internal.Error{
102 Status: http.StatusBadRequest,
103 Err: errors.New("no URL specified"),
104 }
105 }
106 if !strings.HasPrefix(dsn, "http://") && !strings.HasPrefix(dsn, "https://") {
107 dsn = "http://" + dsn
108 }
109 dsnURL, err := url.Parse(dsn)
110 if err != nil {
111 return nil, &internal.Error{Status: http.StatusBadRequest, Err: err}
112 }
113 if dsnURL.Path == "" {
114 dsnURL.Path = "/"
115 }
116 return dsnURL, nil
117 }
118
119
120 func (c *Client) DSN() string {
121 return c.rawDSN
122 }
123
124
125 type Response struct {
126 *http.Response
127
128
129 ContentType string
130 }
131
132
133
134 func DecodeJSON(r *http.Response, i interface{}) error {
135 defer CloseBody(r.Body)
136 if err := json.NewDecoder(r.Body).Decode(i); err != nil {
137 return &internal.Error{Status: http.StatusBadGateway, Err: err}
138 }
139 return nil
140 }
141
142
143
144 func (c *Client) DoJSON(ctx context.Context, method, path string, opts *Options, i interface{}) error {
145 res, err := c.DoReq(ctx, method, path, opts)
146 if err != nil {
147 return err
148 }
149 if res.Body != nil {
150 defer CloseBody(res.Body)
151 }
152 if err = ResponseError(res); err != nil {
153 return err
154 }
155 err = DecodeJSON(res, i)
156 return err
157 }
158
159 func (c *Client) path(path string) string {
160 if c.basePath != "" {
161 return c.basePath + "/" + strings.TrimPrefix(path, "/")
162 }
163 return path
164 }
165
166
167 func (c *Client) fullPathMatches(path, target string) bool {
168 p, err := url.Parse(path)
169 if err != nil {
170
171 return false
172 }
173 p.RawQuery = ""
174 t := new(url.URL)
175 *t = *c.dsn
176 t.Path = c.path(target)
177 t.RawQuery = ""
178 return t.String() == p.String()
179 }
180
181
182
183 func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Reader, opts *Options) (*http.Request, error) {
184 fullPath := c.path(path)
185 reqPath, err := url.Parse(fullPath)
186 if err != nil {
187 return nil, &internal.Error{Status: http.StatusBadRequest, Err: err}
188 }
189 u := *c.dsn
190 u.Path = reqPath.Path
191 u.RawQuery = reqPath.RawQuery
192 compress, body := c.compressBody(u.String(), body, opts)
193 req, err := http.NewRequest(method, u.String(), body)
194 if err != nil {
195 return nil, &internal.Error{Status: http.StatusBadRequest, Err: err}
196 }
197 if compress {
198 req.Header.Add("Content-Encoding", "gzip")
199 }
200 req.Header.Add("User-Agent", c.userAgent())
201 return req.WithContext(ctx), nil
202 }
203
204 func (c *Client) shouldCompressBody(path string, body io.Reader, opts *Options) bool {
205 if c.noGzip || (opts != nil && opts.NoGzip) {
206 return false
207 }
208
209 if c.fullPathMatches(path, "/_session") {
210 return false
211 }
212 if body == nil {
213 return false
214 }
215 return true
216 }
217
218
219
220 func (c *Client) compressBody(path string, body io.Reader, opts *Options) (bool, io.Reader) {
221 if !c.shouldCompressBody(path, body, opts) {
222 return false, body
223 }
224 r, w := io.Pipe()
225 go func() {
226 if closer, ok := body.(io.Closer); ok {
227 defer closer.Close()
228 }
229 gz := gzip.NewWriter(w)
230 _, err := io.Copy(gz, body)
231 _ = gz.Close()
232 w.CloseWithError(err)
233 }()
234 return true, r
235 }
236
237
238
239
240 func (c *Client) DoReq(ctx context.Context, method, path string, opts *Options) (*http.Response, error) {
241 if method == "" {
242 return nil, errors.New("chttp: method required")
243 }
244 var body io.Reader
245 if opts != nil {
246 if opts.GetBody != nil {
247 var err error
248 opts.Body, err = opts.GetBody()
249 if err != nil {
250 return nil, err
251 }
252 }
253 if opts.Body != nil {
254 body = opts.Body
255 defer opts.Body.Close()
256 }
257 }
258 req, err := c.NewRequest(ctx, method, path, body, opts)
259 if err != nil {
260 return nil, err
261 }
262 fixPath(req, path)
263 setHeaders(req, opts)
264 setQuery(req, opts)
265 if opts != nil {
266 req.GetBody = opts.GetBody
267 }
268
269 trace := ContextClientTrace(ctx)
270 if trace != nil {
271 trace.httpRequest(req)
272 trace.httpRequestBody(req)
273 }
274
275 response, err := c.Do(req)
276 if trace != nil {
277 trace.httpResponse(response)
278 trace.httpResponseBody(response)
279 }
280 return response, netError(err)
281 }
282
283 func netError(err error) error {
284 if err == nil {
285 return nil
286 }
287 if urlErr, ok := err.(*url.Error); ok {
288
289
290 status := kivik.HTTPStatus(urlErr.Err)
291 if status == http.StatusInternalServerError {
292 status = http.StatusBadGateway
293 }
294 return &internal.Error{Status: status, Err: err}
295 }
296 if status := kivik.HTTPStatus(err); status != http.StatusInternalServerError {
297 return err
298 }
299 return &internal.Error{Status: http.StatusBadGateway, Err: err}
300 }
301
302
303
304 func fixPath(req *http.Request, path string) {
305
306 parts := strings.SplitN(path, "?", 2)
307 req.URL.RawPath = "/" + strings.TrimPrefix(parts[0], "/")
308 }
309
310
311
312 func BodyEncoder(i interface{}) func() (io.ReadCloser, error) {
313 return func() (io.ReadCloser, error) {
314 return EncodeBody(i), nil
315 }
316 }
317
318
319
320 func EncodeBody(i interface{}) io.ReadCloser {
321 done := make(chan struct{})
322 r, w := io.Pipe()
323 go func() {
324 defer close(done)
325 var err error
326 switch t := i.(type) {
327 case []byte:
328 _, err = w.Write(t)
329 case json.RawMessage:
330 _, err = w.Write(t)
331 case string:
332 _, err = w.Write([]byte(t))
333 default:
334 err = json.NewEncoder(w).Encode(i)
335 switch err.(type) {
336 case *json.MarshalerError, *json.UnsupportedTypeError, *json.UnsupportedValueError:
337 err = &internal.Error{Status: http.StatusBadRequest, Err: err}
338 }
339 }
340 _ = w.CloseWithError(err)
341 }()
342 return &ebReader{
343 ReadCloser: r,
344 done: done,
345 }
346 }
347
348 type ebReader struct {
349 io.ReadCloser
350 done <-chan struct{}
351 }
352
353 var _ io.ReadCloser = &ebReader{}
354
355 func (r *ebReader) Close() error {
356 err := r.ReadCloser.Close()
357 <-r.done
358 return err
359 }
360
361 func setHeaders(req *http.Request, opts *Options) {
362 accept := typeJSON
363 contentType := typeJSON
364 if opts != nil {
365 if opts.Accept != "" {
366 accept = opts.Accept
367 }
368 if opts.ContentType != "" {
369 contentType = opts.ContentType
370 }
371 if opts.FullCommit {
372 req.Header.Add("X-Couch-Full-Commit", "true")
373 }
374 if opts.IfNoneMatch != "" {
375 inm := "\"" + strings.Trim(opts.IfNoneMatch, "\"") + "\""
376 req.Header.Set("If-None-Match", inm)
377 }
378 if opts.ContentLength != 0 {
379 req.ContentLength = opts.ContentLength
380 }
381 for k, v := range opts.Header {
382 if _, ok := req.Header[k]; !ok {
383 req.Header[k] = v
384 }
385 }
386 }
387 req.Header.Add("Accept", accept)
388 req.Header.Add("Content-Type", contentType)
389 }
390
391 func setQuery(req *http.Request, opts *Options) {
392 if opts == nil || len(opts.Query) == 0 {
393 return
394 }
395 if req.URL.RawQuery == "" {
396 req.URL.RawQuery = opts.Query.Encode()
397 return
398 }
399 req.URL.RawQuery = strings.Join([]string{req.URL.RawQuery, opts.Query.Encode()}, "&")
400 }
401
402
403
404
405 func (c *Client) DoError(ctx context.Context, method, path string, opts *Options) (*http.Response, error) {
406 res, err := c.DoReq(ctx, method, path, opts)
407 if err != nil {
408 return res, err
409 }
410 if res.Body != nil {
411 defer CloseBody(res.Body)
412 }
413 err = ResponseError(res)
414 return res, err
415 }
416
417
418
419 func ETag(resp *http.Response) (string, bool) {
420 if resp == nil {
421 return "", false
422 }
423 etag, ok := resp.Header["Etag"]
424 if !ok {
425 etag, ok = resp.Header["ETag"]
426 }
427 if !ok {
428 return "", false
429 }
430 return strings.Trim(etag[0], `"`), ok
431 }
432
433
434
435
436 func GetRev(resp *http.Response) (string, error) {
437 rev, ok := ETag(resp)
438 if ok {
439 return rev, nil
440 }
441 if resp == nil || resp.Request == nil || resp.Request.Method == http.MethodHead {
442 return "", errors.New("unable to determine document revision")
443 }
444 reassembled, rev, err := ExtractRev(resp.Body)
445 resp.Body = reassembled
446 return rev, err
447 }
448
449
450
451
452
453
454
455
456
457
458
459 func ExtractRev(rc io.ReadCloser) (io.ReadCloser, string, error) {
460 buf := &bytes.Buffer{}
461 tr := io.TeeReader(rc, buf)
462 rev, err := readRev(tr)
463 reassembled := struct {
464 io.Reader
465 io.Closer
466 }{
467 Reader: io.MultiReader(buf, rc),
468 Closer: rc,
469 }
470 if err != nil {
471 return reassembled, "", fmt.Errorf("unable to determine document revision: %w", err)
472 }
473 return reassembled, rev, nil
474 }
475
476
477
478 func readRev(r io.Reader) (string, error) {
479 dec := json.NewDecoder(r)
480 tk, err := dec.Token()
481 if err != nil {
482 return "", err
483 }
484 if tk != json.Delim('{') {
485 return "", fmt.Errorf("Expected %q token, found %q", '{', tk)
486 }
487 var val json.RawMessage
488 for dec.More() {
489 tk, err = dec.Token()
490 if err != nil {
491 return "", err
492 }
493 if tk == "_rev" {
494 tk, err = dec.Token()
495 if err != nil {
496 return "", err
497 }
498 if value, ok := tk.(string); ok {
499 return value, nil
500 }
501 return "", fmt.Errorf("found %q in place of _rev value", tk)
502 }
503
504 if err := dec.Decode(&val); err != nil {
505 return "", err
506 }
507 }
508
509 return "", errors.New("_rev key not found in response body")
510 }
511
512 func (c *Client) userAgent() string {
513 ua := fmt.Sprintf("%s/%s (Language=%s; Platform=%s/%s)",
514 userAgent, kivik.Version, runtime.Version(), runtime.GOARCH, runtime.GOOS)
515 return strings.Join(append([]string{ua}, c.UserAgents...), " ")
516 }
517
View as plain text