Http报文抓取

804 阅读13分钟

\

1. Wireshark 抓包分析 HTTP 请求、响应报文格式

HTTP 协议再规范文档里详细定义了报文的格式,规定了组成部分,解析规则,还有处理策略,所以可以在 TCP/IP 层之上实现丰富灵活的功能,例如连接控制、缓存管理、数据编码、内容协商 。

1.1 报文结构

HTTP 协议是一个”纯文本“的协议,在实际传输的数据前要附加一些头数据,不过头数据都是ASCII码的文本,可以很容易地用肉眼阅读,不用借助程序解析也能够看懂。

HTTP协议的请求报文和响应报文的结构基本相同,由三大部分组成:

  • 起始行(start line)描述请求或响应的基本信息
  • 头部字段集合(header)使用 key-value 形式更详细说明报文
  • 消息正文(entity)实际传输的数据,不一定是纯文本,可以是图片、视频等二进制数据

其中前两部分起始行和头部字段经常合称为”请求头“或”响应头“,消息正文又称为”实体“,但与”header“对应,很多时候就直接称为”body“ ,HTTP协议规定报文必须有header,但可以没有body,而且在header之后必须要有一个“空行”,也就是“CRLF”,十六机制的“0D0A” 。所以,一个完整的HTTP报文就像下图的这样,注意在header和body之间有一个“空行”。

image.png 接下来看上一篇提到的获取的 GET 请求报文信息:

image.png

  • 在这个浏览器发出的请求报文里,第一行“GET / HTTP/1.1”就是请求行,

  • 而后面的“Host”“Connection”等等都属于header,

  • 报文的最后是一个空白行结束,没有body。

    其实浏览器发送GET请求的时HTTP报文经常是只有header而没body。,虽然HTTP协议对header的大小没有做限制,但各个Web服务器都不允许过大的请求头,因为头部太大可能会占用大量的服务器资源,影响运行效率。

1.2 HTTP 请求报文的请求行

请求报文里的起始行也就是请求行(request line),简要描述了客户端想要如何操作服务器端的资源。

请求行由三部分构成:

  • 请求方法:是一个动词,如GET/POST,表示对资源的操作

  • 请求目标:通常是一个URI,标记了请求方法要操作的资源

  • 版本号:表示报文使用的HTTP协议版本

    这三个部分通常使用空格(space)来分隔,最后要用CRLF换行表示结束

image.png

还是用[Wireshark](https://so.csdn.net/so/search?q=Wireshark&spm=1001.2101.3001.7020)抓包的数据来举例:
GET / HTTP/1.1
在这个请求行里,“GET”是请求方法,“/”是请求标,“HTTP/1.1”是版本号,把这三部分连起来,意思就是“服务器你好,我想获取网站根目录下的默认文件,我用的协议版本号是1.1

1.3 请求方法

蒂姆·伯纳斯-李最初设想的是要用HTTP协议构建一个超链接文档系统,使用URI来定位这些文档,也就是资源。那么,该怎么在协议里操作这些资源呢?

所以,就出现了“请求方法”。实际含义就是客户端发出了一个“动作指令”,要求服务器端对URI定位的资源执行这个动作

HTTP/1.1规定了八种方法,单词都必须是大写的形式:

  • GET:获取资源,可理解为读取或下载数据
  • HEAD:获取资源的元信息
  • POST:向资源提交数据,相当于写入或上传数据
  • PUT:类似 POST
  • DELETE:删除资源
  • CONNECT:建立特殊的连接隧道
  • OPTIONS:列出可对资源实行的方法
  • TRACE:追踪请求-响应的传输路径

image.png

有点像对文件或数据库的“增删改查”操作,只不过这些动作操作的目标不是本地资源,而是远程服务器上的资源,所以只能由客户端“请求”或者“指示”服务器来完成。既然请求方法是一个“指示”,那么客户端自然就没有决定权,服务器掌控着所有资源,也就有绝对的决策权力。它收到HTTP请求报文后,看到里面的请求方法,可以执行也可以拒绝,或者改变动作的含义,毕竟HTTP是一个“协议”,两边都要“商量着来” 。比如,发起了一个GET请求,想获取“/orders”这个文件,但这个文件保密级别比较高,不是谁都能看的,服务器就可以有如下的几种响应方式:

  • 假装这个文件不存在,直接返回一个404 Not found报文
  • 稍微友好一点,明确告诉你有这个文件,但不允许访问,返回一个403 Forbidden
  • 再宽松一些,返回405 Method Not Allowed,然后用Allow头告诉你可以用HEAD方法获取文件的元信息

举几个个比较常用的方法说明:

  • GET/HEAD 请求从服务器获取资源,这个资源既可以是静态的文本、页面、图片、视频,也可以是由PHP、Java动态生成的页面或者其他格式的数据

GET方法虽然基本动作比较简单,但搭配URI和其他头字段就能实现对资源更精细操作,例如,在URI后使用“#”,可以在获取页面后直接定位到某个标签所在的位置;使用If-Modified-Since字段就变成了“有条件的请求”,仅当资源被修改时才会执行获取动作;使用Range字段就是“范围请求”,只获取资源的一部分数据

HEAD方法与GET方法类似,也是请求从服务器获取资源,服务器的处理机制也是一样的,但服务器不会返回请求的实体数据,只传回响应头,就是资源的“元信息”。可以看做是GET方法的一个“简化版”或者“轻量版”。因为它的响应头与GET完全相同,所以可以用在很多并不真正需要资源的场合,避免传输body数据的浪费。比如,想要检查一个文件是否存在,只要发个HEAD请求就可以了,没有必要用GET把整个文件都取下来。再比如,要检查文件是否有最新版本,同样也应该用HEAD,服务器会在响应头里把文件的修改时间传回来

  • POST/PUT GET和HEAD方法是从服务器获取数据,而POST和PUT方法则是相反操作,向URI指定的资源提交数据,数据就放在报文的body里

比如,上论坛灌水,敲了一堆字后点击“发帖”按钮,浏览器就执行了一次POST请求,把你的文字放进报文的body里,然后拼好POST请求头,通过TCP协议发给服务器。又比如,上购物网站,看到了一件心仪的商品,点击“加入购物车”,这时也会有POST请求,浏览器会把商品ID发给服务器,服务器再把ID写入你的购物车相关的数据库记录。PUT的作用与POST类似,也可以向服务器提交数据,但与POST存在微妙的不同,通常POST表示的是“新建”“create”的含义,而PUT则是“修改”“update”的含义。

1.4 URI

URI,也就是统一资源标识符(Uniform Resource Identifier)包含有URL和URN两个部分,在HTTP世界里用的网址实际上是URL,即统一资源定位符(Uniform Resource Locator)。但因为URL实在是太普及了,所以常常把这两者简单地视为相等

URI 的格式 URI本质上是一个字符串,这个字符串的作用是唯一地标记资源的位置或者名字,它不仅能够标记万维网的资源,也可以标记其他的,如邮件系统、本地文件系统等任意资源。而“资源”既可以是存在磁盘上的静态文本、页面数据,也可以是由Java、PHP提供的动态服务。下面的这张图显示了URI最常用的形式,由scheme、host:port、path和query四个部分组成,但有的部分可以视情况省略。

image.png

  • scheme:“协议名”,表示资源应该使用哪种协议访问,浏览器通过你的应用程序看到URI里的scheme,就知道下一步该怎么走了,会调用相应的HTTP或者HTTPS下层API。在scheme之后,必须是三个特定的字符 : / / ,把scheme和后面的部分分开 。

  • host:port,即主机名加端口号,表示资源所在主机,主机名可以是IP地址或者域名的形式,必须要有,否则浏览器就会找不到服务器。但端口号有时可以省略,浏览器等客户端会依据scheme使用默认的端口号,例如HTTP的默认端口号是80,HTTPS的默认端口号是443 。

  • Path,有了协议名和主机地址、端口号,再加上后面标记资源所在目录,浏览器就可以连接服务器访问资源。URI里path采用了类似文件系统“目录”“路径”的表示方式,因为早期互联网上的计算机多是UNIX系统,所以采用了UNIX的“/”风格。URI的path部分必须以“/”开始 。

image.png

  • 查询参数:

    URI后面还有一个“query”部分,它在path之后,用一个“?”开始,但不包含“?”,表示对资源附加的额外要求。查询参数query有一套自己的格式,是多个“key=value”的字符串,这些KV值用字 符“&”连接,浏览器和客户端都可以按照这个格式把长串的查询参数解析成可理解的字典或关联数组形式。例如:获取商品图片,但想要一个32×32的缩略图版本;获取商品列表,但要按某种规则做分页和排序;跳转页面,但想要标记跳转前的原始页面 。

  • URI的完整格式

image.png

第一个多出的部分是协议名之后、主机名之前的身份信息“user:passwd@”,表示登录主机时的用户名和密码,但现在已经不推荐使用这种形式了(RFC7230),因为它把敏感信息以明文形式暴露出来,存在严重的安全隐患。

第二个多出的部分是查询参数后的片段标识符“#fragment”,它是URI所定位的资源内部的一个“锚点”或者说是“标签”,浏览器可以在获取资源后直接跳转到它指示的位置。但片段标识符仅能由浏览器这样的客户端使用,服务器是看不到的。也就是说,浏览器永远不会把带“#fragment”的URI发送给服务器,服务器也永远不会用这种方式去处理资源的片段

URI 的编码

在URI里只能使用ASCII码。对于ASCII码以外的字符集和特殊字符做一个特殊的操作,把它们转换成与URI语义不冲突的形式。这在RFC规范里称为“escape”和“unescape”,俗称“转义”。

HTTP 响应报文的状态行

看完了请求行,我们再看响应报文里的起始行,在这里它不叫“响应行”,而是叫“状态行”(status line),意思是服务器响应的状态

比起请求行来说,状态行要简单一些,同样也是由三部分构成:

  • 版本号:表示报文使用的HTTP协议版本

  • 状态码:三个数字,表示处理的结果,比如200是成功,500是服务器错误

  • 原因:对状态码的一个解释说明

image.png

看一下之前 Wireshark 抓包里的响应报文,状态行是:

  • HTTP/1.1 200 OK 意思就是:“浏览器你好,我已经处理完了你的请求,这个报文使用的协议版本号是1.1,状态码是200,一切OK。”

另一个“GET /favicon.ico HTTP/1.1”的响应报文状态行是:

  • HTTP/1.1 404 Not Found 意思是:抱歉啊浏览器,刚才你的请求收到了,但我没找到你要的资源,错误代码是404。

状态码

它是一个十进制数字,表示服务器对请求的处理结果。客户端可以依据代码适时转换处理状态,例如继续发送请求、切换协议,重定向跳转等,有那么点TCP状态转换的意思。目前RFC标准里规定的状态码是三位数,所以取值范围就是从000到999。RFC标准把状态码分成了五类,用数字的第一位表示分类,而0-99不用,由000-999变成了100~599 。

这五类具体含义:

1××:提示信息,表示目前是协议处理的中间状态,还需要后续的操作 2××:成功,报文已经收到并被正确处理 3××:重定向,资源位置发生变动,需要客户端重新发送请求 4××:客户端错误,请求报文有误,服务器无法处理 5××:服务器错误,服务器在处理请求时内部发生了错误

HTTP 请求、响应头部字段

请求头和响应头的结构是基本一样的,唯一的区别是起始行,请求行或状态行再加上头部字段集合就构成了HTTP报文里完整的请求头或响应头,对比两个示意图:

image.png

头部字段是key-value的形式,key和value之间用 :分隔,最后用CRLF换行表示字段结束。比如在“Host:127.0.0.1”这一行里key就是“Host”,value就是“127.0.0.1” 。HTTP头字段非常灵活,不仅可以使用标准里的Host、Connection等已有头,也可以任意添加自定义头,这就给HTTP协议带来了无限的扩展可能。不过使用头字段需要注意下面几点:

  • 字段名不区分大小写,例如 Host 也可以写成 host ,但首字母大写的可读性更好
  • 字段名里不允许出现空格,可以使用连字符 - ,但不能使用下划线 _ 。例如,test-name是合法的字段名,而 test name 和 test_name是不正确的
  • 字段名后面必须紧接着:不能有空格,而:后的字段值前可以有多个空格
  • 字段的顺序是没有意义的,可以任意排列不影响语义
  • 字段原则上不能重复,除非这个字段本身的语义允许,例如Set-Cookie

常用头字段

HTTP协议规定了非常多的头部字段,实现各种各样的功能,但基本上可以分为四大类:

  • 通用字段:在请求头和响应头里都可以出现
  • 请求字段:仅能出现在请求头里,进一步说明请求信息或者额外的附加条件
  • 响应字段:仅能出现在响应头里,补充说明响应报文的信息
  • 实体字段:它实际上属于通用字段,但专门描述body的额外信息