为什么httputil.NewSingleHostReverseProxy会在某些www站点上导致错误?

为什么httputil.NewSingleHostReverseProxy会在某些www站点上导致错误?,http,go,reverse-proxy,Http,Go,Reverse Proxy,在以下示例中: package main import ( "fmt" "log" "net/http" "net/http/httputil" "net/url" ) func main() { p := new(Proxy) //host := "www.google.com" // WORKS AS EXPECTED host := "www.apple.com" // GIVES AN ERROR u, err

在以下示例中:

package main

import (
    "fmt"
    "log"
    "net/http"
    "net/http/httputil"
    "net/url"
)

func main() {
    p := new(Proxy)
    //host := "www.google.com" // WORKS AS EXPECTED
    host := "www.apple.com" // GIVES AN ERROR
    u, err := url.Parse(fmt.Sprintf("http://%v/", host))
    if err != nil {
        log.Printf("Error parsing URL")
    }
    p.proxy = httputil.NewSingleHostReverseProxy(u)
    http.Handle("/", p)
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

type Proxy struct {
    proxy *httputil.ReverseProxy
}

func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    p.proxy.ServeHTTP(w, r)
}
将“www.google.com”替换为“www.apple.com”会在将Chrome指向“localhost:8000”时导致此错误:

无效的URL

请求的URL“/”无效。 参考#9.a61a32b8.143823668.41733295

在www.apple.com上再做一点挖掘,我得到:

➜  ~  curl --ipv4 -v localhost:8000

< HTTP/1.1 400 Bad Request
< Content-Length: 194
< Content-Type: text/html
< Date: Thu, 30 Jul 2015 05:20:38 GMT
< Expires: Thu, 30 Jul 2015 05:20:38 GMT
< Mime-Version: 1.0
* Server AkamaiGHost is not blacklisted
< Server: AkamaiGHost
< 
<HTML><HEAD>
<TITLE>Invalid URL</TITLE>
</HEAD><BODY>
<H1>Invalid URL</H1>
The requested URL "&#47;", is invalid.<p>
Reference&#32;&#35;9&#46;65b454b8&#46;1438233638&#46;1f1b8a40
</BODY></HTML>
* Connection #0 to host localhost left intact
➜  ~  curl—ipv4-v本地主机:8000
对于www.google.com:

➜  ~  curl --ipv4 -v localhost:8000

< HTTP/1.1 302 Found
< Alternate-Protocol: 80:quic,p=0
< Cache-Control: private
< Content-Length: 219
< Content-Type: text/html; charset=UTF-8
< Date: Thu, 30 Jul 2015 05:03:16 GMT
< Location: http://www.google.com/
* Server sffe is not blacklisted
< Server: sffe
< X-Content-Type-Options: nosniff
< X-Xss-Protection: 1; mode=block
< 
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>
* Connection #0 to host localhost left intact
➜  ~  curl—ipv4-v本地主机:8000
找到
现在,当我使用“apple.com”而不是“www.apple.com”时,一切正常:

➜  ~  curl --ipv4 -v localhost:8000

< HTTP/1.1 301 Moved Permanently
< Content-Type: text/html
< Date: 
< Location: http://www.apple.com/
< Referer: 
* Server  is not blacklisted
< Server: 
< Content-Length: 0
< 
* Connection #0 to host localhost left intact
➜  ~  curl—ipv4-v本地主机:8000
发生了什么事?

这里的问题是;您连接到的某些网站不知道您请求的域(即
Host
HTTP头字段设置为
localhost:8000
,而不是
www.apple.com
)。要解决此问题,反向代理必须重写主机头

不幸的是,
httputil.NewSingleHostReverseProxy
没有提供一种简单的重写方法,因此我在下面添加的大部分内容都是从


你发布的代码甚至无法编译。请发布一个工作示例。编写自己的示例是正确的解决方案。“但是NewSingleHostReverseProxy只是一个很小的示例函数,展示了如何使用ReverseProxy。如果您想要不同的行为,您可以编写自己的控制器函数。”-BradFitz
package main

import (
    "fmt"
    "log"
    "net/http"
    "net/http/httputil"
    "net/url"
    "strings"
)

func main() {
    host := "www.apple.com"
    u, err := url.Parse(fmt.Sprintf("http://%v/", host))
    if err != nil {
        log.Printf("Error parsing URL")
    }

    targetQuery := u.RawQuery
    p := &httputil.ReverseProxy{
        Director: func(req *http.Request) {
            req.Host = host
            req.URL.Scheme = u.Scheme
            req.URL.Host = u.Host
            req.URL.Path = singleJoiningSlash(u.Path, req.URL.Path)
            if targetQuery == "" || req.URL.RawQuery == "" {
                req.URL.RawQuery = targetQuery + req.URL.RawQuery
            } else {
                req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
            }
        },
    }

    http.Handle("/", p)
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

func singleJoiningSlash(a, b string) string {
    aslash := strings.HasSuffix(a, "/")
    bslash := strings.HasPrefix(b, "/")
    switch {
    case aslash && bslash:
        return a + b[1:]
    case !aslash && !bslash:
        return a + "/" + b
    }
    return a + b
}