| OLD | NEW | 
| (Empty) |  | 
 |    1 // Copyright 2015 The Chromium Authors. All rights reserved. | 
 |    2 // Use of this source code is governed by a BSD-style license that can be | 
 |    3 // found in the LICENSE file. | 
 |    4  | 
 |    5 package lhttp | 
 |    6  | 
 |    7 import ( | 
 |    8         "bytes" | 
 |    9         "encoding/json" | 
 |   10         "errors" | 
 |   11         "fmt" | 
 |   12         "io" | 
 |   13         "io/ioutil" | 
 |   14         "net/http" | 
 |   15         "net/url" | 
 |   16         "os" | 
 |   17         "strings" | 
 |   18  | 
 |   19         "github.com/luci/luci-go/client/internal/retry" | 
 |   20 ) | 
 |   21  | 
 |   22 // Handler is called once or multiple times for each HTTP request that is tried. | 
 |   23 type Handler func(*http.Response) error | 
 |   24  | 
 |   25 // Retriable is a retry.Retriable that exposes the resulting HTTP status | 
 |   26 // code. | 
 |   27 type Retriable interface { | 
 |   28         retry.Retriable | 
 |   29         // Returns the HTTP status code of the last request, if set. | 
 |   30         Status() int | 
 |   31 } | 
 |   32  | 
 |   33 // NewRequest returns a retriable request. | 
 |   34 // | 
 |   35 // To enable automatic retry support, the Request.Body, if present, must | 
 |   36 // implement io.Seeker. | 
 |   37 // | 
 |   38 // handler should return retry.Error in case of retriable error, for example if | 
 |   39 // a TCP connection is teared off while receiving the content. | 
 |   40 func NewRequest(c *http.Client, req *http.Request, handler Handler) (Retriable, 
     error) { | 
 |   41         // Handle req.Body if specified. It has to implement io.Seeker. | 
 |   42         if req.URL.Scheme != "http" && req.URL.Scheme != "https" { | 
 |   43                 return nil, fmt.Errorf("unsupported protocol scheme \"%s\"", req
     .URL.Scheme) | 
 |   44         } | 
 |   45         newReq := *req | 
 |   46         out := &retriable{ | 
 |   47                 handler:   handler, | 
 |   48                 c:         c, | 
 |   49                 req:       &newReq, | 
 |   50                 closeBody: req.Body, | 
 |   51         } | 
 |   52         if req.Body != nil { | 
 |   53                 ok := false | 
 |   54                 if out.seekBody, ok = req.Body.(io.Seeker); !ok { | 
 |   55                         return nil, errors.New("req.Body must implement io.Seeke
     r") | 
 |   56                 } | 
 |   57                 // Make sure the body is not closed when calling http.Client.Do(
     ). | 
 |   58                 out.req.Body = ioutil.NopCloser(req.Body) | 
 |   59         } | 
 |   60         return out, nil | 
 |   61 } | 
 |   62  | 
 |   63 // NewRequestJSON returns a retriable request calling a JSON endpoint. | 
 |   64 func NewRequestJSON(c *http.Client, url, method string, in, out interface{}) (Re
     triable, error) { | 
 |   65         var body io.Reader | 
 |   66         if in != nil { | 
 |   67                 encoded, err := json.Marshal(in) | 
 |   68                 if err != nil { | 
 |   69                         return nil, err | 
 |   70                 } | 
 |   71                 body = newReader(encoded) | 
 |   72         } | 
 |   73         req, err := http.NewRequest(method, url, body) | 
 |   74         if err != nil { | 
 |   75                 return nil, err | 
 |   76         } | 
 |   77         if in != nil { | 
 |   78                 req.Header.Set("Content-Type", jsonContentType) | 
 |   79         } | 
 |   80         return NewRequest(c, req, func(resp *http.Response) error { | 
 |   81                 defer resp.Body.Close() | 
 |   82                 if ct := strings.ToLower(resp.Header.Get("Content-Type")); ct !=
      jsonContentType { | 
 |   83                         // Non-retriable. | 
 |   84                         return fmt.Errorf("unexpected Content-Type, expected \"%
     s\", got \"%s\"", jsonContentType, ct) | 
 |   85                 } | 
 |   86                 if out == nil { | 
 |   87                         // The client doesn't care about the response. Still ens
     ure the response | 
 |   88                         // is valid json. | 
 |   89                         out = &map[string]interface{}{} | 
 |   90                 } | 
 |   91                 if err := json.NewDecoder(resp.Body).Decode(out); err != nil { | 
 |   92                         // Retriable. | 
 |   93                         return retry.Error{fmt.Errorf("bad response %s: %s", url
     , err)} | 
 |   94                 } | 
 |   95                 return nil | 
 |   96         }) | 
 |   97 } | 
 |   98  | 
 |   99 // GetJSON is a shorthand. It returns the HTTP status code and error if any. | 
 |  100 func GetJSON(config *retry.Config, c *http.Client, url string, out interface{}) 
     (int, error) { | 
 |  101         req, err := NewRequestJSON(c, url, "GET", nil, out) | 
 |  102         if err != nil { | 
 |  103                 return 0, err | 
 |  104         } | 
 |  105         err = config.Do(req) | 
 |  106         return req.Status(), err | 
 |  107 } | 
 |  108  | 
 |  109 // PostJSON is a shorthand. It returns the HTTP status code and error if any. | 
 |  110 func PostJSON(config *retry.Config, c *http.Client, url string, in, out interfac
     e{}) (int, error) { | 
 |  111         req, err := NewRequestJSON(c, url, "POST", in, out) | 
 |  112         if err != nil { | 
 |  113                 return 0, err | 
 |  114         } | 
 |  115         err = config.Do(req) | 
 |  116         return req.Status(), err | 
 |  117 } | 
 |  118  | 
 |  119 // Private details. | 
 |  120  | 
 |  121 const jsonContentType = "application/json; charset=utf-8" | 
 |  122  | 
 |  123 // newReader returns a io.ReadCloser compatible read-only buffer that also | 
 |  124 // exposes io.Seeker. This should be used instead of bytes.NewReader(), which | 
 |  125 // doesn't implement Close(). | 
 |  126 func newReader(p []byte) io.ReadCloser { | 
 |  127         return &reader{bytes.NewReader(p)} | 
 |  128 } | 
 |  129  | 
 |  130 type reader struct { | 
 |  131         *bytes.Reader | 
 |  132 } | 
 |  133  | 
 |  134 func (r *reader) Close() error { | 
 |  135         return nil | 
 |  136 } | 
 |  137  | 
 |  138 type retriable struct { | 
 |  139         handler   Handler | 
 |  140         c         *http.Client | 
 |  141         req       *http.Request | 
 |  142         closeBody io.Closer | 
 |  143         seekBody  io.Seeker | 
 |  144         status    int | 
 |  145 } | 
 |  146  | 
 |  147 func (r *retriable) Close() error { | 
 |  148         if r.closeBody != nil { | 
 |  149                 return r.closeBody.Close() | 
 |  150         } | 
 |  151         return nil | 
 |  152 } | 
 |  153  | 
 |  154 // Warning: it returns an error on HTTP >=400. This is different than | 
 |  155 // http.Client.Do() but hell it makes coding simpler. | 
 |  156 func (r *retriable) Do() error { | 
 |  157         if r.seekBody != nil { | 
 |  158                 if _, err := r.seekBody.Seek(0, os.SEEK_SET); err != nil { | 
 |  159                         // Can't be retried. | 
 |  160                         return err | 
 |  161                 } | 
 |  162         } | 
 |  163         resp, err := r.c.Do(r.req) | 
 |  164         if resp != nil { | 
 |  165                 r.status = resp.StatusCode | 
 |  166         } else { | 
 |  167                 r.status = 0 | 
 |  168         } | 
 |  169         if err != nil { | 
 |  170                 // Any TCP level failure can be retried but malformed URL should
      nt. | 
 |  171                 if err2, ok := err.(*url.Error); ok { | 
 |  172                         return err2 | 
 |  173                 } | 
 |  174                 return retry.Error{err} | 
 |  175         } | 
 |  176         // If the HTTP status code means the request should be retried. | 
 |  177         if resp.StatusCode == 408 || resp.StatusCode == 429 || resp.StatusCode >
     = 500 { | 
 |  178                 return retry.Error{fmt.Errorf("http request failed: %s (HTTP %d)
     ", http.StatusText(resp.StatusCode), resp.StatusCode)} | 
 |  179         } | 
 |  180         // Any other failure code is a hard failure. | 
 |  181         if resp.StatusCode >= 400 { | 
 |  182                 return fmt.Errorf("http request failed: %s (HTTP %d)", http.Stat
     usText(resp.StatusCode), resp.StatusCode) | 
 |  183         } | 
 |  184         // The handler may still return a retry.Error to indicate that the reque
     st | 
 |  185         // should be retried even on successful status code. | 
 |  186         return r.handler(resp) | 
 |  187 } | 
 |  188  | 
 |  189 func (r *retriable) Status() int { | 
 |  190         return r.status | 
 |  191 } | 
| OLD | NEW |