Host请求头在接入层主机中的关键引流作用
先重温一下什么叫反向代理,正向代理。
鹅厂二面,nginx回忆录
所谓正向,反向代理取决于代理的是出站请求,还是入站请求。
正向代理: 代理的出站请求, 客户端能感知到代理程序,架构上距离客户端更近。
反向代理: 代理的是入站请求,客户端认为代理程序就是服务器,客户端感知不到代理逻辑,架构上距离服务端更近。
反向代理的血案
前几天打算使用golang做一个代理程序,golang标准库net/http/httputil
已经提供了这样的能力。
一把梭之后发现必然返回403 Forbidden
, 我直接在target
里面填上游服务实例ip就可以正确返回。
给一个向代理百度官网的简化示例,大家可以体会一下:
package main import ( "fmt" "log" "net/http" "net/http/httputil" ) func ReverseProxyHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("receive a request from:", r.RemoteAddr, r.Header) target := "www.baidu.com" director := func(req *http.Request) { req.URL.Scheme = "https" req.URL.Host = target // req.Host = target } proxy := &httputil.ReverseProxy{Director: director} proxy.ServeHTTP(w, r) } func main() { fmt.Printf("Starting server at port 8080\n") if err := http.ListenAndServe(":8080", http.HandlerFunc(ReverseProxyHandler)); err != nil { log.Fatal(err) } }
郁闷了很久,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
请求头是在http1.1作为必选被引入,如果请求头没有Host或有多个Host请求头, 将会返回400错误。- 请求中的“Host”提供了目标URI的主机和端口信息。
Host = uri-host [ ":" port ]
host: the domain name of the server (for virtual hosting).
port[option]: TCP port number on which the server is listening.
最关键的第三点:
- 设计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。
The exact resource identified by an Internet request is determined by
examining both the Request-URI and the Host header field.
什么意思呢?
在微服务架构下,请求在打到业务应用之前都会流经负载均衡器,例如nginx/网关,这些负载均衡器提供了单虚拟主机节点服务于多个域名的能力, 俗称共享主机。
但是请求打到虚拟主机,需要有信息能区分目标服务域名,这就依赖请求头中的Host。
上图来自 阿里云应用型负载均衡
我们来看在nginx配置基于Host请求头的共享主机的写法:
在这个配置中,nginx会检查请求的Host头与server_name指令的相等来决定该请求应由哪个服务来处理。
如果Host头没有匹配任意一个虚拟主机名,或者请求中根本没有包含Host头,那nginx会将请求分发到定义在此端口上的默认虚拟主机。
在以上配置中,第一个被列出的虚拟主机即为nginx的默认虚拟主机——这是nginx的默认行为。当然也可显式设置某个主机为默认虚拟主机,即在"listen"指令中设置"default_server"参数:
server { listen 80 default_server; server_name example.net www.example.net; // server_name name... 设置虚拟主机名,其实就是域名,可设置多个。 ... } 如果不允许请求不带host header,可以定义如下主机,丢弃这些请求。 server { listen 80; server_name ""; return 444; }
回到最开始的问题,我们写的反向代理程序其实是客户端,虽然重写了url Host, 但是请求打到虚拟主机的时候,请求头中的Host还是最开始的localhost:8080, 这个Host根本无法匹配到server_name指令值
, 所以我们还需要重写请求头中的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请求头在接入层主机中的关键引流作用, 支撑了多域名服务。
本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/16639016.html
欢迎关注我的原创技术、职场公众号, 加好友谈天说地,一起进化
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?