带有 Apache2 SNI/Hostname 错误的 Golang ReverseProxy
Golang ReverseProxy with Apache2 SNI/Hostname error
我正在写我自己的 ReverseProxy Go.The ReverseProxy 应该连接我的 go-web 服务器和我的 apache2 网络服务器。但是,当我 运行 我的反向代理在另一个 IP 地址上然后是我的 Apache2 网络服务器时,当反向代理将请求发送到 apache 时,我的 apache 日志文件中出现以下错误。
"Hosname xxxx provided via sni and hostname xxxx2 provided via http are different"
我的反向代理和 apache-webserver 运行在 https 上运行。
这里是一些代码:
func (p *Proxy) directorApache(req *http.Request) {
mainServer := fmt.Sprintf("%s:%d", Config.HostMain, Config.PortMain)
req.URL.Scheme = "https"
req.URL.Host = mainServer
}
func (p *Proxy) directorGo(req *http.Request) {
goServer := fmt.Sprintf("%s:%d", Config.GoHost, Config.GoPort)
req.URL.Scheme = "http"
req.URL.Host = goServer
}
func (p *Proxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
fmt.Println(req.URL.Path)
if p.isGoRequest(req) {
fmt.Println("GO")
p.goProxy.ServeHTTP(rw, req)
return
}
p.httpProxy.ServeHTTP(rw, req)
}
func main() {
var configPath = flag.String("conf", "./configReverse.json", "Path to the Json config file.")
flag.Parse()
proxy := New(*configPath)
cert, err := tls.LoadX509KeyPair(Config.PathCert, Config.PathPrivateKey)
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
config := tls.Config{InsecureSkipVerify: true, Certificates: []tls.Certificate{cert}}
listener, err := net.Listen("tcp",
net.JoinHostPort(proxy.Host, strconv.Itoa(proxy.Port)))
if err != nil {
log.Fatalf("server: listen: %s", err)
}
log.Printf("server: listening on %s")
proxy.listener = tls.NewListener(listener, &config)
serverHTTPS := &http.Server{
Handler: proxy.mux,
TLSConfig: &config,
}
if err := serverHTTPS.Serve(proxy.listener); err != nil {
log.Fatal("SERVER ERROR:", err)
}
}
也许有人对这个问题有想法。
简短示例
假设您正在向 https://your-proxy.local
发起 HTTP 请求。您的请求处理程序采用 http.Request
结构并将其 URL
字段重写为 https://your-apache-backend.local
.
您没有考虑到的是,原始 HTTP 请求还包含一个 Host
header (Host: your-proxy.local
)。当将相同的请求传递给 http://your-apache-backend.local
时,该请求中的 Host
header 仍然显示 Host: your-proxy.local
。这就是 Apache 所抱怨的。
说明
当您使用带服务器名称指示 (SNI) 的 TLS 时,请求主机名不仅会用于 DNS 解析,还会 select 用于建立 TLS 的 SSL 证书联系。另一方面,HTTP 1.1 Host
header 被 Apache 用来区分多个虚拟主机。两个名称 必须匹配 。 Apache HTTPD wiki:
中也提到了这个问题
SNI/Request hostname mismatch, or SNI provides hostname and request doesn't.
This is a browser bug. Apache will reject the request with a 400-type error.
解决方案
也重写Host
header。如果你想保留原来的 Host
header,你可以将它存储在 X-Forwarded-Host
header 中(即 non-standard header,但它广泛用于反向代理):
func (p *Proxy) directorApache(req *http.Request) {
mainServer := fmt.Sprintf("%s:%d", Config.HostMain, Config.PortMain)
req.URL.Scheme = "https"
req.URL.Host = mainServer
req.Header.Set("X-Forwarded-Host", req.Header().Get("Host"))
req.Host = mainServer
}
我正在写我自己的 ReverseProxy Go.The ReverseProxy 应该连接我的 go-web 服务器和我的 apache2 网络服务器。但是,当我 运行 我的反向代理在另一个 IP 地址上然后是我的 Apache2 网络服务器时,当反向代理将请求发送到 apache 时,我的 apache 日志文件中出现以下错误。
"Hosname xxxx provided via sni and hostname xxxx2 provided via http are different"
我的反向代理和 apache-webserver 运行在 https 上运行。
这里是一些代码:
func (p *Proxy) directorApache(req *http.Request) {
mainServer := fmt.Sprintf("%s:%d", Config.HostMain, Config.PortMain)
req.URL.Scheme = "https"
req.URL.Host = mainServer
}
func (p *Proxy) directorGo(req *http.Request) {
goServer := fmt.Sprintf("%s:%d", Config.GoHost, Config.GoPort)
req.URL.Scheme = "http"
req.URL.Host = goServer
}
func (p *Proxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
fmt.Println(req.URL.Path)
if p.isGoRequest(req) {
fmt.Println("GO")
p.goProxy.ServeHTTP(rw, req)
return
}
p.httpProxy.ServeHTTP(rw, req)
}
func main() {
var configPath = flag.String("conf", "./configReverse.json", "Path to the Json config file.")
flag.Parse()
proxy := New(*configPath)
cert, err := tls.LoadX509KeyPair(Config.PathCert, Config.PathPrivateKey)
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
config := tls.Config{InsecureSkipVerify: true, Certificates: []tls.Certificate{cert}}
listener, err := net.Listen("tcp",
net.JoinHostPort(proxy.Host, strconv.Itoa(proxy.Port)))
if err != nil {
log.Fatalf("server: listen: %s", err)
}
log.Printf("server: listening on %s")
proxy.listener = tls.NewListener(listener, &config)
serverHTTPS := &http.Server{
Handler: proxy.mux,
TLSConfig: &config,
}
if err := serverHTTPS.Serve(proxy.listener); err != nil {
log.Fatal("SERVER ERROR:", err)
}
}
也许有人对这个问题有想法。
简短示例
假设您正在向 https://your-proxy.local
发起 HTTP 请求。您的请求处理程序采用 http.Request
结构并将其 URL
字段重写为 https://your-apache-backend.local
.
您没有考虑到的是,原始 HTTP 请求还包含一个 Host
header (Host: your-proxy.local
)。当将相同的请求传递给 http://your-apache-backend.local
时,该请求中的 Host
header 仍然显示 Host: your-proxy.local
。这就是 Apache 所抱怨的。
说明
当您使用带服务器名称指示 (SNI) 的 TLS 时,请求主机名不仅会用于 DNS 解析,还会 select 用于建立 TLS 的 SSL 证书联系。另一方面,HTTP 1.1 Host
header 被 Apache 用来区分多个虚拟主机。两个名称 必须匹配 。 Apache HTTPD wiki:
SNI/Request hostname mismatch, or SNI provides hostname and request doesn't.
This is a browser bug. Apache will reject the request with a 400-type error.
解决方案
也重写Host
header。如果你想保留原来的 Host
header,你可以将它存储在 X-Forwarded-Host
header 中(即 non-standard header,但它广泛用于反向代理):
func (p *Proxy) directorApache(req *http.Request) {
mainServer := fmt.Sprintf("%s:%d", Config.HostMain, Config.PortMain)
req.URL.Scheme = "https"
req.URL.Host = mainServer
req.Header.Set("X-Forwarded-Host", req.Header().Get("Host"))
req.Host = mainServer
}