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-urlencodedPOST上传文件时是multipart/form-dataRESTful请求中都是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包由于无法解密,因此不能适用。