Golang 反向代理每个主机

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

Golang ReverseProxy per host

问题

我正在尝试在Go中实现一个反向代理,根据URL中嵌入的租户将流量代理到不同的主机。实现代码如下:

type Offloader struct {
	tenantHostMap   map[string]string                   // 将租户映射到其主机:端口
	tenantProxyMap  map[string](*httputil.ReverseProxy) // 将租户映射到其反向代理
}

func (o *Offloader) OnCreate() {

	// 租户映射
	o.tenantHostMap = make(map[string]string)
	o.tenantProxyMap = make(map[string]*httputil.ReverseProxy)
	o.PopulateTenantHostMap()

	// Rx
	http.HandleFunc("/", o.ServeHTTP)
	go http.ListenAndServe(":5555", nil)

}

// ServeHTTP 是每次收到 HTTP 请求时调用的回调函数。
func (o *Offloader) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	incomingUrl := req.URL.RequestURI()
	tenant := o.GetTenantFromUrl(incomingUrl)

	if proxy, ok := o.tenantProxyMap[tenant]; ok {
		proxy.ServeHTTP(w, req)
	}

	if remoteHostAddr, ok := o.tenantHostMap[tenant]; ok {
		remoteUrl, err := url.Parse(fmt.Sprintf("http://%s", remoteHostAddr))
		if err != nil {
			return
		}
		proxy := httputil.NewSingleHostReverseProxy(remoteUrl)
		o.tenantProxyMap[tenant] = proxy
		proxy.ServeHTTP(w, req) // 非阻塞

	} else {
		panic("未知租户")
	}
}

当接收到新的HTTP请求时,我从URL中获取租户。如果这是我第一次看到这个租户,我会创建一个新的ReverseProxy,否则我会尝试使用之前创建并存储在tenantProxyMap中的那个。

当我测试时,我得到以下错误:

2022/04/05 12:31:01 http: proxy error: readfrom tcp ****: http: invalid Read on closed Body
2022/04/05 12:31:01 http: superfluous response.WriteHeader call from net/http/httputil.(*ReverseProxy).defaultErrorHandler (reverseproxy.go:190)

如果我为每个请求创建一个新的反向代理而不是重用相同的代理,错误就不会发生。

我以为代理是按主机而不是按请求(如名称所示)的,所以我想知道为什么会发生这个错误?

我知道我需要保护映射免受并发读/写的影响,但这在目前是无关紧要的。

谢谢,

英文:

I am trying to implement a Reverse Proxy in Go that proxies traffic to different hosts based on some tenant embedded in the URL. The implementation looks like this:

type Offloader struct {
	tenantHostMap   map[string]string                   // Map a tenant to its host:port
	tenantProxyMap  map[string](*httputil.ReverseProxy) // Map a tenant to its reverse proxy
}

func (o *Offloader) OnCreate() {

	// Tenants Map
	o.tenantHostMap = make(map[string]string)
	o.tenantProxyMap = make(map[string]*httputil.ReverseProxy)
	o.PopulateTenantHostMap()

	// Rx
	http.HandleFunc("/", o.ServeHTTP)
	go http.ListenAndServe(":5555", nil)

}

// ServeHTTP is the callback that is called each time a Http Request is received.
func (o *Offloader) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	incomingUrl := req.URL.RequestURI()
	tenant := o.GetTenantFromUrl(incomingUrl)

	if proxy, ok := o.tenantProxyMap[tenant]; ok {
		proxy.ServeHTTP(w, req)
	}

	if remoteHostAddr, ok := o.tenantHostMap[tenant]; ok {
		remoteUrl, err := url.Parse(fmt.Sprintf("http://%s", remoteHostAddr))
		if err != nil {
			return
		}
		proxy := httputil.NewSingleHostReverseProxy(remoteUrl)
		o.tenantProxyMap[tenant] = proxy
		proxy.ServeHTTP(w, req) // non blocking

	} else {
		panic("Unknown Tenant")
	}
}

When receiving a new HTTP request, I get the tenant from the URL. If this is the first time I am seeing this tenant I create a new ReverseProxy, otherwise I try to use the one I created before and stored in the tenantProxyMap.

When I test this, I get the following error:

2022/04/05 12:31:01 http: proxy error: readfrom tcp ****: http: invalid Read on closed Body
2022/04/05 12:31:01 http: superfluous response.WriteHeader call from net/http/httputil.(*ReverseProxy).defaultErrorHandler (reverseproxy.go:190)

If I create a new Reverse Proxy for each request rather than reusing the same proxy, the error doesn't happen.

I thought the proxy is per host and not per request (as the name suggests), so I am wondering why this error happens?

I know I need to protect the maps from concurrent reads/writes however that is irrelevant at the moment.

Thanks,

答案1

得分: 1

问题在于,在已经存在先前代理的情况下,你首先将请求传递给该代理,然后重新创建代理,并再次传递请求。换句话说,对于每个传入的请求,你实际上进行了两次代理请求,而对于该租户,tentantProxyMap 已经填充了。

ReverseProxy 实现会关闭 req.Body,因此第二次将请求传递给代理时,它会尝试从已关闭的 body 中读取。因此,你会看到 http: invalid Read on closed Body 错误。

你应该尝试在代理请求后返回,例如添加一个 return

if proxy, ok := o.tenantProxyMap[tenant]; ok {
    proxy.ServeHTTP(w, req)
    return
}
英文:

The problem is that in the scenario where a previous proxy already existed, you first pass the request on to that - and then still recreate the proxy, and again pass the request. In other words: you are making two proxied requests for each incoming request, when the tentantProxyMap is already populated for that tenant.

The ReverseProxy implementation closes the req.Body, so the second time you pass the request on to the proxy, it attempts reading from an already closed body. You're seeing the http: invalid Read on closed Body error as a result.

What you should try is to return after proxying the request, e.g. by adding a return:

if proxy, ok := o.tenantProxyMap[tenant]; ok {
    proxy.ServeHTTP(w, req)
    return
}

huangapple
  • 本文由 发表于 2022年4月5日 17:54:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/71749694.html
匿名

发表评论

匿名网友

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

确定