golang的ReverseProxy不起作用。

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

golang ReverseProxy not working

问题

我不知道为什么这个反向代理不起作用。我看过几个示例,但没有找到任何问题。

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

如果取消注释重新定义host的那一行,目标服务器变为stackoverflow.com(也就是说,它从不使用"myrealserver.com")。

如果我请求mylocalhost.com:8080/somepath(甚至是/),我会从Stackoverflow得到一个404错误,无论stackoverflow.com/somepath是否存在。它显示:

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

它不会自动转换Host头。

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

我使用的是go1.2.1 linux/amd64

请问我应该如何使程序作为代理工作?

英文:

I don't know why this reverse proxy is not working. I've seen several examples and I can't find anything wrong with it.

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

It logs:

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 ""

If I uncomment the line that redefines the Schema the error message becomes:

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

If I uncomment the line that redefines the host also, then the target server becomes stackoverflow.com (I mean, it never uses "myrealserver.com").

If I ask for mylocalhost.com:8080/somepath (or even /) then I get a 404 from Stackoverflow, no matter if stackoverflow.com/somepath exists or not. It says:

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

It does not translate the Host header automatically.

If then I uncomment the line that sets (and the other one that prints) the Header "Host". Then I can read "stackoverflow.com" in the log, but I still get the same 404 page reporting that I am trying to access "mylocalhost.com".

I'm using go1.2.1 linux/amd64

How is it that I am supposed to make the program work as a proxy?

答案1

得分: 13

感谢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),它
> 设置了它自己的Director,而你正在覆盖它。所以你需要
> 重新实现它已经做的事情,再加上额外的Host更改。

无论如何,这并没有解决问题的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请求,所以这还在进行中。

英文:

Thanks to Alex from Golang-nuts, I have the answer now.

This is what Alex said:

> Just need to set http.Request.Host [and scheme] in the Director to get this
> working: http://play.golang.org/p/I17ZSM6LQb
>
> If you read the source for SingleHostReverseProxy
> (http://golang.org/src/pkg/net/http/httputil/reverseproxy.go#L61), it
> sets its own Director which you are overriding. So you need to
> reimplement what it already does plus the extra Host change.

Anyway, that didn't solve de Header part of the problem: the target server was still receiving "localhost:8080" as the HTTP Host name, so I did it without the ReverseProxy package, just with http and a RoundTripper, plus a helper function that copies all the headers:

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

Now I'm able to see StackOverflow or any other site how it's supposed to be.
I'm still working on POST calls, though, so this is a work in progress.

答案2

得分: 13

有点晚了,但是ReverseProxy并没有出错,只是有点令人困惑,因为它的工作方式与你期望的不同(至少我也期望它的工作方式与你一样,所以我们两个人都有这个期望)。

根据文档

对于服务器请求,Host指定了URL所在的主机。根据RFC 2616,它可以是"Host"头部的值,也可以是URL本身中给出的主机名。它可以是"host:port"的形式。对于国际域名,Host可以是Punycode或Unicode形式。如果需要,可以使用golang.org/x/net/idna将其转换为任一格式。

对于客户端请求,Host可选地覆盖Host头部进行发送。如果为空,Request.Write方法将使用URL.Host的值。Host可以包含国际域名。

Host string

由于ReverseProxy在内部使用这个Request来进行客户端请求(在ReverseProxy.Director可选地修改它之后),如果设置了Host,它将覆盖Host头部。这个值将始终被设置,因为文档注释的第一部分指出"对于服务器请求,Host指定了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

英文:

A little late to the party, but ReverseProxy isn't broken, it's just a little confusing because it doesn't work how you'd expect (at the least, I expected it to work the way you did, so that makes two of us).

From 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

Since under the hood ReverseProxy is using this Request to make a client request (after ReverseProxy.Director optionally modifies it), if the Host is set it will override the Host header. This will always be set, because as the first part of the doc comment states "For server requests, Host specifies the host on which the URL is sought".

So in addition to Sebastián's answer, you also need to set req.Host. For example, to proxy to example.com:

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

Alternatively you can set req.Host to "" and it will use the value of req.URL.Host.

I figured this out by reading: https://github.com/golang/go/issues/14413

huangapple
  • 本文由 发表于 2014年4月19日 08:55:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/23164547.html
匿名

发表评论

匿名网友

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

确定