transport/http: {Encode,Decode}{Request,Response}Func
Peter Bourgon
8 years ago
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "encoding/json" |
4 | "io" | |
5 | 4 | "net/http" |
6 | 5 | |
7 | 6 | "golang.org/x/net/context" |
12 | 11 | ) |
13 | 12 | |
14 | 13 | func makeHTTPBinding(ctx context.Context, e endpoint.Endpoint, before []httptransport.RequestFunc, after []httptransport.ResponseFunc) http.Handler { |
15 | decode := func(r io.Reader) (interface{}, error) { | |
14 | decode := func(r *http.Request) (interface{}, error) { | |
16 | 15 | var request reqrep.AddRequest |
17 | if err := json.NewDecoder(r).Decode(&request); err != nil { | |
16 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
18 | 17 | return nil, err |
19 | 18 | } |
20 | 19 | return request, nil |
21 | 20 | } |
22 | encode := func(w io.Writer, response interface{}) error { | |
21 | encode := func(w http.ResponseWriter, response interface{}) error { | |
23 | 22 | return json.NewEncoder(w).Encode(response) |
24 | 23 | } |
25 | 24 | return httptransport.Server{ |
26 | Context: ctx, | |
27 | Endpoint: e, | |
28 | DecodeFunc: decode, | |
29 | EncodeFunc: encode, | |
30 | Before: before, | |
31 | After: append([]httptransport.ResponseFunc{httptransport.SetContentType("application/json; charset=utf-8")}, after...), | |
25 | Context: ctx, | |
26 | Endpoint: e, | |
27 | DecodeRequestFunc: decode, | |
28 | EncodeResponseFunc: encode, | |
29 | Before: before, | |
30 | After: append([]httptransport.ResponseFunc{httptransport.SetContentType("application/json; charset=utf-8")}, after...), | |
32 | 31 | } |
33 | 32 | } |
2 | 2 | import ( |
3 | 3 | "encoding/json" |
4 | 4 | "errors" |
5 | "io" | |
6 | 5 | "log" |
7 | 6 | "net/http" |
8 | 7 | "strings" |
37 | 36 | svc := stringService{} |
38 | 37 | |
39 | 38 | uppercaseHandler := httptransport.Server{ |
40 | Context: ctx, | |
41 | Endpoint: makeUppercaseEndpoint(svc), | |
42 | DecodeFunc: decodeUppercaseRequest, | |
43 | EncodeFunc: encodeResponse, | |
39 | Context: ctx, | |
40 | Endpoint: makeUppercaseEndpoint(svc), | |
41 | DecodeRequestFunc: decodeUppercaseRequest, | |
42 | EncodeResponseFunc: encodeResponse, | |
44 | 43 | } |
45 | 44 | |
46 | 45 | countHandler := httptransport.Server{ |
47 | Context: ctx, | |
48 | Endpoint: makeCountEndpoint(svc), | |
49 | DecodeFunc: decodeCountRequest, | |
50 | EncodeFunc: encodeResponse, | |
46 | Context: ctx, | |
47 | Endpoint: makeCountEndpoint(svc), | |
48 | DecodeRequestFunc: decodeCountRequest, | |
49 | EncodeResponseFunc: encodeResponse, | |
51 | 50 | } |
52 | 51 | |
53 | 52 | http.Handle("/uppercase", uppercaseHandler) |
71 | 70 | } |
72 | 71 | } |
73 | 72 | |
74 | func decodeUppercaseRequest(r io.Reader) (interface{}, error) { | |
73 | func decodeUppercaseRequest(r *http.Request) (interface{}, error) { | |
75 | 74 | var request uppercaseRequest |
76 | if err := json.NewDecoder(r).Decode(&request); err != nil { | |
75 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
77 | 76 | return nil, err |
78 | 77 | } |
79 | 78 | return request, nil |
80 | 79 | } |
81 | 80 | |
82 | func decodeCountRequest(r io.Reader) (interface{}, error) { | |
81 | func decodeCountRequest(r *http.Request) (interface{}, error) { | |
83 | 82 | var request countRequest |
84 | if err := json.NewDecoder(r).Decode(&request); err != nil { | |
83 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
85 | 84 | return nil, err |
86 | 85 | } |
87 | 86 | return request, nil |
88 | 87 | } |
89 | 88 | |
90 | func encodeResponse(w io.Writer, response interface{}) error { | |
89 | func encodeResponse(w http.ResponseWriter, response interface{}) error { | |
91 | 90 | return json.NewEncoder(w).Encode(response) |
92 | 91 | } |
93 | 92 |
43 | 43 | svc = instrumentingMiddleware{requestCount, requestLatency, countResult, svc} |
44 | 44 | |
45 | 45 | uppercaseHandler := httptransport.Server{ |
46 | Context: ctx, | |
47 | Endpoint: makeUppercaseEndpoint(svc), | |
48 | DecodeFunc: decodeUppercaseRequest, | |
49 | EncodeFunc: encodeResponse, | |
46 | Context: ctx, | |
47 | Endpoint: makeUppercaseEndpoint(svc), | |
48 | DecodeRequestFunc: decodeUppercaseRequest, | |
49 | EncodeResponseFunc: encodeResponse, | |
50 | 50 | } |
51 | 51 | |
52 | 52 | countHandler := httptransport.Server{ |
53 | Context: ctx, | |
54 | Endpoint: makeCountEndpoint(svc), | |
55 | DecodeFunc: decodeCountRequest, | |
56 | EncodeFunc: encodeResponse, | |
53 | Context: ctx, | |
54 | Endpoint: makeCountEndpoint(svc), | |
55 | DecodeRequestFunc: decodeCountRequest, | |
56 | EncodeResponseFunc: encodeResponse, | |
57 | 57 | } |
58 | 58 | |
59 | 59 | http.Handle("/uppercase", uppercaseHandler) |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "encoding/json" |
4 | "io" | |
4 | "net/http" | |
5 | 5 | |
6 | 6 | "golang.org/x/net/context" |
7 | 7 | |
24 | 24 | } |
25 | 25 | } |
26 | 26 | |
27 | func decodeUppercaseRequest(r io.Reader) (interface{}, error) { | |
27 | func decodeUppercaseRequest(r *http.Request) (interface{}, error) { | |
28 | 28 | var request uppercaseRequest |
29 | if err := json.NewDecoder(r).Decode(&request); err != nil { | |
29 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
30 | 30 | return nil, err |
31 | 31 | } |
32 | 32 | return request, nil |
33 | 33 | } |
34 | 34 | |
35 | func decodeCountRequest(r io.Reader) (interface{}, error) { | |
35 | func decodeCountRequest(r *http.Request) (interface{}, error) { | |
36 | 36 | var request countRequest |
37 | if err := json.NewDecoder(r).Decode(&request); err != nil { | |
37 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
38 | 38 | return nil, err |
39 | 39 | } |
40 | 40 | return request, nil |
41 | 41 | } |
42 | 42 | |
43 | func encodeResponse(w io.Writer, response interface{}) error { | |
43 | func encodeResponse(w http.ResponseWriter, response interface{}) error { | |
44 | 44 | return json.NewEncoder(w).Encode(response) |
45 | 45 | } |
46 | 46 |
54 | 54 | svc = instrumentingMiddleware(requestCount, requestLatency, countResult)(svc) |
55 | 55 | |
56 | 56 | uppercaseHandler := httptransport.Server{ |
57 | Context: ctx, | |
58 | Endpoint: makeUppercaseEndpoint(svc), | |
59 | DecodeFunc: decodeUppercaseRequest, | |
60 | EncodeFunc: encode, | |
57 | Context: ctx, | |
58 | Endpoint: makeUppercaseEndpoint(svc), | |
59 | DecodeRequestFunc: decodeUppercaseRequest, | |
60 | EncodeResponseFunc: encodeResponse, | |
61 | 61 | } |
62 | 62 | countHandler := httptransport.Server{ |
63 | Context: ctx, | |
64 | Endpoint: makeCountEndpoint(svc), | |
65 | DecodeFunc: decodeCountRequest, | |
66 | EncodeFunc: encode, | |
63 | Context: ctx, | |
64 | Endpoint: makeCountEndpoint(svc), | |
65 | DecodeRequestFunc: decodeCountRequest, | |
66 | EncodeResponseFunc: encodeResponse, | |
67 | 67 | } |
68 | 68 | |
69 | 69 | http.Handle("/uppercase", uppercaseHandler) |
81 | 81 | u.Path = "/uppercase" |
82 | 82 | } |
83 | 83 | return (httptransport.Client{ |
84 | Client: http.DefaultClient, | |
85 | Method: "GET", | |
86 | URL: u, | |
87 | Context: ctx, | |
88 | EncodeFunc: encode, | |
89 | DecodeFunc: decodeUppercaseResponse, | |
84 | Client: http.DefaultClient, | |
85 | Method: "GET", | |
86 | URL: u, | |
87 | Context: ctx, | |
88 | DecodeResponseFunc: decodeUppercaseResponse, | |
89 | EncodeRequestFunc: encodeRequest, | |
90 | 90 | }).Endpoint() |
91 | 91 | } |
92 | 92 |
Binary diff not shown
0 | 0 | package main |
1 | 1 | |
2 | 2 | import ( |
3 | "bytes" | |
3 | 4 | "encoding/json" |
4 | "io" | |
5 | "io/ioutil" | |
6 | "net/http" | |
5 | 7 | |
6 | 8 | "golang.org/x/net/context" |
7 | 9 | |
24 | 26 | } |
25 | 27 | } |
26 | 28 | |
27 | func decodeUppercaseRequest(r io.Reader) (interface{}, error) { | |
29 | func decodeUppercaseRequest(r *http.Request) (interface{}, error) { | |
28 | 30 | var request uppercaseRequest |
29 | if err := json.NewDecoder(r).Decode(&request); err != nil { | |
31 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
30 | 32 | return nil, err |
31 | 33 | } |
32 | 34 | return request, nil |
33 | 35 | } |
34 | 36 | |
35 | func decodeCountRequest(r io.Reader) (interface{}, error) { | |
37 | func decodeCountRequest(r *http.Request) (interface{}, error) { | |
36 | 38 | var request countRequest |
37 | if err := json.NewDecoder(r).Decode(&request); err != nil { | |
39 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
38 | 40 | return nil, err |
39 | 41 | } |
40 | 42 | return request, nil |
41 | 43 | } |
42 | 44 | |
43 | func decodeUppercaseResponse(r io.Reader) (interface{}, error) { | |
45 | func decodeUppercaseResponse(r *http.Response) (interface{}, error) { | |
44 | 46 | var response uppercaseResponse |
45 | if err := json.NewDecoder(r).Decode(&response); err != nil { | |
47 | if err := json.NewDecoder(r.Body).Decode(&response); err != nil { | |
46 | 48 | return nil, err |
47 | 49 | } |
48 | 50 | return response, nil |
49 | 51 | } |
50 | 52 | |
51 | func encode(w io.Writer, response interface{}) error { | |
53 | func encodeResponse(w http.ResponseWriter, response interface{}) error { | |
52 | 54 | return json.NewEncoder(w).Encode(response) |
55 | } | |
56 | ||
57 | func encodeRequest(r *http.Request, request interface{}) error { | |
58 | var buf bytes.Buffer | |
59 | if err := json.NewEncoder(&buf).Encode(request); err != nil { | |
60 | return err | |
61 | } | |
62 | r.Body = ioutil.NopCloser(&buf) | |
63 | return nil | |
53 | 64 | } |
54 | 65 | |
55 | 66 | type uppercaseRequest struct { |
0 | 0 | package http |
1 | 1 | |
2 | 2 | import ( |
3 | "bytes" | |
4 | 3 | "fmt" |
5 | 4 | "net/http" |
6 | 5 | "net/url" |
24 | 23 | // A background context must be provided. |
25 | 24 | context.Context |
26 | 25 | |
27 | // EncodeFunc, used to encode request types, must be provided. | |
28 | EncodeFunc | |
26 | // EncodeRequestFunc must be provided. The HTTP request passed to the | |
27 | // EncodeRequestFunc will have a nil body. | |
28 | EncodeRequestFunc | |
29 | 29 | |
30 | // DecodeFunc, used to decode response types, must be provided. | |
31 | DecodeFunc | |
30 | // DecodeResponseFunc must be provided. | |
31 | DecodeResponseFunc | |
32 | 32 | |
33 | 33 | // Before functions are executed on the outgoing request after it is |
34 | 34 | // created, but before it's sent to the HTTP client. Clients have no After |
43 | 43 | ctx, cancel := context.WithCancel(ctx) |
44 | 44 | defer cancel() |
45 | 45 | |
46 | var buf bytes.Buffer | |
47 | if err := c.EncodeFunc(&buf, request); err != nil { | |
48 | return nil, fmt.Errorf("Encode: %v", err) | |
46 | req, err := http.NewRequest(c.Method, c.URL.String(), nil) | |
47 | if err != nil { | |
48 | return nil, fmt.Errorf("NewRequest: %v", err) | |
49 | 49 | } |
50 | 50 | |
51 | req, err := http.NewRequest(c.Method, c.URL.String(), &buf) | |
52 | if err != nil { | |
53 | return nil, fmt.Errorf("NewRequest: %v", err) | |
51 | if err = c.EncodeRequestFunc(req, request); err != nil { | |
52 | return nil, fmt.Errorf("Encode: %v", err) | |
54 | 53 | } |
55 | 54 | |
56 | 55 | for _, f := range c.Before { |
68 | 67 | } |
69 | 68 | defer func() { _ = resp.Body.Close() }() |
70 | 69 | |
71 | response, err := c.DecodeFunc(resp.Body) | |
70 | response, err := c.DecodeResponseFunc(resp) | |
72 | 71 | if err != nil { |
73 | 72 | return nil, fmt.Errorf("Decode: %v", err) |
74 | 73 | } |
0 | 0 | package http_test |
1 | 1 | |
2 | 2 | import ( |
3 | "io" | |
4 | 3 | "net/http" |
5 | 4 | "net/http/httptest" |
6 | 5 | "net/url" |
14 | 13 | |
15 | 14 | func TestHTTPClient(t *testing.T) { |
16 | 15 | var ( |
17 | decode = func(r io.Reader) (interface{}, error) { return struct{}{}, nil } | |
18 | encode = func(w io.Writer, response interface{}) error { return nil } | |
16 | encode = func(*http.Request, interface{}) error { return nil } | |
17 | decode = func(*http.Response) (interface{}, error) { return struct{}{}, nil } | |
19 | 18 | headers = make(chan string, 1) |
20 | 19 | headerKey = "X-Foo" |
21 | 20 | headerVal = "abcde" |
27 | 26 | })) |
28 | 27 | |
29 | 28 | client := httptransport.Client{ |
30 | Method: "GET", | |
31 | URL: mustParse(server.URL), | |
32 | Context: context.Background(), | |
33 | DecodeFunc: decode, | |
34 | EncodeFunc: encode, | |
35 | Before: []httptransport.RequestFunc{httptransport.SetRequestHeader(headerKey, headerVal)}, | |
29 | Method: "GET", | |
30 | URL: mustParse(server.URL), | |
31 | Context: context.Background(), | |
32 | EncodeRequestFunc: encode, | |
33 | DecodeResponseFunc: decode, | |
34 | Before: []httptransport.RequestFunc{httptransport.SetRequestHeader(headerKey, headerVal)}, | |
36 | 35 | } |
37 | 36 | |
38 | 37 | _, err := client.Endpoint()(context.Background(), struct{}{}) |
0 | 0 | package http |
1 | 1 | |
2 | import "io" | |
2 | import "net/http" | |
3 | 3 | |
4 | // DecodeFunc converts a serialized request (server) or response (client) to a | |
5 | // user version of the same. One straightforward DecodeFunc could be something | |
6 | // that JSON-decodes the reader to a concrete type. | |
7 | type DecodeFunc func(io.Reader) (interface{}, error) | |
4 | // DecodeRequestFunc extracts a user-domain request object from an HTTP | |
5 | // request object. It's designed to be used in HTTP servers, for server-side | |
6 | // endpoints. One straightforward DecodeRequestFunc could be something that | |
7 | // JSON decodes from the request body to the concrete response type. | |
8 | type DecodeRequestFunc func(*http.Request) (request interface{}, err error) | |
8 | 9 | |
9 | // EncodeFunc converts a user response (server) or request (client) to a | |
10 | // serialized version of the same, by encoding the interface to the writer. | |
11 | type EncodeFunc func(io.Writer, interface{}) error | |
10 | // EncodeRequestFunc encodes the passed request object into the HTTP request | |
11 | // object. It's designed to be used in HTTP clients, for client-side | |
12 | // endpoints. One straightforward EncodeRequestFunc could something that JSON | |
13 | // encodes the object directly to the request body. | |
14 | type EncodeRequestFunc func(*http.Request, interface{}) error | |
15 | ||
16 | // EncodeResponseFunc encodes the passed response object to the HTTP response | |
17 | // writer. It's designed to be used in HTTP servers, for server-side | |
18 | // endpoints. One straightforward EncodeResponseFunc could be something that | |
19 | // JSON encodes the object directly to the response body. | |
20 | type EncodeResponseFunc func(http.ResponseWriter, interface{}) error | |
21 | ||
22 | // DecodeResponseFunc extracts a user-domain response object from an HTTP | |
23 | // response object. It's designed to be used in HTTP clients, for client-side | |
24 | // endpoints. One straightforward DecodeResponseFunc could be something that | |
25 | // JSON decodes from the response body to the concrete response type. | |
26 | type DecodeResponseFunc func(*http.Response) (response interface{}, err error) |
9 | 9 | |
10 | 10 | // Server wraps an endpoint and implements http.Handler. |
11 | 11 | type Server struct { |
12 | // A background context must be provided. | |
12 | 13 | context.Context |
14 | ||
15 | // The endpoint that will be invoked. | |
13 | 16 | endpoint.Endpoint |
14 | DecodeFunc | |
15 | EncodeFunc | |
17 | ||
18 | // DecodeRequestFunc must be provided. | |
19 | DecodeRequestFunc | |
20 | ||
21 | // EncodeResponseFunc must be provided. | |
22 | EncodeResponseFunc | |
23 | ||
24 | // Before functions are executed on the HTTP request object before the | |
25 | // request is decoded. | |
16 | 26 | Before []RequestFunc |
17 | After []ResponseFunc | |
27 | ||
28 | // After functions are executed on the HTTP response writer after the | |
29 | // endpoint is invoked, but before anything is written to the client. | |
30 | After []ResponseFunc | |
18 | 31 | } |
19 | 32 | |
20 | 33 | // ServeHTTP implements http.Handler. |
26 | 39 | ctx = f(ctx, r) |
27 | 40 | } |
28 | 41 | |
29 | request, err := b.DecodeFunc(r.Body) | |
42 | request, err := b.DecodeRequestFunc(r) | |
30 | 43 | if err != nil { |
31 | 44 | http.Error(w, err.Error(), http.StatusBadRequest) |
32 | 45 | return |
46 | 59 | f(ctx, w) |
47 | 60 | } |
48 | 61 | |
49 | if err := b.EncodeFunc(w, response); err != nil { | |
62 | if err := b.EncodeResponseFunc(w, response); err != nil { | |
50 | 63 | http.Error(w, err.Error(), http.StatusInternalServerError) |
51 | 64 | return |
52 | 65 | } |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "errors" |
4 | "io" | |
5 | 4 | "io/ioutil" |
6 | 5 | "net/http" |
7 | 6 | "net/http/httptest" |
14 | 13 | |
15 | 14 | func TestServerBadDecode(t *testing.T) { |
16 | 15 | handler := httptransport.Server{ |
17 | Context: context.Background(), | |
18 | Endpoint: func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, | |
19 | DecodeFunc: func(io.Reader) (interface{}, error) { return struct{}{}, errors.New("dang") }, | |
20 | EncodeFunc: func(io.Writer, interface{}) error { return nil }, | |
16 | Context: context.Background(), | |
17 | Endpoint: func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, | |
18 | DecodeRequestFunc: func(*http.Request) (interface{}, error) { return struct{}{}, errors.New("dang") }, | |
19 | EncodeResponseFunc: func(http.ResponseWriter, interface{}) error { return nil }, | |
21 | 20 | } |
22 | 21 | server := httptest.NewServer(handler) |
23 | 22 | defer server.Close() |
29 | 28 | |
30 | 29 | func TestServerBadEndpoint(t *testing.T) { |
31 | 30 | handler := httptransport.Server{ |
32 | Context: context.Background(), | |
33 | Endpoint: func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errors.New("dang") }, | |
34 | DecodeFunc: func(io.Reader) (interface{}, error) { return struct{}{}, nil }, | |
35 | EncodeFunc: func(io.Writer, interface{}) error { return nil }, | |
31 | Context: context.Background(), | |
32 | Endpoint: func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errors.New("dang") }, | |
33 | DecodeRequestFunc: func(*http.Request) (interface{}, error) { return struct{}{}, nil }, | |
34 | EncodeResponseFunc: func(http.ResponseWriter, interface{}) error { return nil }, | |
36 | 35 | } |
37 | 36 | server := httptest.NewServer(handler) |
38 | 37 | defer server.Close() |
44 | 43 | |
45 | 44 | func TestServerBadEncode(t *testing.T) { |
46 | 45 | handler := httptransport.Server{ |
47 | Context: context.Background(), | |
48 | Endpoint: func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, | |
49 | DecodeFunc: func(io.Reader) (interface{}, error) { return struct{}{}, nil }, | |
50 | EncodeFunc: func(io.Writer, interface{}) error { return errors.New("dang") }, | |
46 | Context: context.Background(), | |
47 | Endpoint: func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, | |
48 | DecodeRequestFunc: func(*http.Request) (interface{}, error) { return struct{}{}, nil }, | |
49 | EncodeResponseFunc: func(http.ResponseWriter, interface{}) error { return errors.New("dang") }, | |
51 | 50 | } |
52 | 51 | server := httptest.NewServer(handler) |
53 | 52 | defer server.Close() |
75 | 74 | endpoint = func(context.Context, interface{}) (interface{}, error) { <-stepch; return struct{}{}, nil } |
76 | 75 | response = make(chan *http.Response) |
77 | 76 | handler = httptransport.Server{ |
78 | Context: ctx, | |
79 | Endpoint: endpoint, | |
80 | DecodeFunc: func(io.Reader) (interface{}, error) { return struct{}{}, nil }, | |
81 | EncodeFunc: func(io.Writer, interface{}) error { return nil }, | |
82 | Before: []httptransport.RequestFunc{func(ctx context.Context, r *http.Request) context.Context { return ctx }}, | |
83 | After: []httptransport.ResponseFunc{func(ctx context.Context, w http.ResponseWriter) { return }}, | |
77 | Context: ctx, | |
78 | Endpoint: endpoint, | |
79 | DecodeRequestFunc: func(*http.Request) (interface{}, error) { return struct{}{}, nil }, | |
80 | EncodeResponseFunc: func(http.ResponseWriter, interface{}) error { return nil }, | |
81 | Before: []httptransport.RequestFunc{func(ctx context.Context, r *http.Request) context.Context { return ctx }}, | |
82 | After: []httptransport.ResponseFunc{func(ctx context.Context, w http.ResponseWriter) { return }}, | |
84 | 83 | } |
85 | 84 | ) |
86 | 85 | go func() { |