Codebase list golang-github-go-kit-kit / 95ff200
Merge pull request #214 from skidder/httprp-transport HTTP Reverse Proxy Transport Peter Bourgon 8 years ago
3 changed file(s) with 230 addition(s) and 0 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 associate 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).
0 package httprp
1
2 import (
3 "net/http"
4 "net/http/httputil"
5 "net/url"
6
7 "golang.org/x/net/context"
8 )
9
10 // RequestFunc may take information from an HTTP request and put it into a
11 // request context. BeforeFuncs are executed prior to invoking the
12 // endpoint.
13 type RequestFunc func(context.Context, *http.Request) context.Context
14
15 // Server is a proxying request handler.
16 type Server struct {
17 ctx context.Context
18 proxy http.Handler
19 before []RequestFunc
20 errorEncoder func(w http.ResponseWriter, err error)
21 }
22
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.
27 func NewServer(
28 ctx context.Context,
29 baseURL *url.URL,
30 options ...ServerOption,
31 ) *Server {
32 s := &Server{
33 ctx: ctx,
34 proxy: httputil.NewSingleHostReverseProxy(baseURL),
35 }
36 for _, option := range options {
37 option(s)
38 }
39 return s
40 }
41
42 // ServerOption sets an optional parameter for servers.
43 type ServerOption func(*Server)
44
45 // ServerBefore functions are executed on the HTTP request object before the
46 // request is decoded.
47 func ServerBefore(before ...RequestFunc) ServerOption {
48 return func(s *Server) { s.before = before }
49 }
50
51 // ServeHTTP implements http.Handler.
52 func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
53 ctx, cancel := context.WithCancel(s.ctx)
54 defer cancel()
55
56 for _, f := range s.before {
57 ctx = f(ctx, r)
58 }
59
60 s.proxy.ServeHTTP(w, r)
61 }
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 const (
43 headerKey = "X-TEST-HEADER"
44 headerVal = "go-kit-proxy"
45 )
46
47 originServer := httptest.NewServer(
48 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
49 if want, have := headerVal, r.Header.Get(headerKey); want != have {
50 t.Errorf("want %d, have %d", want, have)
51 }
52
53 w.WriteHeader(http.StatusOK)
54 w.Write([]byte("hey"))
55 }))
56 defer originServer.Close()
57 originURL, _ := url.Parse(originServer.URL)
58
59 handler := httptransport.NewServer(
60 context.Background(),
61 originURL,
62 httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context {
63 r.Header.Add(headerKey, headerVal)
64 return ctx
65 }),
66 )
67 proxyServer := httptest.NewServer(handler)
68 defer proxyServer.Close()
69
70 resp, _ := http.Get(proxyServer.URL)
71 if want, have := http.StatusOK, resp.StatusCode; want != have {
72 t.Errorf("want %d, have %d", want, have)
73 }
74
75 responseBody, _ := ioutil.ReadAll(resp.Body)
76 if want, have := "hey", string(responseBody); want != have {
77 t.Errorf("want %d, have %d", want, have)
78 }
79 }
80
81 func TestServerOriginServerNotFoundResponse(t *testing.T) {
82 originServer := httptest.NewServer(http.NotFoundHandler())
83 defer originServer.Close()
84 originURL, _ := url.Parse(originServer.URL)
85
86 handler := httptransport.NewServer(
87 context.Background(),
88 originURL,
89 )
90 proxyServer := httptest.NewServer(handler)
91 defer proxyServer.Close()
92
93 resp, _ := http.Get(proxyServer.URL)
94 if want, have := http.StatusNotFound, resp.StatusCode; want != have {
95 t.Errorf("want %d, have %d", want, have)
96 }
97 }
98
99 func TestServerOriginServerUnreachable(t *testing.T) {
100 // create a server, then promptly shut it down
101 originServer := httptest.NewServer(
102 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
103 w.WriteHeader(http.StatusOK)
104 }))
105 originURL, _ := url.Parse(originServer.URL)
106 originServer.Close()
107
108 handler := httptransport.NewServer(
109 context.Background(),
110 originURL,
111 )
112 proxyServer := httptest.NewServer(handler)
113 defer proxyServer.Close()
114
115 resp, _ := http.Get(proxyServer.URL)
116 if want, have := http.StatusInternalServerError, resp.StatusCode; want != have {
117 t.Errorf("want %d, have %d", want, have)
118 }
119 }