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

Unified Diff: client/internal/lhttp/client.go

Issue 1159563002: Move retry and lhttp packages from client/internal to common/. (Closed) Base URL: git@github.com:luci/luci-go@master
Patch Set: move-retry-http Created 5 years, 7 months 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « client/cmd/swarming/common.go ('k') | client/internal/lhttp/client_test.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
-}
« no previous file with comments | « client/cmd/swarming/common.go ('k') | client/internal/lhttp/client_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698