dsjpik057730 2015-06-01 04:10
浏览 78
已采纳

使用httputil.ReverseProxy时mux.Vars为空

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条回答 默认 最新

  • dongyao2129 2015-06-01 12:27
    关注

    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
    ", mux.Vars(inr)) // Now inr has proper vars
            log.Printf("OUT VARS: %#v
    ", 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
    ", mux.Vars(inr)) // Now inr has proper vars
            log.Printf("OUT VARS: %#v
    ", 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.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥100 set_link_state
  • ¥15 虚幻5 UE美术毛发渲染
  • ¥15 CVRP 图论 物流运输优化
  • ¥15 Tableau online 嵌入ppt失败
  • ¥100 支付宝网页转账系统不识别账号
  • ¥15 基于单片机的靶位控制系统
  • ¥15 真我手机蓝牙传输进度消息被关闭了,怎么打开?(关键词-消息通知)
  • ¥15 装 pytorch 的时候出了好多问题,遇到这种情况怎么处理?
  • ¥20 IOS游览器某宝手机网页版自动立即购买JavaScript脚本
  • ¥15 手机接入宽带网线,如何释放宽带全部速度