Codebase list golang-github-go-kit-kit / 6ce524c
transport/http: NewExplicitClient (#971) * transport/http: NewExplicitClient * transport/http: improve NewClient/NewExplicitClient relationship Peter Bourgon authored 4 years ago GitHub committed 4 years ago
3 changed file(s) with 85 addition(s) and 37 deletion(s). Raw diff Collapse all Expand all
2020 // Client wraps a URL and provides a method that implements endpoint.Endpoint.
2121 type Client struct {
2222 client HTTPClient
23 method string
24 tgt *url.URL
25 enc EncodeRequestFunc
23 req CreateRequestFunc
2624 dec DecodeResponseFunc
2725 before []RequestFunc
2826 after []ClientResponseFunc
3129 }
3230
3331 // NewClient constructs a usable Client for a single remote method.
34 func NewClient(
35 method string,
36 tgt *url.URL,
37 enc EncodeRequestFunc,
38 dec DecodeResponseFunc,
39 options ...ClientOption,
40 ) *Client {
32 func NewClient(method string, tgt *url.URL, enc EncodeRequestFunc, dec DecodeResponseFunc, options ...ClientOption) *Client {
33 return NewExplicitClient(makeCreateRequestFunc(method, tgt, enc), dec, options...)
34 }
35
36 // NewExplicitClient is like NewClient but uses a CreateRequestFunc instead of a
37 // method, target URL, and EncodeRequestFunc, which allows for more control over
38 // the outgoing HTTP request.
39 func NewExplicitClient(req CreateRequestFunc, dec DecodeResponseFunc, options ...ClientOption) *Client {
4140 c := &Client{
42 client: http.DefaultClient,
43 method: method,
44 tgt: tgt,
45 enc: enc,
46 dec: dec,
47 before: []RequestFunc{},
48 after: []ClientResponseFunc{},
49 bufferedStream: false,
41 client: http.DefaultClient,
42 req: req,
43 dec: dec,
5044 }
5145 for _, option := range options {
5246 option(c)
6357 return func(c *Client) { c.client = client }
6458 }
6559
66 // ClientBefore sets the RequestFuncs that are applied to the outgoing HTTP
60 // ClientBefore adds one or more RequestFuncs to be applied to the outgoing HTTP
6761 // request before it's invoked.
6862 func ClientBefore(before ...RequestFunc) ClientOption {
6963 return func(c *Client) { c.before = append(c.before, before...) }
7064 }
7165
72 // ClientAfter sets the ClientResponseFuncs applied to the incoming HTTP
73 // request prior to it being decoded. This is useful for obtaining anything off
74 // of the response and adding onto the context prior to decoding.
66 // ClientAfter adds one or more ClientResponseFuncs, which are applied to the
67 // incoming HTTP response prior to it being decoded. This is useful for
68 // obtaining anything off of the response and adding it into the context prior
69 // to decoding.
7570 func ClientAfter(after ...ClientResponseFunc) ClientOption {
7671 return func(c *Client) { c.after = append(c.after, after...) }
7772 }
7873
79 // ClientFinalizer is executed at the end of every HTTP request.
80 // By default, no finalizer is registered.
74 // ClientFinalizer adds one or more ClientFinalizerFuncs to be executed at the
75 // end of every HTTP request. Finalizers are executed in the order in which they
76 // were added. By default, no finalizer is registered.
8177 func ClientFinalizer(f ...ClientFinalizerFunc) ClientOption {
8278 return func(s *Client) { s.finalizer = append(s.finalizer, f...) }
8379 }
8480
85 // BufferedStream sets whether the Response.Body is left open, allowing it
81 // BufferedStream sets whether the HTTP response body is left open, allowing it
8682 // to be read from later. Useful for transporting a file as a buffered stream.
87 // That body has to be Closed to propery end the request.
83 // That body has to be drained and closed to properly end the request.
8884 func BufferedStream(buffered bool) ClientOption {
8985 return func(c *Client) { c.bufferedStream = buffered }
9086 }
9187
92 // Endpoint returns a usable endpoint that invokes the remote endpoint.
88 // Endpoint returns a usable Go kit endpoint that calls the remote HTTP endpoint.
9389 func (c Client) Endpoint() endpoint.Endpoint {
9490 return func(ctx context.Context, request interface{}) (interface{}, error) {
9591 ctx, cancel := context.WithCancel(ctx)
110106 }()
111107 }
112108
113 req, err := http.NewRequest(c.method, c.tgt.String(), nil)
114 if err != nil {
115 cancel()
116 return nil, err
117 }
118
119 if err = c.enc(ctx, req, request); err != nil {
109 req, err := c.req(ctx, request)
110 if err != nil {
120111 cancel()
121112 return nil, err
122113 }
126117 }
127118
128119 resp, err = c.client.Do(req.WithContext(ctx))
129
130120 if err != nil {
131121 cancel()
132122 return nil, err
133123 }
134124
135 // If we expect a buffered stream, we don't cancel the context when the endpoint returns.
136 // Instead, we should call the cancel func when closing the response body.
125 // If the caller asked for a buffered stream, we don't cancel the
126 // context when the endpoint returns. Instead, we should call the
127 // cancel func when closing the response body.
137128 if c.bufferedStream {
138129 resp.Body = bodyWithCancel{ReadCloser: resp.Body, cancel: cancel}
139130 } else {
206197 r.Body = ioutil.NopCloser(&b)
207198 return xml.NewEncoder(&b).Encode(request)
208199 }
200
201 //
202 //
203 //
204
205 func makeCreateRequestFunc(method string, target *url.URL, enc EncodeRequestFunc) CreateRequestFunc {
206 return func(ctx context.Context, request interface{}) (*http.Request, error) {
207 req, err := http.NewRequest(method, target.String(), nil)
208 if err != nil {
209 return nil, err
210 }
211
212 if err = enc(ctx, req, request); err != nil {
213 return nil, err
214 }
215
216 return req, nil
217 }
218 }
22 import (
33 "bytes"
44 "context"
5 "fmt"
56 "io"
67 "io/ioutil"
78 "net/http"
89 "net/http/httptest"
910 "net/url"
11 "strings"
1012 "testing"
1113 "time"
1214
296298 }
297299 }
298300
301 func TestNewExplicitClient(t *testing.T) {
302 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
303 fmt.Fprintf(w, "%d", r.ContentLength)
304 }))
305 defer srv.Close()
306
307 req := func(ctx context.Context, request interface{}) (*http.Request, error) {
308 req, _ := http.NewRequest("POST", srv.URL, strings.NewReader(request.(string)))
309 return req, nil
310 }
311
312 dec := func(_ context.Context, resp *http.Response) (response interface{}, err error) {
313 buf, err := ioutil.ReadAll(resp.Body)
314 resp.Body.Close()
315 return string(buf), err
316 }
317
318 client := httptransport.NewExplicitClient(req, dec)
319
320 request := "hello world"
321 response, err := client.Endpoint()(context.Background(), request)
322 if err != nil {
323 t.Fatal(err)
324 }
325
326 if want, have := "11", response.(string); want != have {
327 t.Fatalf("want %q, have %q", want, have)
328 }
329 }
330
299331 func mustParse(s string) *url.URL {
300332 u, err := url.Parse(s)
301333 if err != nil {
1616 // encodes the object directly to the request body.
1717 type EncodeRequestFunc func(context.Context, *http.Request, interface{}) error
1818
19 // CreateRequestFunc creates an outgoing HTTP request based on the passed
20 // request object. It's designed to be used in HTTP clients, for client-side
21 // endpoints. It's a more powerful version of EncodeRequestFunc, and can be used
22 // if more fine-grained control of the HTTP request is required.
23 type CreateRequestFunc func(context.Context, interface{}) (*http.Request, error)
24
1925 // EncodeResponseFunc encodes the passed response object to the HTTP response
2026 // writer. It's designed to be used in HTTP servers, for server-side
2127 // endpoints. One straightforward EncodeResponseFunc could be something that