Golang反向代理到Nginx后面的应用程序

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

Golang reverse proxy to app behind nginx

问题

我正在使用Golang编写一个简单的反向代理。以下是代码:

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

当我尝试将一个请求代理到Nginx后面的REST API时,Nginx总是返回404错误。然而,如果我直接访问REST API,它会正确返回结果。这是我的Nginx配置:

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;
    }
}

请问我该如何调试这个问题?是由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 &lt; len(target_urls); i++ {
		target, err := url.Parse(target_urls[i])
		if err != nil {
			fmt.Errorf(&quot;Error parsing url&quot;)
			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(&quot;X-GoProxy&quot;, &quot;GoProxy&quot;)
			if target.RawQuery == &quot;&quot; || req.URL.RawQuery == &quot;&quot; {
				req.URL.RawQuery = target.RawQuery + req.URL.RawQuery
			} else {
				req.URL.RawQuery = target.RawQuery + &quot;&amp;&quot; + req.URL.RawQuery
			}
			log.Print(req.URL)
		}
		proxy := &amp;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?

答案1

得分: 2

从https://golang.org/pkg/net/http/httputil/#ReverseProxy中可以看到:

// Director必须是一个函数,它将请求修改为要使用Transport发送的新请求。然后将其响应无修改地复制回原始客户端。
// Director在返回后不能访问提供的请求。
Director func(*http.Request)

这是用于构建&amp;httputil.ReverseProxy{Director: director}的函数。

但是你的director从不修改原始req指向的http.Request。它使用req = r重新分配指针。而是修改了一个无关的http.Request

英文:

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 &amp;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.

答案2

得分: 0

问题出在请求体上。如果你查看Gin如何实现获取参数的方式,你会发现它打开、读取并关闭请求体。所以当你转发请求时,请求体是空的。

英文:

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.

答案3

得分: 0

我刚遇到了同样的问题。我通过设置Host来解决了这个问题。nginx使用host(server_name)来决定在端口80上提供哪个服务器。

req.Host = target.Host
// 或者
req.Host = ""

它可以设置为空字符串,因为url的主机刚刚设置为req.URL.Host = target.Host
https://golang.org/pkg/net/http/#Request

    // 对于客户端请求,Host可选地覆盖要发送的Host标头。如果为空,则Request.Write方法使用URL.Host的值。Host可能包含国际域名。
    Host string

顺便说一下,你可以在gin处理程序之外创建代理,并在gin处理程序中调用proxy.ServeHTTP(c.Writer, c.Request)。最终的结果将如下所示:

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("解析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)
    }
}
英文:

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 = &quot;&quot;

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 &lt; len(target_urls); i++ {
        target, err := url.Parse(target_urls[i])
        if err != nil {
            fmt.Errorf(&quot;Error parsing url&quot;)
            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 = &quot;&quot;
        req.Header.Set(&quot;X-GoProxy&quot;, &quot;GoProxy&quot;)
        if target.RawQuery == &quot;&quot; || req.URL.RawQuery == &quot;&quot; {
            req.URL.RawQuery = target.RawQuery + req.URL.RawQuery
        } else {
            req.URL.RawQuery = target.RawQuery + &quot;&amp;&quot; + req.URL.RawQuery
        }
        log.Print(req.URL)
    }
    proxy := &amp;httputil.ReverseProxy{Director: director}
    return func(c *gin.Context) {
        proxy.ServeHTTP(c.Writer, c.Request)
    }
}

huangapple
  • 本文由 发表于 2017年2月26日 16:46:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/42466491.html
匿名

发表评论

匿名网友

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

确定