[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
- 应用层
- HTTP
- Cookie
- Session
- HTTP报文格式
- HTTP的Header字段
- HTTPS
- 简介
- 原理
- RSA加密与解密
- 证书签名与验证
- TLS双向认证
- openssl命令汇总
- DNS
- DNS的记录类型
- DNS的报文格式
- FAQ
- 传输层
- TCP
- CloseWait
- 网络层
- IPv6
- 链路层
- 链接层基础知识
- VLAN
- FAQ
- Linux网络收发包
- 网卡收包
- 网卡发包
- 收发包FAQ
- LVS
- 安装-DR模式
- 基本原理
- Ipvsadm命令
- Netfilter
- Netfilter简介
- 注册钩子函数
- Netfilter中数据包流向
- Iptables的数据结构
- 连接跟踪
- 初识连接跟踪
- 连接跟踪详解
- 连接跟踪数据结构
- 数据包与连接的状态
- NAT
- IPVS
- KubeProxy的IPVS模式
- Linux虚拟网络设备
- 虚拟网络设备简介
- Tap
- VethPair
- Vlan
- Vxlan
- Flannel的VXLAN原理
- Openstack的VXLAN原理
- VXLAN总结
- Bridge
- 给容器设置主机网段IP
- Macvlan
- Ipvlan
- IPIP
- IPIP使用介绍
- IPIP源码分析
- Limdiag网络
- 详细设计
- kubeovn
- IP命令
- Calico
- Calico常见问题
- ARP无响应
- 其他