Golang反向代理到Nginx之后的应用

I am writing a simple reverse proxy with Golang. The code is listed below:

func NewMultiHostProxy(target_urls []string) gin.HandlerFunc {
    var urls []*url.URL
    for i := 0; i < len(target_urls); i++ {
        target, err := url.Parse(target_urls[i])
        if err != nil {
            fmt.Errorf("Error parsing url")
            return nil
        }
        urls = append(urls, target)
    }
    return func(c *gin.Context) {
        director := func(req *http.Request) {
            target := urls[rand.Int()%len(urls)]
            r := c.Request
            req = r
            req.URL.Scheme = target.Scheme
            req.URL.Host = target.Host
            req.URL.Path = target.Path
            req.Header.Set("X-GoProxy", "GoProxy")
            if target.RawQuery == "" || req.URL.RawQuery == "" {
                req.URL.RawQuery = target.RawQuery + req.URL.RawQuery
            } else {
                req.URL.RawQuery = target.RawQuery + "&" + req.URL.RawQuery
            }
            log.Print(req.URL)
        }
        proxy := &httputil.ReverseProxy{Director: director}
        proxy.ServeHTTP(c.Writer, c.Request)
    }
}

When I am trying to proxy one request to a REST api behind Nginx, Nginx always returns 404. However, if i directly access the REST api, it returns the result correctly. Here's my Nginx config:

server {
    listen       80;
    server_name  myservername;

    location /api {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

May I know how to debug this problem? Is it caused by Nginx configuration?

From https://golang.org/pkg/net/http/httputil/#ReverseProxy:

    // Director must be a function which modifies
    // the request into a new request to be sent
    // using Transport. Its response is then copied
    // back to the original client unmodified.
    // Director must not access the provided Request
    // after returning.
    Director func(*http.Request)

That is the function used to construct ReverseProxy in &httputil.ReverseProxy{Director: director}

But your director never modifies the http.Request that original req points to. It reassigns the pointer with req = r. An irrelevant http.Request is modified instead.

The issue is with request body. If you look into how Gin implement get parameter, you will find that it open, read it and close it. So when you forward the request, the request body is empty.

I just ran into this same issue. I solved it by setting the Host. nginx uses the host(server_name) to decide which server on port 80 to serve.

req.Host = target.Host
// or
req.Host = ""

It can be set to an empty string because the url host was just set req.URL.Host = target.Host https://golang.org/pkg/net/http/#Request

    // For client requests Host optionally overrides the Host
    // header to send. If empty, the Request.Write method uses
    // the value of URL.Host. Host may contain an international
    // domain name.
    Host string

As a side note, you can create the proxy outside of the gin handler and call proxy.ServeHTTP(c.Writer, c.Request) in the gin handler. The final result would look like this:

func NewMultiHostProxy(target_urls []string) gin.HandlerFunc {
    var urls []*url.URL
    for i := 0; i < len(target_urls); i++ {
        target, err := url.Parse(target_urls[i])
        if err != nil {
            fmt.Errorf("Error parsing url")
            return nil
        }
        urls = append(urls, target)
    }
    director := func(req *http.Request) {
        target := urls[rand.Int()%len(urls)]
        req.URL.Scheme = target.Scheme
        req.URL.Host = target.Host
        req.URL.Path = target.Path
        req.Host = ""
        req.Header.Set("X-GoProxy", "GoProxy")
        if target.RawQuery == "" || req.URL.RawQuery == "" {
            req.URL.RawQuery = target.RawQuery + req.URL.RawQuery
        } else {
            req.URL.RawQuery = target.RawQuery + "&" + req.URL.RawQuery
        }
        log.Print(req.URL)
    }
    proxy := &httputil.ReverseProxy{Director: director}
    return func(c *gin.Context) {
        proxy.ServeHTTP(c.Writer, c.Request)
    }
}