Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1253)

Side by Side Diff: common/lhttp/client.go

Issue 2562293002: common/lhttp: close the Response.Body on non-200s (Closed)
Patch Set: fix typos Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | common/lhttp/client_test.go » ('j') | common/lhttp/client_test.go » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2015 The LUCI Authors. All rights reserved. 1 // Copyright 2015 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0 2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file. 3 // that can be found in the LICENSE file.
4 4
5 package lhttp 5 package lhttp
6 6
7 import ( 7 import (
8 "bytes" 8 "bytes"
9 "encoding/json" 9 "encoding/json"
10 "fmt" 10 "fmt"
11 "io" 11 "io"
12 "io/ioutil"
12 "net/http" 13 "net/http"
13 "strings" 14 "strings"
14 15
15 "golang.org/x/net/context" 16 "golang.org/x/net/context"
16 17
17 "github.com/luci/luci-go/common/errors" 18 "github.com/luci/luci-go/common/errors"
18 "github.com/luci/luci-go/common/logging" 19 "github.com/luci/luci-go/common/logging"
19 "github.com/luci/luci-go/common/retry" 20 "github.com/luci/luci-go/common/retry"
20 ) 21 )
21 22
22 const ( 23 const (
23 jsonContentType = "application/json" 24 jsonContentType = "application/json"
24 jsonContentTypeForPOST = "application/json; charset=utf-8" 25 jsonContentTypeForPOST = "application/json; charset=utf-8"
25 ) 26 )
26 27
27 // Handler is called once or multiple times for each HTTP request that is tried. 28 // Handler is called once or multiple times for each HTTP request that is tried.
28 type Handler func(*http.Response) error 29 type Handler func(*http.Response) error
29 30
30 // RequestGen is a generator function to create a new request. It may be called 31 // RequestGen is a generator function to create a new request. It may be called
31 // multiple times if an operation needs to be retried. The HTTP server is 32 // multiple times if an operation needs to be retried. The HTTP server is
32 // responsible for closing the Request body, as per http.Request Body method 33 // responsible for closing the Request body, as per http.Request Body method
33 // documentation. 34 // documentation.
34 type RequestGen func() (*http.Request, error) 35 type RequestGen func() (*http.Request, error)
35 36
36 // NewRequest returns a retriable request. 37 // NewRequest returns a retriable request.
37 // 38 //
38 // To enable automatic retry support, the Request.Body, if present, must 39 // The handler func is responsible for closing the response Body before
39 // implement io.Seeker. 40 // returning. It should return retry.Error in case of retriable error, for
40 // 41 // example if a TCP connection is terminated while receiving the content.
41 // handler should return retry.Error in case of retriable error, for example if
42 // a TCP connection is teared off while receiving the content.
43 func NewRequest(ctx context.Context, c *http.Client, rFn retry.Factory, rgen Req uestGen, handler Handler) func() (int, error) { 42 func NewRequest(ctx context.Context, c *http.Client, rFn retry.Factory, rgen Req uestGen, handler Handler) func() (int, error) {
44 if rFn == nil { 43 if rFn == nil {
45 rFn = retry.Default 44 rFn = retry.Default
46 } 45 }
47 46
48 return func() (int, error) { 47 return func() (int, error) {
49 » » status := 0 48 » » status, attempts := 0, 0
50 err := retry.Retry(ctx, rFn, func() error { 49 err := retry.Retry(ctx, rFn, func() error {
50 attempts++
51 req, err := rgen() 51 req, err := rgen()
52 if err != nil { 52 if err != nil {
53 return err 53 return err
54 } 54 }
55 55
56 resp, err := c.Do(req) 56 resp, err := c.Do(req)
57 if err != nil { 57 if err != nil {
58 // Retry every error. This is sad when you speci fy an invalid hostname but 58 // Retry every error. This is sad when you speci fy an invalid hostname but
59 // it's better than failing when DNS resolution is flaky. 59 // it's better than failing when DNS resolution is flaky.
60 return errors.WrapTransient(err) 60 return errors.WrapTransient(err)
61 } 61 }
62 status = resp.StatusCode 62 status = resp.StatusCode
63 63
64 » » » // If the HTTP status code means the request should be r etried. 64 » » » switch {
65 » » » if status == 408 || status == 429 || status >= 500 { 65 » » » case status == 408, status == 429, status >= 500:
66 » » » » return errors.WrapTransient( 66 » » » » // The HTTP status code means the request should be retried.
67 » » » » err = errors.WrapTransient(
67 fmt.Errorf("http request failed: %s (HTT P %d)", http.StatusText(status), status)) 68 fmt.Errorf("http request failed: %s (HTT P %d)", http.StatusText(status), status))
69 case status == 404 && strings.HasPrefix(req.URL.Path, "/ _ah/api/"):
70 // Endpoints occasionally return 404 on valid re quests!
71 logging.Infof(ctx, "lhttp.Do() got a Cloud Endpo ints 404: %#v", resp.Header)
72 err = errors.WrapTransient(
73 fmt.Errorf("http request failed (endpoin ts): %s (HTTP %d)", http.StatusText(status), status))
74 case status >= 400:
75 // Any other failure code is a hard failure.
76 err = fmt.Errorf("http request failed: %s (HTTP %d)", http.StatusText(status), status)
77 default:
78 // The handler may still return a retry.Error to indicate that the request
79 // should be retried even on successful status c ode.
80 return handler(resp)
68 } 81 }
69 » » » // Endpoints occasionally return 404 on valid requests ( !) 82
70 » » » if status == 404 && strings.HasPrefix(req.URL.Path, "/_a h/api/") { 83 » » » // Drain and close the resp.Body.
71 » » » » logging.Infof(ctx, "lhttp.Do() got a Cloud Endpo ints 404: %#v", resp.Header) 84 » » » io.Copy(ioutil.Discard, resp.Body)
72 » » » » return errors.WrapTransient( 85 » » » resp.Body.Close()
73 » » » » » fmt.Errorf("http request failed (endpoin ts): %s (HTTP %d)", http.StatusText(status), status)) 86 » » » return err
74 » » » }
75 » » » // Any other failure code is a hard failure.
76 » » » if status >= 400 {
77 » » » » return fmt.Errorf("http request failed: %s (HTTP %d)", http.StatusText(status), status)
78 » » » }
79 » » » // The handler may still return a retry.Error to indicat e that the request
80 » » » // should be retried even on successful status code.
81 » » » return handler(resp)
82 }, nil) 87 }, nil)
88 if err != nil {
89 err = fmt.Errorf("%v (attempts: %d)", err, attempts)
90 }
83 return status, err 91 return status, err
84 } 92 }
85 } 93 }
86 94
87 // NewRequestJSON returns a retriable request calling a JSON endpoint. 95 // NewRequestJSON returns a retriable request calling a JSON endpoint.
88 func NewRequestJSON(ctx context.Context, c *http.Client, rFn retry.Factory, url, method string, headers map[string]string, in, out interface{}) (func() (int, er ror), error) { 96 func NewRequestJSON(ctx context.Context, c *http.Client, rFn retry.Factory, url, method string, headers map[string]string, in, out interface{}) (func() (int, er ror), error) {
89 var encoded []byte 97 var encoded []byte
90 if in != nil { 98 if in != nil {
91 var err error 99 var err error
92 if encoded, err = json.Marshal(in); err != nil { 100 if encoded, err = json.Marshal(in); err != nil {
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
142 } 150 }
143 151
144 // PostJSON is a shorthand. It returns the HTTP status code and error if any. 152 // PostJSON is a shorthand. It returns the HTTP status code and error if any.
145 func PostJSON(ctx context.Context, rFn retry.Factory, c *http.Client, url string , headers map[string]string, in, out interface{}) (int, error) { 153 func PostJSON(ctx context.Context, rFn retry.Factory, c *http.Client, url string , headers map[string]string, in, out interface{}) (int, error) {
146 req, err := NewRequestJSON(ctx, c, rFn, url, "POST", headers, in, out) 154 req, err := NewRequestJSON(ctx, c, rFn, url, "POST", headers, in, out)
147 if err != nil { 155 if err != nil {
148 return 0, err 156 return 0, err
149 } 157 }
150 return req() 158 return req()
151 } 159 }
OLDNEW
« no previous file with comments | « no previous file | common/lhttp/client_test.go » ('j') | common/lhttp/client_test.go » ('J')

Powered by Google App Engine
This is Rietveld 408576698