golang ReverseProxy无法正常工作

时间:2014-04-19 00:55:31

标签: proxy go

我不知道为什么这个反向代理不起作用。我已经看过几个例子,我发现它没有任何问题。

package main

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

func report(r *http.Request){
  log.Print("URL: " + r.URL.Path)
  log.Print("Scheme: " + r.URL.Scheme)
  log.Print("Host: " + r.URL.Host)
  //r.URL.Scheme = "http"
  //r.URL.Host = "stackoverflow.com"

  //r.Header.Set("Host", "stackoverflow.com")
  //log.Print("Header Host: " + r.Header.Get("Host"))
}

func main() {
  proxy := httputil.NewSingleHostReverseProxy( &url.URL{Scheme:"http",Host:"myrealserver.com"})
    proxy.Director = report
    // http.Handle("/", proxy)
    error := http.ListenAndServe("mylocalhost.com:8080", proxy)
    if(error != nil) {
        log.Fatal(error)
    }
}

记录:

2014/04/18 21:32:50 URL: /arg/es
2014/04/18 21:32:50 Scheme:
2014/04/18 21:32:50 Host:
2014/04/18 21:32:50 http: proxy error: unsupported protocol scheme ""

2014/04/18 21:32:51 URL: /favicon.ico
2014/04/18 21:32:51 Scheme:
2014/04/18 21:32:51 Host:
2014/04/18 21:32:51 http: proxy error: unsupported protocol scheme ""

如果我取消注释重新定义Schema的行,则错误消息变为:

2014/04/18 21:38:05 http: proxy error: http: no Host in request URL

如果我取消注释重新定义主机的行,那么目标服务器将变为stackoverflow.com(我的意思是,它永远不会使用" myrealserver.com")。

如果我要求mylocalhost.com:8080/somepath(甚至/),那么无论stackoverflow.com/somepath是否存在,我都会从Stackoverflow获得404。它说:

Couldn't find mylocalhost.com:8080
The Q&A site mylocalhost.com:8080 doesn't seem to exist... yet

它不会自动翻译主机标头。

如果那时我取消注释设置的行(以及打印的另一行)Header" Host"。然后我就可以阅读" stackoverflow.com"在日志中,但我仍然得到我正在尝试访问的相同的404页面报告" mylocalhost.com"。

我正在使用go1.2.1 linux/amd64

我应该如何让程序作为代理工作?

2 个答案:

答案 0 :(得分:10)

感谢Golang-nuts的Alex,我现在有了答案。

这就是Alex所说的:

  

只需要在Director中设置http.Request.Host [和scheme]即可   工作:http://play.golang.org/p/I17ZSM6LQb

     

如果您阅读了SingleHostReverseProxy的来源   (http://golang.org/src/pkg/net/http/httputil/reverseproxy.go#L61),它   设置自己的主管,你要重写。所以你需要   重新实现它已经做的以及额外的主机更改。

无论如何,这并没有解决问题的de Header部分:目标服务器仍在接收" localhost:8080"作为HTTP主机名,所以我没有ReverseProxy包,只使用http和RoundTripper,以及复制所有标题的辅助函数:

package main

import (
    "flag"
    "fmt"
    "os"
    "log"
    "net/http"
    "io/ioutil"
)

var target *string

func main() {
  target = flag.String("target", "http://stackoverflow.com", "target URL for reverse proxy")
  flag.Parse()
  http.HandleFunc("/", report)
  log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}

func report(w http.ResponseWriter, r *http.Request){

  uri := *target+r.RequestURI

  fmt.Println(r.Method + ": " + uri)

  if r.Method == "POST" {
    body, err := ioutil.ReadAll(r.Body)
    fatal(err)
    fmt.Printf("Body: %v\n", string(body));
  }

  rr, err := http.NewRequest(r.Method, uri, r.Body)
  fatal(err)
  copyHeader(r.Header, &rr.Header)

  // Create a client and query the target
  var transport http.Transport
  resp, err := transport.RoundTrip(rr)
  fatal(err)

  fmt.Printf("Resp-Headers: %v\n", resp.Header);

  defer resp.Body.Close()
  body, err := ioutil.ReadAll(resp.Body)
  fatal(err)

  dH := w.Header()
  copyHeader(resp.Header, &dH)
  dH.Add("Requested-Host", rr.Host)

  w.Write(body)
}

func fatal(err error) {
  if err != nil {
    log.Fatal(err)
    os.Exit(1)
  }
}

func copyHeader(source http.Header, dest *http.Header){
  for n, v := range source {
      for _, vv := range v {
          dest.Add(n, vv)
      }
  }
}

现在,我能够看到StackOverflow或其他任何网站应该如何。
不过,我仍然在进行POST调用,所以这是一项正在进行中的工作。

答案 1 :(得分:1)

派对有点晚了,但是ReverseProxy并没有被打破,只是有点混乱,因为它没有达到预期的效果(至少,我希望它能像你一样工作做了,所以这让我们两个人。)

来自the docs

// For server requests Host specifies the host on which the
// URL is sought. Per RFC 2616, this is either the value of
// the "Host" header or the host name given in the URL itself.
// It may be of the form "host:port". For international domain
// names, Host may be in Punycode or Unicode form. Use
// golang.org/x/net/idna to convert it to either format if
// needed.
//
// 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

由于引擎ReverseProxy正在使用此Request发出客户端请求(在ReverseProxy.Director可选地修改它之后),如果Host已设置,它将覆盖Host标题。这将始终设置,因为doc注释的第一部分指出“对于服务器请求,主机指定寻求URL的主机”。

因此,除了Sebastián的回答,您还需要设置req.Host。例如,代理example.com

proxy := ReverseProxy{
    Director: func(req *http.Request) {
        req.URL.Scheme = "http"
        req.URL.Host = "example.com"
        req.Host = "example.com"
    }
}

或者,您可以将req.Host设置为"",并使用req.URL.Host的值。

我通过阅读:https://github.com/golang/go/issues/14413

来解决这个问题