HTTP协议基础
1. 前言
HTTP协议是一种最为常见的应用层协议,其特点是:
- 文本协议,可读性好
- 扩展性好
- 应用十分广泛
HTTP协议主要分为请求包
和响应包
,每个包都包含包头
和包体
,使用\r\n\r\n
进行分隔。
下面是使用Chrome
浏览器访问https://www.baidu.com/
抓到的HTTP请求包和返回包。
GET / HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
DNT: 1
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
HTTP/1.1 200 OK
Bdpagetype: 1
Bdqid: 0xe523220d0004f1b5
Cache-Control: private
Connection: Keep-Alive
Content-Type: text/html
Cxy_all: baidu+f77b725b5dda7a0d1796206dd5fc54c1
Date: Sat, 23 Feb 2019 06:33:26 GMT
Expires: Sat, 23 Feb 2019 06:32:35 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Server: BWS/1.1
Set-Cookie: BAIDUID=3B2BAAB84798D4193EDD80E73439CB3E:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BIDUPSID=3B2BAAB84798D4193EDD80E73439CB3E; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1550903606; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: delPer=0; path=/; domain=.baidu.com
Set-Cookie: BDSVRTM=0; path=/
Set-Cookie: BD_HOME=0; path=/
Set-Cookie: H_PS_PSSID=1460_21101_28558; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Vary: Accept-Encoding
X-Ua-Compatible: IE=Edge,chrome=1
Content-Length: 156207
<!DOCTYPE html>
<!--STATUS OK-->
......
2. 包头
包头主要包含首行
和Headers
,行间通过\r\n
分隔。请求包
和响应包
的首行
格式有所差别。
2.1. 首行
请求包
请求包
的首行
格式为:
METHOD URI HTTP/x.y
METHOD
是请求方法,常见的如:GET
、POST
、PUT
、DELETE
等。示例中给的就是一个GET
请求。
GET
请求一般用于获取数据或资源的读请求,请求参数放在URI
字段中;POST
请求一般用于登录、提交等操作,通常会有防CSRF
的字段,请求参数在包体
中。
URI
是要请求的资源地址,一般以/
开头,只有在HTTP代理中才会使用完整的URL。
x.y
是HTTP协议的版本,目前最常见的是1.1
。
返回包
返回包
的首行格式为:
HTTP/x.y CODE MESSAGE
x.y
含义与请求包一致
CODE
是HTTP返回码,表示当前请求的处理结果。HTTP返回码定义请参考:HTTP返回码定义。
MESSAGE
一般是CODE
对应的解释,也可以是自定义的内容。
2.2. Headers
这部分是可扩展的,允许用户使用自定义的HTTP头。格式一般如下:
Header-Name: header-value
Header-Name
是HTTP头名称,header-value
是对应的值。
请求包
和返回包
都会出现的HTTP头
Connection
: 保持连接选项,可选的值有:close
(请求返回后关闭连接)、keep-alive
(多个请求可以复用该连接)(注意:由于HTTP 1.x 的限制,同一个连接必须要在上一个请求返回后才能发送下一个请求)
Content-Length
: 包体长度的10进制字符串,没有包体的请求可以没有该字段
Content-Type
: 包体类型。常见类型如下:
- HTML类型是
text/html
- JavaScript类型是
application/javascript
POST
请求中一般是application/x-www-form-urlencoded
POST
上传文件时是multipart/form-data
RESTful
请求中都是application/json
只有请求包
中出现的HTTP头
Host
: 要访问的主机名,如果端口不是80
,需要加上:port
,如:www.qq.com:8080
User-Agent
: 客户端标识
Referer
: 发起请求的页面url,一般用作防盗链,防CSRF
Accept
: 支持的类型
Accept-Encoding
: 支持的编码,如:gzip
、deflate
、sdch
等,编码间使用,
分隔
Accept-Language
: 支持的语言,如:zh-CN,zh;q=0.8
Cookie
: cookie
只有返回包
中出现的HTTP头
Server
: 服务器标识
Date
: 服务端响应的时间
Expires
: 当前资源的过期时间
Content-Encoding
: 包体的内容编码,如:gzip
、deflate
等
Transfer-Encoding
: 包体的传输编码,只能设为chunked
,表示分块传输
Access-Control-Allow-Origin
: 允许跨域访问的列表,如果要允许任意域名访问。可以设置为*
Access-Control-Allow-Methods
: 允许跨域访问METHOD列表,如:POST, GET, OPTIONS, DELETE
Set-Cookie
: 设置Cookie,格式为:key=value; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.domain.com
2.3. URL编码
GET
请求中位于url的?
后面的参数以及application/x-www-form-urlencoded
类型的POST
请求中的包体需要进行URL编码。
具体规则如下:
对于以下列表中的字符不需要处理:
ASCII | Char | ASCII | Char | ASCII | Char |
---|---|---|---|---|---|
48 | 0 | 65 | A | 97 | a |
49 | 1 | 66 | B | 98 | b |
50 | 2 | 67 | C | 99 | c |
51 | 3 | 68 | D | 100 | d |
52 | 4 | 69 | E | 101 | e |
53 | 5 | 70 | F | 102 | f |
54 | 6 | 71 | G | 103 | g |
55 | 7 | 72 | H | 104 | h |
56 | 8 | 73 | I | 105 | i |
57 | 9 | 74 | J | 106 | j |
75 | K | 107 | k | ||
76 | L | 108 | l | ||
77 | M | 109 | m | ||
78 | N | 110 | n | ||
79 | O | 111 | o | ||
80 | P | 112 | p | ||
81 | Q | 113 | q | ||
82 | R | 114 | r | ||
83 | S | 115 | s | ||
84 | T | 116 | t | ||
85 | U | 117 | u | ||
86 | V | 118 | v | ||
87 | W | 119 | w | ||
88 | X | 120 | x | ||
89 | Y | 121 | y | ||
90 | Z | 122 | z |
其它字符绝大多数都需要根据字符的ASCII码
转换成%.2X
的格式。例如::
=>%3A
,/
=>%2F
,%
=>%25
。对于汉字等多字节字符串使用UTF8
编码后再转成%.2X
的形式。
对于特殊字符,不同函数的处理逻辑略有不同:
Python的urllib.quote
- 只有
- . / _
这几个字符不处理
- 只有
JavaScript的escape
- 只有
* + - . / @ _
这几个字符不处理
- 只有
JavaScript的encodeURI
- 只有
! # $ & ' ( ) * + , - . / : ; = ? @ _ ~
这几个字符不处理
- 只有
JavaScript的encodeURIComponent
- 只有
! ' ( ) * - . _ ~
这几个字符不处理
- 只有
3. 包体
3.1. 包体长度
包体长度的计算有两种方式:
- 包头中存在
Content-Length
字段时,包体长度由该字段决定 - 包头中存在
Transfer-Encoding
字段时,使用chunked
方式,具体规则如下:- 对于每一个分块,要传输的内容为当前分块长度的16进制字符串加上
\r\n
,再加上分块数据和\r\n
;例如分块内容为:1234567890
,那么要传输的内容就为:a\r\n1234567890\r\n
- 最后一块必须为:
0\r\n\r\n
- 对于每一个分块,要传输的内容为当前分块长度的16进制字符串加上
使用chunked
方式的优点是:发送包头时,不用确定包体的长度,可以显著缩短TTFB
(Time To First Byte
)值;并且对于需要动态计算包体的情况非常适合。
3.2. 包体类型
如前所述,包体的类型会由包头中的Content-Type
字段决定
application/x-www-form-urlencoded
类型
该类型是POST
请求中最为常见的类型,如以下所示:
staticpage=https%3A%2F%2Fwww.baidu.com%2Fcache%2Fuser%2Fhtml%2Fv3Jump.html&charset=UTF-8&tpl=mn&subpro=&apiver=v3&tt=1550929251247&codestring=&safeflg=0&u=https%3A%2F%2Fwww.baidu.com%2F&isPhone=false&detect=1
multipart/form-data
类型
该类型完整的值为:multipart/form-data; boundary=----WebKitFormBoundaryobuwoK7HliCagMDU
。boundary
后面跟的是随机分隔字符串,用于分隔多个字段。包体内容如下所示:
------WebKitFormBoundaryobuwoK7HliCagMDU
Content-Disposition: form-data; name="bdstoken"
50463f616a435dd1586f7c25abcacb84
------WebKitFormBoundaryobuwoK7HliCagMDU
Content-Disposition: form-data; name="psign"
2d2e6472756e6b647265616d2001
------WebKitFormBoundaryobuwoK7HliCagMDU
Content-Disposition: form-data; name="staticpage"
https://www.baidu.com/p/jump.html
------WebKitFormBoundaryobuwoK7HliCagMDU
Content-Disposition: form-data; name="file"; filename="02480674.jpg"
Content-Type: image/jpeg
����
示例里共包含bdstoken
、psign
、staticpage
、file
这4个字段,其中前3个是普通字符串,file
是图片文件内容。
application/json
类型
此时包体是json格式的字符串,如下所示:
{"url": "http://www.baidu.com/"}
3.3. 包体内容编码
包体的内容编码类型由Content-Encoding
字段决定,主要是为了减少包体体积,提升性能。
DEFLATE
是同时使用了LZ77算法与哈夫曼编码(Huffman Coding)的一个无损数据压缩算法。
GZIP
,是一种使用 DEFLATE 的文件格式。
ZLIB
,是一种使用 DEFLATE 的数据压缩格式。
gzip
压缩方式
使用GZIP
格式压缩
deflate
压缩方式
其实是使用ZLIB
方式压缩。由于IE
错误地实现为了使用DEFLATE
方式解压,因此, 为了兼容IE,Server一般都是使用gzip
方式。
sdch
压缩方式
Google发明的压缩算法,全称为 Shared Dictionary Compression over HTTP
,是一种基于网页HTML中存在大量冗余字符串,从而建立字典,进行压缩的方式,相对gzip
等基于单文件进行压缩的方式,可以极大地提升压缩率。
目前主要是Google的服务器在使用。
4. 如何抓包
4.1. 使用Chrome抓包
Chrome浏览器自带抓包功能,可以捕获所有HTTP(S)请求,甚至是WebSocket协议。
方法是按F12
打开开发者工具,进入Network
页面,然后刷新页面就可以了。
这种方法抓包简单,但是只能抓取浏览器收发的包。
4.2. 使用Fiddler抓包
Fiddler是一款通过提供HTTP代理
服务来抓包的工具,类似的工具还有很多,读者可以选择自己习惯的工具。
Fiddler具体的工作原理是监听了8888
端口,并将其设置为系统代理。因此,浏览器在访问网页时,都会发送请求到Fiddler,从而可以捕获到请求包以及返回包。
对于HTTPS
请求,Fiddler提供了HTTP CONNECT
服务,进而与浏览器建立HTTPS隧道,并返回了一个自签名的证书,以绕过SSL/TLS
层的加密。因此,浏览器需要忽略证书错误,才能使用Fiddler抓取HTTPS包。
Fiddler适合抓取那些使用了系统代理,并且允许证书错误的场景。如果应用限定了根证书,就无法抓包了。
4.3. 使用Wireshark抓包
Wireshark是一款驱动层的抓包工具,与tcpdump
类似,适合抓取明文包,也就是HTTP包,HTTPS包由于无法解密,因此不能适用。