个人随笔
目录
HTTP协议-05:HTTP报文
2020-03-28 23:15:27

那么HTTP协议的核心部分是什么呢?

答案就是它传输的报文内容。

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

1.报文结构

拿TCP报文来举例,它在实际要传输的数据之前附加了一个20字节的头部数据,存储TCP协议必须的额外信息,例如发送方的端口号、接收方的端口号、包序号、标志位等等。

有了这个附加的TCP头,数据包才能够正确传输,到了目的地后把头部去掉,就可以拿到真正的数据。

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

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

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

这其中前两部分起始行和头部字段经常又合称为“请求头”或“响应头”,消息正文又称为“实体”,但与“header”对应,很多时候就直接称为“body”。

HTTP协议规定报文必须有header,但可以没有body,而且在header之后必须要有一个“空行”,也就是“CRLF”,十六进制的“0D0A”。

所以,一个完整的HTTP报文就像是下图的这个样子,注意在header和body之间有一个“空行”。

看一下之前用Wireshark抓的包吧。

在这个浏览器发出的请求报文里,第一行“GET / HTTP/1.1”就是请求行,而后面的“Host”“Connection”等等都属于header,报文的最后是一个空白行结束,没有body。

在很多时候,特别是浏览器发送GET请求的时候都是这样,HTTP报文经常是只有header而没body。

不过这个“大头”也不能太大,虽然HTTP协议对header的大小没有做限制,但各个Web服务器都不允许过大的请求头,因为头部太大可能会占用大量的服务器资源,影响运行效率。


2.请求行

了解了HTTP报文的基本结构后,来看看请求报文里的起始行也就是请求行(request line),它简要地描述了客户端想要如何操作服务器端的资源。

请求行由三部分构成:

  • 1)请求方法:是一个动词,如GET/POST,表示对资源的操作;
  • 2)请求目标:通常是一个URI,标记了请求方法要操作的资源;
  • 3)版本号:表示报文使用的HTTP协议版本。

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

还是用Wireshark抓包的数据来举例:

  1. GET / HTTP/1.1

在这个请求行里,“GET”是请求方法,“/”是请求目标,“HTTP/1.1”是版本号,把这三部分连起来,意思就是“服务器你好,我想获取网站根目录下的默认文件,我用的协议版本号是1.1,请不要用1.0或者2.0回复我。”

别看请求行就一行,貌似很简单,其实这里面的“讲究”是非常多的,尤其是前面的请求方法和请求目标,组合起来变化多端。


3.状态行

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

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

  • 1)版本号:表示报文使用的HTTP协议版本;
  • 2)状态码:一个三位数,用代码的形式表示处理的结果,比如200是成功,500是服务器错误;
  • 3)原因:作为数字状态码补充,是更详细的解释文字,帮助人理解原因。

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

  1. HTTP/1.1 200 OK

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

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

  1. HTTP/1.1 404 Not Found

翻译成人话就是:“抱歉啊浏览器,刚才你的请求收到了,但我没找到你要的资源,错误代码是404,接下来的事情你就看着办吧。”


4.头部字段

请求行或状态行再加上头部字段集合就构成了HTTP报文里完整的请求头或响应头。

请求头和响应头的结构是基本一样的,唯一的区别是起始行,所以把请求头和响应头里的字段放在一起介绍。

头部字段是key-value的形式,key和value之间用“:”分隔,最后用CRLF换行表示字段结束。比如在“Host: 127.0.0.1”这一行里key就是“Host”,value就是“127.0.0.1”。

HTTP头字段非常灵活,不仅可以使用标准里的Host、Connection等已有头,也可以任意添加自定义头,这就给HTTP协议带来了无限的扩展可能。

不过使用头字段需要注意下面几点:

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

在实验环境里用Lua编写了一个小服务程序,URI是“/09-1”,效果是输出所有的请求头。

可以在实验环境里用Telnet连接OpenResty服务器试一下,手动发送HTTP请求头,试验各种正确和错误的情况。

先启动OpenResty服务器,然后用组合键“Win+R”运行telnet,输入命令“open www.chrono.com 80”,就连上了Web服务器。

连接上之后按组合键“CTRL+]”,然后按回车键,就进入了编辑模式。在这个界面里,你可以直接用鼠标右键粘贴文本,敲两下回车后就会发送数据,也就是模拟了一次HTTP请求。

下面是两个最简单的HTTP请求,第一个在“:”后有多个空格,第二个在“:”前有空格。

  1. GET /09-1 HTTP/1.1
  2. Host: www.chrono.com
  3. GET /09-1 HTTP/1.1
  4. Host : www.chrono.com

第一个可以正确获取服务器的响应报文,而第二个得到的会是一个“400 Bad Request”,表示请求报文格式有误,服务器无法正确处理:

  1. HTTP/1.1 400 Bad Request
  2. Server: openresty/1.15.8.1
  3. Connection: close

5.常用头字段

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

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

对HTTP报文的解析和处理实际上主要就是对头字段的处理,理解了头字段也就理解了HTTP报文。

先讲几个最基本的头,看完了它们就应该能够读懂大多数HTTP报文了。

首先要说的是Host字段,它属于请求字段,只能出现在请求头里,它同时也是唯一一个HTTP/1.1规范里要求必须出现的字段,也就是说,如果请求头里没有Host,那这就是一个错误的报文。

Host字段告诉服务器这个请求应该由哪个主机来处理,当一台计算机上托管了多个虚拟主机的时候,服务器端就需要用Host字段来选择,有点像是一个简单的“路由重定向”。

例如试验环境,在127.0.0.1上有三个虚拟主机:“www.chrono.com”“www.metroid.net”和“origin.io”。那么当使用域名的方式访问时,就必须要用Host字段来区分这三个IP相同但域名不同的网站,否则服务器就会找不到合适的虚拟主机,无法处理。

User-Agent是请求字段,只出现在请求头里。它使用一个字符串来描述发起HTTP请求的客户端,服务器可以依据它来返回最合适此浏览器显示的页面。

但由于历史的原因,User-Agent非常混乱,每个浏览器都自称是“Mozilla”“Chrome”“Safari”,企图使用这个字段来互相“伪装”,导致User-Agent变得越来越长,最终变得毫无意义。

不过有的比较“诚实”的爬虫会在User-Agent里用“spider”标明自己是爬虫,所以可以利用这个字段实现简单的反爬虫策略。

Date字段是一个通用字段,但通常出现在响应头里,表示HTTP报文创建的时间,客户端可以使用这个时间再搭配其他字段决定缓存策略。

  1. Hypertext Transfer Protocol
  2. HTTP/1.1 200 OK
  3. Server: openresty/1.15.8.1
  4. Date: Wed, 28 Aug 2019 05:33:42 GMT
  5. Content-Type: text/html
  6. Content-Length: 334
  7. Last-Modified: Thu, 04 Jul 2019 09:08:01 GMT
  8. Connection: keep-alive
  9. ETag: "5d1dc1f1-14e"
  10. Accept-Ranges: bytes
  11. [HTTP response 1/1]
  12. [Time since request: 0.000727000 seconds]
  13. [Request in frame: 11]
  14. [Request URI: http://127.0.0.1/]
  15. File Data: 334 bytes

Server字段是响应字段,只能出现在响应头里。它告诉客户端当前正在提供Web服务的软件名称和版本号,例如在实验环境里它就是“Server: openresty/1.15.8.1”,即使用的是OpenResty 1.15.8.1。

Server字段也不是必须要出现的,因为这会把服务器的一部分信息暴露给外界,如果这个版本恰好存在bug,那么黑客就有可能利用bug攻陷服务器。所以,有的网站响应头里要么没有这个字段,要么就给出一个完全无关的描述信息。

比如GitHub,它的Server字段里就看不出是使用了Apache还是Nginx,只是显示为“GitHub.com”。

实体字段里要说的一个是Content-Length,它表示报文里body的长度,也就是请求头或响应头空行后面数据的长度。服务器看到这个字段,就知道了后续有多少数据,可以直接接收。如果没有这个字段,那么body就是不定长的,需要使用chunked方式分段传输。

小结

  • 1)HTTP报文结构就像是“大头儿子”,由“起始行+头部+空行+实体”组成,简单地说就是“header+body”;
  • 2)HTTP报文可以没有body,但必须要有header,而且header后也必须要有空行,形象地说就是“大头”必须要带着“脖子”;
  • 3)请求头由“请求行+头部字段”构成,响应头由“状态行+头部字段”构成;
  • 4)请求行有三部分:请求方法,请求目标和版本号;
  • 5)状态行也有三部分:版本号,状态码和原因字符串;
  • 6)头部字段是key-value的形式,用“:”分隔,不区分大小写,顺序任意,除了规定的标准头,也可以任意添加自定义字段,实现功能扩展;
  • 7)HTTP/1.1里唯一要求必须提供的头字段是Host,它必须出现在请求头里,标记虚拟主机名。

作者:王侦
链接:https://www.jianshu.com/p/83a1f1815184
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 502

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2