🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] HTTP协议的报文分为请求报文和响应报文,二者的报文格式不一样 ### **请求报文** HTTP的请求报文如下(来自[csdn博客](https://blog.csdn.net/zx_emily/article/details/83024065),官方文档参考[RFC7230](https://www.greenbytes.de/tech/webdav/rfc7230.html)): ![](https://img.kancloud.cn/4d/53/4d534cce6ff679587c782916986f67c3_483x173.png) 分为四部分: * 请求行 * 请求方法:比如GET; * URL:官方文档叫RequestTarget,即在浏览器中输入的完整内容,比如`http://xxx.org:8080/login?username=peng`, 包括协议(scheme)、域名、端口、路径、query参数; * 协议版本:比如`HTTP/1.1` * 头部参数(Header) * 回车换行:用来分隔Header与Body * 请求数据:即Body数据,一般只有POST、PUT方法才有,比如json数据或上传视频时的流数据 ##### **请求行** 请求行的官方文档参考 [RFC7230-RequestLine](https://www.greenbytes.de/tech/webdav/rfc7230.html#request.line)。它包含了上面提到的三个信息,可以通过下面的方法获取各个信息(不同语言的获取方法不一样,下面是golang的获取方法) ``` package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/hello", helloHandleFunc) http.ListenAndServe(":30000", nil) } func helloHandleFunc(writer http.ResponseWriter, request *http.Request) { fmt.Fprintf(writer, "request.Method: %s\n", request.Method) fmt.Fprintf(writer, "request.Proto: %s\n", request.Proto) fmt.Fprintf(writer, "request.Host: %s\n", request.Host) fmt.Fprintf(writer, "request.RequestURI: %s\n", request.RequestURI) } ``` 请求时的输出如下: ![](https://img.kancloud.cn/b0/7b/b07b32153f037ed88a8b3dd2cce8d065_488x186.png) ##### **请求头** HTTP的请求头是一组key-value,key是可以自定义的,也就是说,客户端在发起HTTP请求时,是可以携带任何的key-value的。比如使用curl工具时,可以通过`curl -H "key: value"`来指定任意的Header。 **但是浏览器或curl等工具在发起请求时,一般会默认携带下面的Header:** | 字段名 | 含义 | 备注 | | --- | --- | --- | | User-Agent | 客户端是什么工具 | 比如使用curl工具时:`User-Agent: [curl/7.66.0]` | | Content-Type | 请求Body的类型 | 只有POST、PUT方法有该参数,比如:`Content-Type: application/json` | | Accept | 能够接受的回应内容类型(Content-Types)| 比如,`Accept: text/plain` | | ~~Host~~ | 请求行的URL中使用的`域名:端口` | 维基百科中说这个是“常设”的Header,但是实际中发现chrome、firefox、curl等都不会携带这个Header | ### **响应报文** ### **扩展阅读一:Nginx转发** Q:Nginx转发时,服务器如何获得真实客户端的请求域名、源IP? Nginx中有几个变量,记录了客户端请求的信息: * `$remote_addr`:真实客户端的IP * `$http_host`: 真实客户端请求时使用的URL中的`域名:端口` * `$host`: 真实客户端请求时使用的URL中的`域名` 我们可以在nginx.conf中配置它们,然后通过Header带给后端服务,如下: ``` http { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } ``` 假设有如下的服务程序,它返回Header以及`request.Host`和`request.RequestURI`: ``` package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/hello", helloHandleFunc) http.ListenAndServe(":30000", nil) } func helloHandleFunc(writer http.ResponseWriter, request *http.Request) { fmt.Fprintf(writer, "request.Host: %s\n", request.Host) fmt.Fprintf(writer, "request.RequestURI: %s\n", request.RequestURI) fmt.Fprintf(writer, "\n下面是Header\n") for key, value := range request.Header { fmt.Fprintf(writer, "%s: %s\n", key, value) } } ``` 我们把上面的程序运行在主机192.168.90.1上,然后我们在另一台主机192.168.90.72上运行nginx,nginx.conf如下配置: ``` ... http { ... proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; upstream hello { server 192.168.90.1:30000; } server { listen 10080; location / { proxy_pass http://hello; } } } ``` 然后我们在主机192.168.90.70上访问nginx,访问链条为 ``` curl -> nginx -> go-server 192.168.90.70 -> 192.168.90.72:10080 -> 192.168.90.1:30000 ``` 访问结果如下(注意在curl主机上已经在`/etc/hosts`中添加了hello.peng.io到192.168.90.72的解析): ``` $ curl http://hello.peng.io:10080/hello?id=1 request.Host: hello.peng.io request.RequestURI: /hello?id=1 下面是Header Accept: [*/*] X-Real-Ip: [192.168.90.70] Connection: [close] User-Agent: [curl/7.29.0] ``` 从结果我们可以看到,Go-Server接收到的Header中已经有了X-Real-Ip。但是为什么没有Host这个Header呢?这是因为,当nginx设置了`proxy-set-header Host $host`之后,nginx在构造HTTP请求报文时,把它放到了请求行的`request-target`中,而上面Go-Server的代码中`request.Host`正是从HTTP请求报文的`request-target`中获得的。 当然,如果在nginx.conf中设置为`proxy-set-header Host $http_host`,那么Go-Server的`request.Host`的值就是`hello.peng.io:10080`(已验证)。 ### **FAQ** Q:HTTP请求报文中的`request-target`,其实并不是URL,到底包含哪些信息? Q:什么情况下客户端发起HTTP请求时,Header中会设置Host参数,什么情况下不会? A:需要参考[RFC7230](https://www.greenbytes.de/tech/webdav/rfc7230.html)这篇文章深入研究 ### **参考** * https://www.greenbytes.de/tech/webdav/rfc7230.html * https://blog.csdn.net/zx_emily/article/details/83024065 * https://zh.wikipedia.org/wiki/HTTP%E5%A4%B4%E5%AD%97%E6%AE%B5 * https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/ * https://stackoverflow.com/questions/63857806/nginx-proxy-proxy-set-header-custom-header-from-site-conf-for-varnish-cache