Move HTTP client to transport/http
Peter Bourgon
8 years ago
0 | 0 | package main |
1 | 1 | |
2 | 2 | import ( |
3 | "bytes" | |
4 | "net/http" | |
5 | "net/url" | |
6 | "strings" | |
7 | ||
8 | "github.com/go-kit/kit/log" | |
9 | ||
10 | 3 | "golang.org/x/net/context" |
11 | 4 | |
12 | 5 | "github.com/go-kit/kit/client" |
13 | "github.com/go-kit/kit/transport/codec" | |
14 | httptransport "github.com/go-kit/kit/transport/http" | |
6 | "github.com/go-kit/kit/log" | |
15 | 7 | ) |
16 | 8 | |
17 | 9 | // Add is the abstract definition of what this service does. It could easily |
19 | 11 | // be an endpoint. |
20 | 12 | type Add func(context.Context, int64, int64) int64 |
21 | 13 | |
14 | // pureAdd implements Add with no dependencies. | |
22 | 15 | func pureAdd(_ context.Context, a, b int64) int64 { return a + b } |
23 | 16 | |
17 | // proxyAdd implements Add by invoking a remote Add service. | |
24 | 18 | func proxyAdd(e client.Endpoint) Add { |
25 | 19 | return func(ctx context.Context, a, b int64) int64 { |
26 | 20 | resp, err := e(ctx, &addRequest{a, b}) |
36 | 30 | return addResp.V |
37 | 31 | } |
38 | 32 | } |
39 | ||
40 | type httpClient struct { | |
41 | *url.URL | |
42 | codec.Codec | |
43 | method string | |
44 | *http.Client | |
45 | before []httptransport.BeforeFunc | |
46 | makeResponse func() interface{} | |
47 | } | |
48 | ||
49 | // TODO this needs to go to package client | |
50 | func newHTTPClient(addr string, cdc codec.Codec, makeResponse func() interface{}, options ...httpClientOption) client.Endpoint { | |
51 | if !strings.HasPrefix(addr, "http") { | |
52 | addr = "http://" + addr | |
53 | } | |
54 | u, err := url.Parse(addr) | |
55 | if err != nil { | |
56 | panic(err) | |
57 | } | |
58 | ||
59 | c := httpClient{ | |
60 | URL: u, | |
61 | Codec: cdc, | |
62 | method: "GET", | |
63 | Client: http.DefaultClient, | |
64 | makeResponse: makeResponse, | |
65 | } | |
66 | for _, option := range options { | |
67 | option(&c) | |
68 | } | |
69 | return c.endpoint | |
70 | } | |
71 | ||
72 | func (c httpClient) endpoint(ctx context.Context, request interface{}) (interface{}, error) { | |
73 | var buf bytes.Buffer | |
74 | if err := c.Codec.Encode(&buf, request); err != nil { | |
75 | return nil, err | |
76 | } | |
77 | ||
78 | req, err := http.NewRequest(c.method, c.URL.String(), &buf) | |
79 | if err != nil { | |
80 | return nil, err | |
81 | } | |
82 | ||
83 | for _, f := range c.before { | |
84 | f(ctx, req) | |
85 | } | |
86 | ||
87 | resp, err := c.Client.Do(req) | |
88 | if err != nil { | |
89 | return nil, err | |
90 | } | |
91 | defer resp.Body.Close() | |
92 | ||
93 | response := c.makeResponse() | |
94 | ctx, err = c.Codec.Decode(ctx, resp.Body, response) | |
95 | if err != nil { | |
96 | return nil, err | |
97 | } | |
98 | ||
99 | return response, nil | |
100 | } | |
101 | ||
102 | type httpClientOption func(*httpClient) | |
103 | ||
104 | func before(funcs ...httptransport.BeforeFunc) httpClientOption { | |
105 | return func(c *httpClient) { c.before = append(c.before, funcs...) } | |
106 | } |
97 | 97 | makeResponse := func() interface{} { return &addResponse{} } |
98 | 98 | |
99 | 99 | var e client.Endpoint |
100 | e = newHTTPClient(*proxyHTTPAddr, codec, makeResponse, before(zipkin.ToRequest(zipkinSpanFunc))) | |
100 | e = httptransport.NewClient(*proxyHTTPAddr, codec, makeResponse, httptransport.ClientBefore(zipkin.ToRequest(zipkinSpanFunc))) | |
101 | 101 | e = zipkin.AnnotateClient(zipkinSpanFunc, zipkinCollector)(e) |
102 | 102 | |
103 | 103 | a = proxyAdd(e) |
130 | 130 | defer cancel() |
131 | 131 | |
132 | 132 | field := metrics.Field{Key: "transport", Value: "http"} |
133 | before := httptransport.Before(zipkin.ToContext(zipkinSpanFunc)) | |
134 | after := httptransport.After(httptransport.SetContentType("application/json")) | |
133 | before := httptransport.BindingBefore(zipkin.ToContext(zipkinSpanFunc)) | |
134 | after := httptransport.BindingAfter(httptransport.SetContentType("application/json")) | |
135 | 135 | makeRequest := func() interface{} { return &addRequest{} } |
136 | 136 | |
137 | 137 | var handler http.Handler |
0 | package http_test | |
1 | ||
2 | import ( | |
3 | "net/http/httptest" | |
4 | "testing" | |
5 | ||
6 | "golang.org/x/net/context" | |
7 | ||
8 | httptransport "github.com/go-kit/kit/transport/http" | |
9 | ) | |
10 | ||
11 | func TestSetContentType(t *testing.T) { | |
12 | contentType := "application/whatever" | |
13 | rec := httptest.NewRecorder() | |
14 | httptransport.SetContentType(contentType)(context.Background(), rec) | |
15 | if want, have := contentType, rec.Header().Get("Content-Type"); want != have { | |
16 | t.Errorf("want %q, have %q", want, have) | |
17 | } | |
18 | } |
7 | 7 | "github.com/go-kit/kit/server" |
8 | 8 | "github.com/go-kit/kit/transport/codec" |
9 | 9 | ) |
10 | ||
11 | // BindingOption sets a parameter for the binding. | |
12 | type BindingOption func(*binding) | |
13 | ||
14 | // Before adds pre-RPC BeforeFuncs to the binding. | |
15 | func Before(funcs ...BeforeFunc) BindingOption { | |
16 | return func(b *binding) { b.before = append(b.before, funcs...) } | |
17 | } | |
18 | ||
19 | // After adds post-RPC AfterFuncs to the binding. | |
20 | func After(funcs ...AfterFunc) BindingOption { | |
21 | return func(b *binding) { b.after = append(b.after, funcs...) } | |
22 | } | |
23 | 10 | |
24 | 11 | type binding struct { |
25 | 12 | context.Context |
80 | 67 | return |
81 | 68 | } |
82 | 69 | } |
70 | ||
71 | // BindingOption sets a parameter for the HTTP binding. | |
72 | type BindingOption func(*binding) | |
73 | ||
74 | // BindingBefore adds pre-RPC BeforeFuncs to the HTTP binding. | |
75 | func BindingBefore(funcs ...BeforeFunc) BindingOption { | |
76 | return func(b *binding) { b.before = append(b.before, funcs...) } | |
77 | } | |
78 | ||
79 | // BindingAfter adds post-RPC AfterFuncs to the HTTP binding. | |
80 | func BindingAfter(funcs ...AfterFunc) BindingOption { | |
81 | return func(b *binding) { b.after = append(b.after, funcs...) } | |
82 | } |
55 | 55 | defer resp.Body.Close() |
56 | 56 | |
57 | 57 | var r myResponse |
58 | if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { | |
58 | if _, err := codec.Decode(ctx, resp.Body, &r); err != nil { | |
59 | 59 | t.Fatal(err) |
60 | 60 | } |
61 | 61 |
0 | package http | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "net/http" | |
5 | "net/url" | |
6 | ||
7 | "golang.org/x/net/context" | |
8 | ||
9 | "github.com/go-kit/kit/client" | |
10 | "github.com/go-kit/kit/transport/codec" | |
11 | ) | |
12 | ||
13 | type httpClient struct { | |
14 | *url.URL | |
15 | codec.Codec | |
16 | method string | |
17 | *http.Client | |
18 | before []BeforeFunc | |
19 | makeResponse func() interface{} | |
20 | } | |
21 | ||
22 | // NewClient returns a client endpoint for a remote service. addr must be a | |
23 | // valid, parseable URL, including the scheme and path. | |
24 | func NewClient(addr string, cdc codec.Codec, makeResponse func() interface{}, options ...ClientOption) client.Endpoint { | |
25 | u, err := url.Parse(addr) | |
26 | if err != nil { | |
27 | panic(err) | |
28 | } | |
29 | c := httpClient{ | |
30 | URL: u, | |
31 | Codec: cdc, | |
32 | method: "GET", | |
33 | Client: http.DefaultClient, | |
34 | makeResponse: makeResponse, | |
35 | } | |
36 | for _, option := range options { | |
37 | option(&c) | |
38 | } | |
39 | return c.endpoint | |
40 | } | |
41 | ||
42 | func (c httpClient) endpoint(ctx context.Context, request interface{}) (interface{}, error) { | |
43 | var buf bytes.Buffer | |
44 | if err := c.Codec.Encode(&buf, request); err != nil { | |
45 | return nil, err | |
46 | } | |
47 | ||
48 | req, err := http.NewRequest(c.method, c.URL.String(), &buf) | |
49 | if err != nil { | |
50 | return nil, err | |
51 | } | |
52 | ||
53 | for _, f := range c.before { | |
54 | f(ctx, req) | |
55 | } | |
56 | ||
57 | resp, err := c.Client.Do(req) | |
58 | if err != nil { | |
59 | return nil, err | |
60 | } | |
61 | defer resp.Body.Close() | |
62 | ||
63 | response := c.makeResponse() | |
64 | ctx, err = c.Codec.Decode(ctx, resp.Body, response) | |
65 | if err != nil { | |
66 | return nil, err | |
67 | } | |
68 | ||
69 | return response, nil | |
70 | } | |
71 | ||
72 | // ClientOption sets a parameter for the HTTP client. | |
73 | type ClientOption func(*httpClient) | |
74 | ||
75 | // ClientBefore adds pre-invocation BeforeFuncs to the HTTP client. | |
76 | func ClientBefore(funcs ...BeforeFunc) ClientOption { | |
77 | return func(c *httpClient) { c.before = append(c.before, funcs...) } | |
78 | } | |
79 | ||
80 | // ClientMethod sets the method used to invoke the RPC. By default, it's GET. | |
81 | func ClientMethod(method string) ClientOption { | |
82 | return func(c *httpClient) { c.method = method } | |
83 | } | |
84 | ||
85 | // SetClient sets the HTTP client struct used to invoke the RPC. By default, | |
86 | // it's http.DefaultClient. | |
87 | func SetClient(client *http.Client) ClientOption { | |
88 | return func(c *httpClient) { c.Client = client } | |
89 | } |
0 | package http_test | |
1 | ||
2 | import ( | |
3 | "net/http" | |
4 | "net/http/httptest" | |
5 | "reflect" | |
6 | "testing" | |
7 | ||
8 | "golang.org/x/net/context" | |
9 | ||
10 | jsoncodec "github.com/go-kit/kit/transport/codec/json" | |
11 | httptransport "github.com/go-kit/kit/transport/http" | |
12 | ) | |
13 | ||
14 | func TestClient(t *testing.T) { | |
15 | type myResponse struct { | |
16 | V int `json:"v"` | |
17 | } | |
18 | const v = 123 | |
19 | codec := jsoncodec.New() | |
20 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
21 | codec.Encode(w, myResponse{v}) | |
22 | })) | |
23 | defer server.Close() | |
24 | makeResponse := func() interface{} { return &myResponse{} } | |
25 | client := httptransport.NewClient(server.URL, codec, makeResponse) | |
26 | resp, err := client(context.Background(), struct{}{}) | |
27 | if err != nil { | |
28 | t.Fatal(err) | |
29 | } | |
30 | response, ok := resp.(*myResponse) | |
31 | if !ok { | |
32 | t.Fatalf("not myResponse (%s)", reflect.TypeOf(response)) | |
33 | } | |
34 | if want, have := v, response.V; want != have { | |
35 | t.Errorf("want %d, have %d", want, have) | |
36 | } | |
37 | } |