英文:
How to set X-Forwarded-For with httputil.ReverseProxy
问题
为什么我想要这样做?
我遇到了这个问题两次。
第一次是在一个仅支持IPv6的服务器网络中使用反向代理。客户端请求通过NAT46进入。请求的源IP变成了[固定的96位前缀] + [32位客户端IPv4地址]。这意味着反向代理始终可以识别真实的客户端IP。然而,我找不到一种设置X-Forwarded-For
头部为该地址的方法。不过,我通过修改后端服务器来解决了这个问题。
这一次,我有一个将在Google App Engine上运行的反向代理。请求首先到达Google的负载均衡器,它会添加X-Forwarded-For
头部并将请求转发给我的应用程序。我想稍微修改请求,然后将其传递给一个我无法修改的后端服务器。后端服务器需要原始的客户端IP,并且可以通过X-Forwarded-For
接受它(它是经过身份验证的,请放心)。在这种情况下,我希望将X-Forwarded-For
头部从Google的负载均衡器中不经修改地传递过来。
问题
使用httputil.ReverseProxy
时,似乎没有办法将X-Forwarded-For
设置为我选择的值。如果我设置它(选项1),TCP连接的客户端地址将被追加。如果我将其设置为nil
(选项2),它将被省略,就像文档中所建议的那样,但这也不是我想要的。
package main
import (
"log"
"net/http"
"net/http/httputil"
)
func director(req *http.Request) {
// 当前运行一个只记录头部的PHP脚本
host := "bsweb02.bentonvillek12.org"
// 要拨号的主机
req.URL.Scheme = "https"
req.URL.Host = host
// 主机头部
req.Host = host
// 证明大多数头部可以正常传递
req.Header["Foo"] = []string{"Bar"}
req.Header["X-Forwarded-For"] = []string{"1.2.3.4"} // 选项1
//req.Header["X-Forwarded-For"] = nil // 选项2
}
func main() {
http.ListenAndServe(":80", &httputil.ReverseProxy{
Director: director,
})
}
选项1
array(9) {
["Content-Type"]=>
string(0) ""
["Content-Length"]=>
string(1) "0"
["Foo"]=>
string(3) "Bar"
["X-Forwarded-For"]=>
string(18) "1.2.3.4, 127.0.0.1"
["User-Agent"]=>
string(11) "curl/7.81.0"
["Host"]=>
string(26) "bsweb02.bentonvillek12.org"
["Accept-Encoding"]=>
string(4) "gzip"
["Accept"]=>
string(3) "*/*"
["Connection"]=>
string(5) "close"
}
选项2
array(8) {
["Content-Type"]=>
string(0) ""
["Content-Length"]=>
string(1) "0"
["Foo"]=>
string(3) "Bar"
["User-Agent"]=>
string(11) "curl/7.81.0"
["Host"]=>
string(26) "bsweb02.bentonvillek12.org"
["Accept-Encoding"]=>
string(4) "gzip"
["Accept"]=>
string(3) "*/*"
["Connection"]=>
string(5) "close"
}
英文:
Why would I want to do this
I have run in to this issue twice.
The first time was with a reverse proxy that lived inside an IPv6 only server network. Client requests come in through NAT46. The source-IP of the request becomes [fixed 96-bit prefix] + [32-bit client IPv4 address]. This means that the reverse proxy could always identify the real client IP. I couldn't find a way to set the X-Forwarded-For
header to that address though. I got around it by modifying the backend server.
This time I have a reverse proxy which will run on Google App Engine. Requests hit Google's load balancer first, which adds the X-Forwarded-For
header and forwards the request to my app. I want to modify the request a bit and then pass it to a backend server, which I cannot modify. The back-end needs the original client IP, and can accept it via X-Forwarded-For
(it's authenticated, don't worry). In this case I want to pass the X-Forwarded-For
header from Google's load balencer through unmodified.
The Problem
It seems like there is no way to set X-Forwarded-For
to a value that I chose when using httputil.ReverseProxy
. If I set it (option 1 below) the client address from the TCP connection will be appended. If if I set it to nil
(option 2 below), it is omitted like the documentation suggests, but that's not what I want either.
package main
import (
"log"
"net/http"
"net/http/httputil"
)
func director(req *http.Request) {
// currently running a PHP script that just records headers
host := "bsweb02.bentonvillek12.org"
// who we dial
req.URL.Scheme = "https"
req.URL.Host = host
// host header
req.Host = host
// proof that most headers can be passed through fine
req.Header["Foo"] = []string{"Bar"}
req.Header["X-Forwarded-For"] = []string{"1.2.3.4"} // option 1
//req.Header["X-Forwarded-For"] = nil // option 2
}
func main() {
http.ListenAndServe(":80", &httputil.ReverseProxy{
Director: director,
})
}
Option 1
array(9) {
["Content-Type"]=>
string(0) ""
["Content-Length"]=>
string(1) "0"
["Foo"]=>
string(3) "Bar"
["X-Forwarded-For"]=>
string(18) "1.2.3.4, 127.0.0.1"
["User-Agent"]=>
string(11) "curl/7.81.0"
["Host"]=>
string(26) "bsweb02.bentonvillek12.org"
["Accept-Encoding"]=>
string(4) "gzip"
["Accept"]=>
string(3) "*/*"
["Connection"]=>
string(5) "close"
}
Option 2
array(8) {
["Content-Type"]=>
string(0) ""
["Content-Length"]=>
string(1) "0"
["Foo"]=>
string(3) "Bar"
["User-Agent"]=>
string(11) "curl/7.81.0"
["Host"]=>
string(26) "bsweb02.bentonvillek12.org"
["Accept-Encoding"]=>
string(4) "gzip"
["Accept"]=>
string(3) "*/*"
["Connection"]=>
string(5) "close"
}
答案1
得分: 2
我相信你有两个选项。
1. 实现 http.RoundTripper
你可以实现自己的 RoundTripper
并在其中重新设置 X-Forwarded-For
。(演示)
type MyRoundTripper struct{}
func (t *MyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header["X-Forwarded-For"] = []string{"1.2.3.4"}
return http.DefaultTransport.RoundTrip(req)
}
func main() {
http.ListenAndServe(":80", &httputil.ReverseProxy{
Director: director,
Transport: &MyRoundTripper{},
})
}
当 httputil.ReverseProxy
的 Transport
字段未设置时,它会回退到 http.DefaultTransport
,所以你也可以在自定义代码之后回退到它。
2. 取消设置 req.RemoteAddr
在调用反向代理之前,你可以重置原始请求的 RemoteAddr
字段。该字段由 HTTP 服务器设置,当存在时,会触发反向代理实现中的 X-Forwarded-For
替换。(演示)
func main() {
http.ListenAndServe(":80", func(w http.ResponseWriter, r *http.Request) {
r.RemoteAddr = ""
proxy := &httputil.ReverseProxy{ Director: director }
proxy.ServeHTTP(w, r)
})
}
然而,这种行为依赖于实现细节,可能会在将来改变。我建议使用选项 1。
英文:
I believe you have two options.
1. Implement http.RoundTripper
You implement your own RoundTripper
and re-set X-Forwarded-For
in there. (demonstration)
type MyRoundTripper struct{}
func (t *MyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header["X-Forwarded-For"] = []string{"1.2.3.4"}
return http.DefaultTransport.RoundTrip(req)
}
func main() {
http.ListenAndServe(":80", &httputil.ReverseProxy{
Director: director,
Transport: &MyRoundTripper{},
})
}
When the Transport
field isn't set on httputil.ReverseProxy
it falls back to http.DefaultTransport
, so you can fall back to it too after your custom code.
2. Unset req.RemoteAddr
You reset the original request's RemoteAddr
field before invoking the reverse proxy. This field is set by the HTTP server and, when present, triggers the X-Forwarded-For
replacement in the reverse proxy implementation. (demonstration)
func main() {
http.ListenAndServe(":80", func(w http.ResponseWriter, r *http.Request) {
r.RemoteAddr = ""
proxy := &httputil.ReverseProxy{ Director: director }
proxy.ServeHTTP(w, r)
})
}
However this behavior relies on an implementation detail, which may or may not change in the future. I recommend using option 1 instead.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论