Mux.Vars is empty when using httputil.ReverseProxy

I'm trying to use gorilla mux and httputil.ReverseProxy together, but when trying to get mux.Vars it is empty. According to https://golang.org/src/net/http/httputil/reverseproxy.go?s=2744:2819#L93 it seems that the http.Request pointer is a shallow copy of the original request, which should 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)
}

      

+3


source to share


1 answer


You have two different problems here.

First, you are not using mux.Router

, so gorilla/mux

it has no way to pre-process your request. In other words, requests go directly from the package http

to your reverse proxies. This problem has an easy solution:

r := mux.NewRouter()
for _, route := range routes {
    r.Handle(route.match, NewProxy(&route))
}
http.Handle("/", r)

      

The second problem is more complex than the first. This issue has to do with how the package is implemented mux

. If you look at the implementation mux.Vars()

, you can see that it uses something called Context

. A Context

, as described in the official documentation, is what stores values ​​shared over the lifetime of the request. A simplified implementation of the context would 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 can see, given http.Request

we can store values ​​in context. We can later retrieve these values ​​using the same Context

ones http.Request

. mux

uses the global one Context

to store the vars parsed in the routing process so you can use the standard one http.Request

. But since it httputil.ReverseProxy

passes a copy of the actual request and Context

the reference values ​​as requested, this new Request

one has no values ​​in Context

.

To fix this, 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 save the declaration 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}
}

      

Both implementations seem complex to me. They have to change httputil.ReverseProxy

Director

in every challenge. So I probably agree that mux

this is not the best choice, and instead I use a 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 the mux

source code
to implement a complex regex based solution.

+5


source







All Articles