...

Source file src/github.com/go-kit/kit/transport/http/client.go

Documentation: github.com/go-kit/kit/transport/http

     1  package http
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"encoding/xml"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/url"
    12  
    13  	"github.com/go-kit/kit/endpoint"
    14  )
    15  
    16  // HTTPClient is an interface that models *http.Client.
    17  type HTTPClient interface {
    18  	Do(req *http.Request) (*http.Response, error)
    19  }
    20  
    21  // Client wraps a URL and provides a method that implements endpoint.Endpoint.
    22  type Client struct {
    23  	client         HTTPClient
    24  	req            CreateRequestFunc
    25  	dec            DecodeResponseFunc
    26  	before         []RequestFunc
    27  	after          []ClientResponseFunc
    28  	finalizer      []ClientFinalizerFunc
    29  	bufferedStream bool
    30  }
    31  
    32  // NewClient constructs a usable Client for a single remote method.
    33  func NewClient(method string, tgt *url.URL, enc EncodeRequestFunc, dec DecodeResponseFunc, options ...ClientOption) *Client {
    34  	return NewExplicitClient(makeCreateRequestFunc(method, tgt, enc), dec, options...)
    35  }
    36  
    37  // NewExplicitClient is like NewClient but uses a CreateRequestFunc instead of a
    38  // method, target URL, and EncodeRequestFunc, which allows for more control over
    39  // the outgoing HTTP request.
    40  func NewExplicitClient(req CreateRequestFunc, dec DecodeResponseFunc, options ...ClientOption) *Client {
    41  	c := &Client{
    42  		client: http.DefaultClient,
    43  		req:    req,
    44  		dec:    dec,
    45  	}
    46  	for _, option := range options {
    47  		option(c)
    48  	}
    49  	return c
    50  }
    51  
    52  // ClientOption sets an optional parameter for clients.
    53  type ClientOption func(*Client)
    54  
    55  // SetClient sets the underlying HTTP client used for requests.
    56  // By default, http.DefaultClient is used.
    57  func SetClient(client HTTPClient) ClientOption {
    58  	return func(c *Client) { c.client = client }
    59  }
    60  
    61  // ClientBefore adds one or more RequestFuncs to be applied to the outgoing HTTP
    62  // request before it's invoked.
    63  func ClientBefore(before ...RequestFunc) ClientOption {
    64  	return func(c *Client) { c.before = append(c.before, before...) }
    65  }
    66  
    67  // ClientAfter adds one or more ClientResponseFuncs, which are applied to the
    68  // incoming HTTP response prior to it being decoded. This is useful for
    69  // obtaining anything off of the response and adding it into the context prior
    70  // to decoding.
    71  func ClientAfter(after ...ClientResponseFunc) ClientOption {
    72  	return func(c *Client) { c.after = append(c.after, after...) }
    73  }
    74  
    75  // ClientFinalizer adds one or more ClientFinalizerFuncs to be executed at the
    76  // end of every HTTP request. Finalizers are executed in the order in which they
    77  // were added. By default, no finalizer is registered.
    78  func ClientFinalizer(f ...ClientFinalizerFunc) ClientOption {
    79  	return func(s *Client) { s.finalizer = append(s.finalizer, f...) }
    80  }
    81  
    82  // BufferedStream sets whether the HTTP response body is left open, allowing it
    83  // to be read from later. Useful for transporting a file as a buffered stream.
    84  // That body has to be drained and closed to properly end the request.
    85  func BufferedStream(buffered bool) ClientOption {
    86  	return func(c *Client) { c.bufferedStream = buffered }
    87  }
    88  
    89  // Endpoint returns a usable Go kit endpoint that calls the remote HTTP endpoint.
    90  func (c Client) Endpoint() endpoint.Endpoint {
    91  	return func(ctx context.Context, request interface{}) (interface{}, error) {
    92  		ctx, cancel := context.WithCancel(ctx)
    93  
    94  		var (
    95  			resp *http.Response
    96  			err  error
    97  		)
    98  		if c.finalizer != nil {
    99  			defer func() {
   100  				if resp != nil {
   101  					ctx = context.WithValue(ctx, ContextKeyResponseHeaders, resp.Header)
   102  					ctx = context.WithValue(ctx, ContextKeyResponseSize, resp.ContentLength)
   103  				}
   104  				for _, f := range c.finalizer {
   105  					f(ctx, err)
   106  				}
   107  			}()
   108  		}
   109  
   110  		req, err := c.req(ctx, request)
   111  		if err != nil {
   112  			cancel()
   113  			return nil, err
   114  		}
   115  
   116  		for _, f := range c.before {
   117  			ctx = f(ctx, req)
   118  		}
   119  
   120  		resp, err = c.client.Do(req.WithContext(ctx))
   121  		if err != nil {
   122  			cancel()
   123  			return nil, err
   124  		}
   125  
   126  		// If the caller asked for a buffered stream, we don't cancel the
   127  		// context when the endpoint returns. Instead, we should call the
   128  		// cancel func when closing the response body.
   129  		if c.bufferedStream {
   130  			resp.Body = bodyWithCancel{ReadCloser: resp.Body, cancel: cancel}
   131  		} else {
   132  			defer resp.Body.Close()
   133  			defer cancel()
   134  		}
   135  
   136  		for _, f := range c.after {
   137  			ctx = f(ctx, resp)
   138  		}
   139  
   140  		response, err := c.dec(ctx, resp)
   141  		if err != nil {
   142  			return nil, err
   143  		}
   144  
   145  		return response, nil
   146  	}
   147  }
   148  
   149  // bodyWithCancel is a wrapper for an io.ReadCloser with also a
   150  // cancel function which is called when the Close is used
   151  type bodyWithCancel struct {
   152  	io.ReadCloser
   153  
   154  	cancel context.CancelFunc
   155  }
   156  
   157  func (bwc bodyWithCancel) Close() error {
   158  	bwc.ReadCloser.Close()
   159  	bwc.cancel()
   160  	return nil
   161  }
   162  
   163  // ClientFinalizerFunc can be used to perform work at the end of a client HTTP
   164  // request, after the response is returned. The principal
   165  // intended use is for error logging. Additional response parameters are
   166  // provided in the context under keys with the ContextKeyResponse prefix.
   167  // Note: err may be nil. There maybe also no additional response parameters
   168  // depending on when an error occurs.
   169  type ClientFinalizerFunc func(ctx context.Context, err error)
   170  
   171  // EncodeJSONRequest is an EncodeRequestFunc that serializes the request as a
   172  // JSON object to the Request body. Many JSON-over-HTTP services can use it as
   173  // a sensible default. If the request implements Headerer, the provided headers
   174  // will be applied to the request.
   175  func EncodeJSONRequest(c context.Context, r *http.Request, request interface{}) error {
   176  	r.Header.Set("Content-Type", "application/json; charset=utf-8")
   177  	if headerer, ok := request.(Headerer); ok {
   178  		for k := range headerer.Headers() {
   179  			r.Header.Set(k, headerer.Headers().Get(k))
   180  		}
   181  	}
   182  	var b bytes.Buffer
   183  	r.Body = ioutil.NopCloser(&b)
   184  	return json.NewEncoder(&b).Encode(request)
   185  }
   186  
   187  // EncodeXMLRequest is an EncodeRequestFunc that serializes the request as a
   188  // XML object to the Request body. If the request implements Headerer,
   189  // the provided headers will be applied to the request.
   190  func EncodeXMLRequest(c context.Context, r *http.Request, request interface{}) error {
   191  	r.Header.Set("Content-Type", "text/xml; charset=utf-8")
   192  	if headerer, ok := request.(Headerer); ok {
   193  		for k := range headerer.Headers() {
   194  			r.Header.Set(k, headerer.Headers().Get(k))
   195  		}
   196  	}
   197  	var b bytes.Buffer
   198  	r.Body = ioutil.NopCloser(&b)
   199  	return xml.NewEncoder(&b).Encode(request)
   200  }
   201  
   202  //
   203  //
   204  //
   205  
   206  func makeCreateRequestFunc(method string, target *url.URL, enc EncodeRequestFunc) CreateRequestFunc {
   207  	return func(ctx context.Context, request interface{}) (*http.Request, error) {
   208  		req, err := http.NewRequest(method, target.String(), nil)
   209  		if err != nil {
   210  			return nil, err
   211  		}
   212  
   213  		if err = enc(ctx, req, request); err != nil {
   214  			return nil, err
   215  		}
   216  
   217  		return req, nil
   218  	}
   219  }
   220  

View as plain text