Index: client/internal/lhttp/client.go |
diff --git a/client/internal/lhttp/client.go b/client/internal/lhttp/client.go |
deleted file mode 100644 |
index c277d18e9705e003f00ebd757db5c71ef79f906a..0000000000000000000000000000000000000000 |
--- a/client/internal/lhttp/client.go |
+++ /dev/null |
@@ -1,191 +0,0 @@ |
-// Copyright 2015 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-package lhttp |
- |
-import ( |
- "bytes" |
- "encoding/json" |
- "errors" |
- "fmt" |
- "io" |
- "io/ioutil" |
- "net/http" |
- "net/url" |
- "os" |
- "strings" |
- |
- "github.com/luci/luci-go/client/internal/retry" |
-) |
- |
-// Handler is called once or multiple times for each HTTP request that is tried. |
-type Handler func(*http.Response) error |
- |
-// Retriable is a retry.Retriable that exposes the resulting HTTP status |
-// code. |
-type Retriable interface { |
- retry.Retriable |
- // Returns the HTTP status code of the last request, if set. |
- Status() int |
-} |
- |
-// NewRequest returns a retriable request. |
-// |
-// To enable automatic retry support, the Request.Body, if present, must |
-// implement io.Seeker. |
-// |
-// handler should return retry.Error in case of retriable error, for example if |
-// a TCP connection is teared off while receiving the content. |
-func NewRequest(c *http.Client, req *http.Request, handler Handler) (Retriable, error) { |
- // Handle req.Body if specified. It has to implement io.Seeker. |
- if req.URL.Scheme != "http" && req.URL.Scheme != "https" { |
- return nil, fmt.Errorf("unsupported protocol scheme \"%s\"", req.URL.Scheme) |
- } |
- newReq := *req |
- out := &retriable{ |
- handler: handler, |
- c: c, |
- req: &newReq, |
- closeBody: req.Body, |
- } |
- if req.Body != nil { |
- ok := false |
- if out.seekBody, ok = req.Body.(io.Seeker); !ok { |
- return nil, errors.New("req.Body must implement io.Seeker") |
- } |
- // Make sure the body is not closed when calling http.Client.Do(). |
- out.req.Body = ioutil.NopCloser(req.Body) |
- } |
- return out, nil |
-} |
- |
-// NewRequestJSON returns a retriable request calling a JSON endpoint. |
-func NewRequestJSON(c *http.Client, url, method string, in, out interface{}) (Retriable, error) { |
- var body io.Reader |
- if in != nil { |
- encoded, err := json.Marshal(in) |
- if err != nil { |
- return nil, err |
- } |
- body = newReader(encoded) |
- } |
- req, err := http.NewRequest(method, url, body) |
- if err != nil { |
- return nil, err |
- } |
- if in != nil { |
- req.Header.Set("Content-Type", jsonContentType) |
- } |
- return NewRequest(c, req, func(resp *http.Response) error { |
- defer resp.Body.Close() |
- if ct := strings.ToLower(resp.Header.Get("Content-Type")); ct != jsonContentType { |
- // Non-retriable. |
- return fmt.Errorf("unexpected Content-Type, expected \"%s\", got \"%s\"", jsonContentType, ct) |
- } |
- if out == nil { |
- // The client doesn't care about the response. Still ensure the response |
- // is valid json. |
- out = &map[string]interface{}{} |
- } |
- if err := json.NewDecoder(resp.Body).Decode(out); err != nil { |
- // Retriable. |
- return retry.Error{fmt.Errorf("bad response %s: %s", url, err)} |
- } |
- return nil |
- }) |
-} |
- |
-// GetJSON is a shorthand. It returns the HTTP status code and error if any. |
-func GetJSON(config *retry.Config, c *http.Client, url string, out interface{}) (int, error) { |
- req, err := NewRequestJSON(c, url, "GET", nil, out) |
- if err != nil { |
- return 0, err |
- } |
- err = config.Do(req) |
- return req.Status(), err |
-} |
- |
-// PostJSON is a shorthand. It returns the HTTP status code and error if any. |
-func PostJSON(config *retry.Config, c *http.Client, url string, in, out interface{}) (int, error) { |
- req, err := NewRequestJSON(c, url, "POST", in, out) |
- if err != nil { |
- return 0, err |
- } |
- err = config.Do(req) |
- return req.Status(), err |
-} |
- |
-// Private details. |
- |
-const jsonContentType = "application/json; charset=utf-8" |
- |
-// newReader returns a io.ReadCloser compatible read-only buffer that also |
-// exposes io.Seeker. This should be used instead of bytes.NewReader(), which |
-// doesn't implement Close(). |
-func newReader(p []byte) io.ReadCloser { |
- return &reader{bytes.NewReader(p)} |
-} |
- |
-type reader struct { |
- *bytes.Reader |
-} |
- |
-func (r *reader) Close() error { |
- return nil |
-} |
- |
-type retriable struct { |
- handler Handler |
- c *http.Client |
- req *http.Request |
- closeBody io.Closer |
- seekBody io.Seeker |
- status int |
-} |
- |
-func (r *retriable) Close() error { |
- if r.closeBody != nil { |
- return r.closeBody.Close() |
- } |
- return nil |
-} |
- |
-// Warning: it returns an error on HTTP >=400. This is different than |
-// http.Client.Do() but hell it makes coding simpler. |
-func (r *retriable) Do() error { |
- if r.seekBody != nil { |
- if _, err := r.seekBody.Seek(0, os.SEEK_SET); err != nil { |
- // Can't be retried. |
- return err |
- } |
- } |
- resp, err := r.c.Do(r.req) |
- if resp != nil { |
- r.status = resp.StatusCode |
- } else { |
- r.status = 0 |
- } |
- if err != nil { |
- // Any TCP level failure can be retried but malformed URL should nt. |
- if err2, ok := err.(*url.Error); ok { |
- return err2 |
- } |
- return retry.Error{err} |
- } |
- // If the HTTP status code means the request should be retried. |
- if resp.StatusCode == 408 || resp.StatusCode == 429 || resp.StatusCode >= 500 { |
- return retry.Error{fmt.Errorf("http request failed: %s (HTTP %d)", http.StatusText(resp.StatusCode), resp.StatusCode)} |
- } |
- // Any other failure code is a hard failure. |
- if resp.StatusCode >= 400 { |
- return fmt.Errorf("http request failed: %s (HTTP %d)", http.StatusText(resp.StatusCode), resp.StatusCode) |
- } |
- // The handler may still return a retry.Error to indicate that the request |
- // should be retried even on successful status code. |
- return r.handler(resp) |
-} |
- |
-func (r *retriable) Status() int { |
- return r.status |
-} |