go - mux.Vars is empty when using httputil.ReverseProxy -
i trying use gorilla mux , httputil.reverseproxy together, when trying mux.vars empty. according https://golang.org/src/net/http/httputil/reverseproxy.go?s=2744:2819#l93 seems http.request pointer shallow copy of original request, should still work.
any ideas?
https://play.golang.org/p/jpjnvemifb
package main import ( "github.com/gorilla/mux" "log" "net/http" "net/http/httputil" "net/url" ) type route struct { match string base string } var routes = []route{ // proxy http://localhost:3000/api/foo/bar => https://api.bar.com/5/foo/bar route{match: "/api/{path}", base: "https://api.bar.com/5"}, route{match: "/sales/{path}", base: "https://sales.bar.com/3"}, } func newproxy(r *route) http.handler { director := func(req *http.request) { out, _ := url.parse(r.base) req.url.scheme = out.scheme req.url.host = out.host req.url.path = out.path + "/" + mux.vars(req)["path"] // mux vars empty here } return &httputil.reverseproxy{director: director} } func main() { _, route := range routes { http.handle(route.match, newproxy(&route)) } log.println("listening on port 8080") http.listenandserve(":8080", nil) }
you have 2 different problems here.
the first one, not using mux.router
, gorilla/mux
has not opportunity pre-process request. in other words, requests going directly http
package reverse proxies. issue has easy fix:
r := mux.newrouter() _, route := range routes { r.handle(route.match, newproxy(&route)) } http.handle("/", r)
the second problem more tricky first one. issue related how mux
package implemented. if mux.vars()
implementation, see uses called context
. context
, described in official documentation, stores values shared during request lifetime. simplified context implementation be:
type context map[*http.request]interface{} func (c context) set(req *http.request, v interface{}) { c[req] = v } func (c context) get(req *http.request) interface{} { return c[req] }
as see, given http.request
, can store values in context. later can retrieve these values using same context
, same http.request
. mux
uses global context
store vars parsed in routing process can use standard http.request
. but, because httputil.reverseproxy
passes copy of actual request , context
links values request, new request
has no values in context
.
to fix it, can implement own reverseproxy
based on httputil.reverseproxy
:
type myreverseproxy struct { httputil.reverseproxy director func(inr, outr *http.request) } func (p *myreverseproxy) servehttp(rw http.responsewriter, inr *http.request) { p.reverseproxy.director = func(outr *http.request) { p.director(inr, outr) } p.reverseproxy.servehttp(rw, inr) } func newproxy(r *route) http.handler { director := func(inr, outr *http.request) { out, _ := url.parse(r.base) outr.url.scheme = out.scheme outr.url.host = out.host outr.url.path = out.path + "/" + mux.vars(inr)["path"] log.printf("in vars: %#v\n", mux.vars(inr)) // inr has proper vars log.printf("out vars: %#v\n", mux.vars(outr)) } return &myreverseproxy{director: director}
you can use context
, keep director
declaration:
type myreverseproxy struct { httputil.reverseproxy director func(req *http.request) } func (p *myreverseproxy) servehttp(rw http.responsewriter, inr *http.request) { p.reverseproxy.director = func(outr *http.request) { context.set(outr, "in_req", inr) p.director(outr) } p.reverseproxy.servehttp(rw, inr) } func newproxy(r *route) http.handler { director := func(outr *http.request) { out, _ := url.parse(r.base) inr := context.get(outr, "in_req").(*http.request) outr.url.scheme = out.scheme outr.url.host = out.host outr.url.path = out.path + "/" + mux.vars(inr)["path"] log.printf("in vars: %#v\n", mux.vars(inr)) // inr has proper vars log.printf("out vars: %#v\n", mux.vars(outr)) } return &myreverseproxy{director: director} }
both implementations seem tricky me. have change httputil.reverseproxy
's director
in every call. so, accept mux
not choice here, , instead use simpler solution:
var routes = []route{ route{match: "/api/", base: "https://api.bar.com/5"}, route{match: "/sales/", base: "https://sales.bar.com/3"}, } func newproxy(r *route) http.handler { director := func(req *http.request) { out, _ := url.parse(r.base) req.url.scheme = out.scheme req.url.host = out.host req.url.path = out.path + "/" + strings.trimprefix(req.url.path, r.match) } return &httputil.reverseproxy{director: director} }
you can read mux
source code implement complex solution based on regular expressions.
Comments
Post a Comment