英文:
mux.Vars is empty when using httputil.ReverseProxy
问题
我正在尝试同时使用gorilla mux和httputil.ReverseProxy,但是当尝试获取mux.Vars时它是空的。根据https://golang.org/src/net/http/httputil/reverseproxy.go?s=2744:2819#L93,似乎http.Request指针是原始请求的浅拷贝,这应该仍然有效。
有什么想法吗?
以下是代码的翻译:
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 are empty here
}
return &httputil.ReverseProxy{Director: director}
}
func main() {
for _, route := range routes {
http.Handle(route.match, NewProxy(&route))
}
log.Println("Listening on port 8080")
http.ListenAndServe(":8080", nil)
}
希望对你有帮助!
英文:
I am trying to use gorilla mux and httputil.ReverseProxy together, but when trying to get the mux.Vars it is empty. According to https://golang.org/src/net/http/httputil/reverseproxy.go?s=2744:2819#L93 it seems like the http.Request pointer is a shallow copy of the original request, which 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 are empty here
}
return &httputil.ReverseProxy{Director: director}
}
func main() {
for _, route := range routes {
http.Handle(route.match, NewProxy(&route))
}
log.Println("Listening on port 8080")
http.ListenAndServe(":8080", nil)
}
答案1
得分: 5
你有两个不同的问题。
第一个问题是你没有使用mux.Router
,所以gorilla/mux
没有机会预处理你的请求。换句话说,请求直接从http
包传递到你的反向代理。这个问题很容易解决:
r := mux.NewRouter()
for _, route := range routes {
r.Handle(route.match, NewProxy(&route))
}
http.Handle("/", r)
第二个问题比第一个问题更棘手。这个问题与mux
包的实现方式有关。如果你查看mux.Vars()
的实现,你会发现它使用了一个叫做Context
的东西。Context
在官方文档中被描述为在请求生命周期中存储共享值的东西。一个简化的Context
实现如下:
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]
}
如你所见,给定一个http.Request
,我们可以在上下文中存储值。稍后我们可以使用相同的Context
和相同的http.Request
检索这些值。mux
使用一个全局的Context
来存储路由过程中解析的变量,以便你可以使用标准的http.Request
。但是,由于httputil.ReverseProxy
传递的是实际请求的副本,而Context
通过请求链接值,所以这个新的Request
在Context
中没有值。
为了解决这个问题,你可以基于httputil.ReverseProxy
实现自己的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有正确的变量
log.Printf("OUT VARS: %#v\n", mux.Vars(outr))
}
return &MyReverseProxy{Director: director}
}
你甚至可以使用context
并保留Director
声明:
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有正确的变量
log.Printf("OUT VARS: %#v\n", mux.Vars(outr))
}
return &MyReverseProxy{Director: director}
}
对我来说,这两种实现都有些棘手。它们必须在每次调用中更改httputil.ReverseProxy
的Director
。所以,我可能会接受mux
在这里不是一个好选择的事实,而是使用一些更简单的解决方案:
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}
}
你可以阅读mux
源代码来基于正则表达式实现一个复杂的解决方案。
英文:
You have two different problems here.
The first one, you are not using a mux.Router
, so gorilla/mux
has not the opportunity to pre-process your request. In other words, the requests are going directly from http
package to your reverse proxies. This issue has an easy fix:
r := mux.NewRouter()
for _, route := range routes {
r.Handle(route.match, NewProxy(&route))
}
http.Handle("/", r)
The second problem is more tricky than the first one. This issue is related to how is mux
package implemented. If you look mux.Vars()
implementation, you will see that it uses something called Context
. A Context
, as described in the official documentation, is something that stores values shared during a request lifetime. A simplified Context implementation will 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 you see, given a http.Request
, we can store values in a context. Later we can retrieve these values using the same Context
and the same http.Request
. mux
uses a global Context
to store the vars parsed in routing process so that you can use the standard http.request
. But, because httputil.ReverseProxy
passes a copy of the actual request and Context
links values by request, this new Request
has no values in the Context
.
To fix it, you can implement your 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)) // Now inr has proper vars
log.Printf("OUT VARS: %#v\n", mux.Vars(outr))
}
return &MyReverseProxy{Director: director}
You can even use context
and 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)) // Now inr has proper vars
log.Printf("OUT VARS: %#v\n", mux.Vars(outr))
}
return &MyReverseProxy{Director: director}
}
Both implementations seem tricky to me. They have to change httputil.ReverseProxy
's Director
in every call. So, I probably accept that mux
is not a good choice here, and instead I will use some 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 to implement a complex solution based on regular expressions.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论