当使用httputil.ReverseProxy时,mux.Vars为空。

huangapple go评论89阅读模式
英文:

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通过请求链接值,所以这个新的RequestContext中没有值。

为了解决这个问题,你可以基于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.ReverseProxyDirector。所以,我可能会接受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.

huangapple
  • 本文由 发表于 2015年6月1日 12:10:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/30565518.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定