3

我正在尝试同时使用 gorilla mux 和 httputil.ReverseProxy,但是在尝试获取 mux.Vars 时它是空的。根据https://golang.org/src/net/http/httputil/reverseproxy.go?s=2744:2819#L93似乎 http.Request 指针是原始请求的浅拷贝,应该仍然有效.

有任何想法吗?

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)
}
4

1 回答 1

5

你在这里有两个不同的问题。

第一个,您没有使用 a 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. A 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]
}

如您所见,给定 a http.Request,我们可以将值存储在上下文中。稍后我们可以使用 sameContext和 same检索这些值http.Requestmux使用全局Context存储在路由过程中解析的变量,以便您可以使用标准http.request. 但是,因为httputil.ReverseProxy传递了实际请求的副本并按请求Context链接值,所以这个新Request的在Context.

要修复它,您可以根据以下内容实现自己ReverseProxyhttputil.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}

你甚至可以使用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)) // Now inr has proper vars
        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源代码来实现基于正则表达式的复杂解决方案。

于 2015-06-01T12:27:14.470 回答