Codebase list golang-github-go-kit-kit / 2a460f0
Add unit tests and README.md for httprp package Scott Kidder 8 years ago
3 changed file(s) with 171 addition(s) and 7 deletion(s). Raw diff Collapse all Expand all
0 # package transport/httprp
1
2 `package transport/httprp` provides an HTTP reverse-proxy transport.
3
4 ## Rationale
5
6 HTTP server applications often associated multiple handlers with a single HTTP listener, each handler differentiated by the request URI and/or HTTP method. Handlers that perform business-logic in the app can implement the `Endpoint` interface and be exposed using the `package transport/http` server. Handlers that need to proxy the request to another HTTP endpoint can do so with this package by simply specifying the base URL to forward the request to.
7
8 ## Usage
9
10 The following example uses the [Gorilla Mux](https://github.com/gorilla/mux) router to illustrate how a mixture of proxying and non-proxying request handlers can be used with a single listener:
11
12 ```go
13 import (
14 "net/http"
15 "net/url"
16
17 kithttp "github.com/go-kit/kit/transport/http"
18 kithttprp "github.com/go-kit/kit/transport/httprp"
19 "github.com/gorilla/mux"
20 "golang.org/x/net/context"
21 )
22
23 func main() {
24 router := mux.NewRouter()
25
26 // server HTTP endpoint handled here
27 router.Handle("/foo",
28 kithttp.NewServer(
29 context.Background(),
30 func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil },
31 func(*http.Request) (interface{}, error) { return struct{}{}, nil },
32 func(http.ResponseWriter, interface{}) error { return nil },
33 )).Methods("GET")
34
35 // proxy endpoint, forwards requests to http://other.service.local/base/bar
36 remoteServiceURL, _ := url.Parse("http://other.service.local/base")
37 router.Handle("/bar",
38 kithttprp.NewServer(
39 context.Background(),
40 remoteServiceURL,
41 )).Methods("GET")
42
43 http.ListenAndServe(":8080", router)
44 }
45 ```
46
47 You can also supply a set of `RequestFunc` functions to be run before proxying the request. This can be useful for adding request headers required by the backend system (e.g. API tokens).
88 )
99
1010 // RequestFunc may take information from an HTTP request and put it into a
11 // request context. In Servers, BeforeFuncs are executed prior to invoking the
12 // endpoint. In Clients, BeforeFuncs are executed after creating the request
13 // but prior to invoking the HTTP client.
11 // request context. BeforeFuncs are executed prior to invoking the
12 // endpoint.
1413 type RequestFunc func(context.Context, *http.Request) context.Context
1514
1615 // Server wraps an endpoint and implements http.Handler.
2120 errorEncoder func(w http.ResponseWriter, err error)
2221 }
2322
24 // NewServer constructs a new server, which implements http.Server and wraps
25 // the provided endpoint.
23 // NewServer constructs a new server that implements http.Server and will proxy
24 // requests to the given base URL using its scheme, host, and base path.
25 // If the target's path is "/base" and the incoming request was for "/dir",
26 // the target request will be for /base/dir.
2627 func NewServer(
2728 ctx context.Context,
28 target *url.URL,
29 baseURL *url.URL,
2930 options ...ServerOption,
3031 ) *Server {
3132 s := &Server{
3233 ctx: ctx,
33 proxy: httputil.NewSingleHostReverseProxy(target),
34 proxy: httputil.NewSingleHostReverseProxy(baseURL),
3435 }
3536 for _, option := range options {
3637 option(s)
0 package httprp_test
1
2 import (
3 "io/ioutil"
4 "net/http"
5 "net/http/httptest"
6 "net/url"
7 "testing"
8
9 "golang.org/x/net/context"
10
11 httptransport "github.com/go-kit/kit/transport/httprp"
12 )
13
14 func TestServerHappyPathSingleServer(t *testing.T) {
15 originServer := httptest.NewServer(
16 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17 w.WriteHeader(http.StatusOK)
18 w.Write([]byte("hey"))
19 }))
20 defer originServer.Close()
21 originURL, _ := url.Parse(originServer.URL)
22
23 handler := httptransport.NewServer(
24 context.Background(),
25 originURL,
26 )
27 proxyServer := httptest.NewServer(handler)
28 defer proxyServer.Close()
29
30 resp, _ := http.Get(proxyServer.URL)
31 if want, have := http.StatusOK, resp.StatusCode; want != have {
32 t.Errorf("want %d, have %d", want, have)
33 }
34
35 responseBody, _ := ioutil.ReadAll(resp.Body)
36 if want, have := "hey", string(responseBody); want != have {
37 t.Errorf("want %d, have %d", want, have)
38 }
39 }
40
41 func TestServerHappyPathSingleServerWithServerOptions(t *testing.T) {
42 originServer := httptest.NewServer(
43 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
44 if want, have := "go-kit-proxy", r.Header.Get("X-TEST-HEADER"); want != have {
45 t.Errorf("want %d, have %d", want, have)
46 }
47
48 w.WriteHeader(http.StatusOK)
49 w.Write([]byte("hey"))
50 }))
51 defer originServer.Close()
52 originURL, _ := url.Parse(originServer.URL)
53
54 handler := httptransport.NewServer(
55 context.Background(),
56 originURL,
57 httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context {
58 r.Header.Add("X-TEST-HEADER", "go-kit-proxy")
59 return ctx
60 }),
61 )
62 proxyServer := httptest.NewServer(handler)
63 defer proxyServer.Close()
64
65 resp, _ := http.Get(proxyServer.URL)
66 if want, have := http.StatusOK, resp.StatusCode; want != have {
67 t.Errorf("want %d, have %d", want, have)
68 }
69
70 responseBody, _ := ioutil.ReadAll(resp.Body)
71 if want, have := "hey", string(responseBody); want != have {
72 t.Errorf("want %d, have %d", want, have)
73 }
74 }
75
76 func TestServerOriginServerNotFoundResponse(t *testing.T) {
77 originServer := httptest.NewServer(http.NotFoundHandler())
78 defer originServer.Close()
79 originURL, _ := url.Parse(originServer.URL)
80
81 handler := httptransport.NewServer(
82 context.Background(),
83 originURL,
84 )
85 proxyServer := httptest.NewServer(handler)
86 defer proxyServer.Close()
87
88 resp, _ := http.Get(proxyServer.URL)
89 if want, have := http.StatusNotFound, resp.StatusCode; want != have {
90 t.Errorf("want %d, have %d", want, have)
91 }
92 }
93
94 func TestServerOriginServerUnreachable(t *testing.T) {
95 // create a server, then promptly shut it down
96 originServer := httptest.NewServer(
97 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
98 w.WriteHeader(http.StatusOK)
99 }))
100 originURL, _ := url.Parse(originServer.URL)
101 originServer.Close()
102
103 handler := httptransport.NewServer(
104 context.Background(),
105 originURL,
106 )
107 proxyServer := httptest.NewServer(handler)
108 defer proxyServer.Close()
109
110 resp, _ := http.Get(proxyServer.URL)
111 if want, have := http.StatusInternalServerError, resp.StatusCode; want != have {
112 t.Errorf("want %d, have %d", want, have)
113 }
114 }