先重温一下什么叫反向代理,正向代理。

所谓正向,反向代理取决于代理的是出站请求,还是入站请求

正向代理: 代理的出站请求, 客户端能感知到代理程序,架构上距离客户端更近。

反向代理: 代理的是入站请求,客户端认为代理程序就是服务器,客户端感知不到代理逻辑,架构上距离服务端更近。

反向代理的血案

net/http/httputil
403 Forbiddentarget

给一个向代理百度官网的简化示例,大家可以体会一下:

郁闷了很久,wireshark抓包也看不出端倪(其实是知识有漏洞,那肯定找不到原因)。

头脑风暴

调试httputil的源代码:

  • 在代理后url中的host已经变成指定域名,但header中的host值没有发生变化还是localhost:8000;
  • 此时我并没有发现问题,因为我笃定url中的host应该决定了请求的具体地址,抱着死马当活马医的态度,我重写了header中的host为目标百度域名
 req.Host = target  // 上面被注释 

竟然真的成功了

小板凳好好摆一摆

知识漏洞的关键点在于 :

  • url中已经有host了,为什么header中还要有host?
  • url中的host与request.header中的host到底什么关系?
Host

最关键的第三点:

3. 设计Host请求头的动机: 在请求(为多个主机名服务的)服务器地址时,使初始服务器能够区分资源。

The "Host" header field in a request provides the host and port information from the target URI, enabling the origin server to distinguish among resources while servicing requests for multiple host names

什么意思呢?

在微服务架构下,请求在打到业务应用之前都会流经负载均衡器,例如nginx/网关,这些负载均衡器提供了单负载节点配置多个域名的能力。但是请求打到负载主机,需要有信息能区分目标服务域名,这就依赖请求头中的Host。



在这个配置中,nginx会检查请求的Host头与 server_name指令值的相等关系以决定该请求应由哪个虚拟主机来处理。

如果Host头没有匹配任意一个虚拟主机,或者请求中根本没有包含Host头,那nginx会将请求分发到定义在此端口上的默认虚拟主机。
在以上配置中,第一个被列出的虚拟主机即nginx的默认虚拟主机——这是nginx的默认行为。而且,可以显式地设置某个主机为默认虚拟主机,即在"listen"指令中设置"default_server"参数:

回到最开始的问题,我们写的反向代理程序其实是客户端,虽然重写了url Host, 但是请求打到虚拟主机的时候,请求头中的Host还是最开始的localhost:8080, 这个Host根本无法在虚拟主机中被识别, 所以我们还需要重写请求头中的Host为目标域名。


httputil内置的NewSingleHostReverseProxy 是一个【反向代理到固定地址】的实现,他也没有重写Host请求头, 而我的写法其实就是一个自定义实现。

NewSingleHostReverseProxy returns a new ReverseProxy that routes URLs to the scheme, host, and base path provided in target. If the target's path is "/base" and the incoming request was for "/dir",the target request will be for /base/dir. NewSingleHostReverseProxy does not rewrite the Host header. To rewrite Host headers, use ReverseProxy directly with a custom Director policy.

结束语

本文通过一个简单的反向代理程序的错误姿势,引出了Host请求头的作用,更进一步认识了主流负载均衡服务器在请求链路中的行为。

Host请求头用于在单负载节点支撑多域名。