Http报文格式浅析


  1. 有了第一篇关于http的介绍,相信大家对http报文已经有了一点点的认识,这一章将接着上一篇,分析一下http报文格式
  2. 之所以说是浅析,是因为http报文并没有那么的简单,很多内容不可能在这一篇就介绍完全,比如说是cookie 代理等相关知识,在http报文中都有所体现,所以这一章仅仅只能浅浅地分析一下http中最常见的几种字段内容。

好了,那么废话不多说,直接开始。。。。这一章将先从理论介绍一下http报文格式,再从具体的报文进行分析

  • Http协议是一个纯文本协议,所谓纯文本协议,我的理解就是所有头部数据都是ASCII码的文本内容,可以很容易的用肉眼识别,不像有些协议是二进制数据格式,很不容易分析。就比如底层的TCP协议,还有我所了解的8583报文以及openflow协议,这些都属于二进制数据协议,不能通过肉眼直接观察就可以大致明白其意思。

    Http请求报文

  • 我打算先接着下一章 初识Http协议 ,自己实现的python程序收到的浏览器发过来的请求,来分析一下Http报文请求。

Http请求由三大部分组成:

  1. 起始行(start line):描述请求的基本信息;
  2. 头部字段集合(header):使用key-value形式更详细地说明报文;
  3. 消息正文(entity):实际传输数据,它不一定是纯文本(html文件即是纯文本),可以是图片,视频等二进制数据。
    前两部分 起始行和头部字段 通常被称为请求头,header,消息正文称为实体,为了与header对应通过也被说成body。
    Http协议规定报文必须有头部,但可以没有body,在头部字段之后必须有一个空行,也就是”CRLF”,十六进制的 “0D0A”,ASCII码形式的 “\r\n”。
    一图胜千言,相信通过图片能更加直观容易的认识一下报文格式,http报文结构如下图:
    image

  • 好了,通过如上对http报文格式的介绍,我们再从上一章内容浏览器发送的请求入手,来具体分析一下http报文,上一章浏览器发送的报文如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    **************************************************
    GET / HTTP/1.1
    Host: 192.168.112.131:8080
    Connection: keep-alive
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    If-None-Match: W/"5d46de74-939"
    If-Modified-Since: Sun, 04 Aug 2019 13:32:36 GMT

    **************************************************
  • 首先起始行字段,对应的就是我们报文的第一行,”GET / HTTP/1.1”,而后边的以冒号分隔的,就是头部字段描述,key-value形式的,最后是一个空行也就是CRLF。
    请求行
    请求行简要的概述了客户端想要如何操作服务端的资源,请求行由三部分构成:

  1. 请求方法:是一个动词,如GET/POST,表示对资源的操作;
  2. 请求目标:通常是一个URI, 标记了请求方法要操作的资源;
  3. 版本号:表示报文使用的HTTP协议版本。
    这三个部分通常以空格分隔,最后要用CRLF换行表示结束。
    image
    如上浏览器发送的请求报文, GET代表了请求方法, /就是要操作的资源URI, HTTP/1.1代表了HTTP协议版本号

头部字段
头部字段是key-value的形式,key和value之间用”:”分隔,然后以CRLF换行表示字段结束,可以有多个头部字段。比如”Host: 192.168.112.131:8080”,这一行的key就是”Host”,value就是”192.168.112.131:8080”。
Http头部字段非常灵活,不仅可以使用标准的Host,Connection等协议规定的头,还可以任意自定义头,这就给http协议带来了无限的扩展可能。
使用头部字段需要注意如下几点:

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

请求行和头部字段加起来就构成了完整的Http请求头,如下图:

image

Http响应报文

  • 介绍完了Http请求报文,下面来介绍一下Http应答报文,应答报文是由服务器应答给客户端也就是浏览器的,由于我目前没有完整的web服务器以作测试,我选择使用Nginx来作为web服务器来进行实验,Nginx如何使用我暂且不多做任何解释,仅仅只解释一下简单的配置以及启动命令。
    Nginx配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    server {
    listen 8080;
    server_name localhost;

    ...

    location / {
    root html;
    }
    ...
    }

这里仅仅只列出了nginx.conf配置最为关键的信息,监听8080端口号,绑定了本地环回地址,location指定了”/“这个URI操作的默认路径为html目录(nginx安装完后自带html目录并且有几个默认的html文件)

启动Nginx服务器
可以进入到nginx根目录下的sbin目录,里边有一个nginx可执行程序,直接输入nginx运行,没有任何错误信息显示及运行成功。
至此,服务端配置完成,接下来我们就来发送请求给nginx服务器,使其回应http应答

  • 为了让大家更清晰的看清楚请求响应这一过程,我这里不选择用浏览器来使用发送http请求,而选择自己组织http请求报文,然后使用telnet这一工具发送并接受应答,这样子可以更加直观看到服务器的响应报文。

首先是组织一下要发送的请求报文
这里没有选择上边浏览器发送的报文,是因为浏览器会自带很多功能,例如压缩字段等。。。这样子有对我们观察报文内容会有影响,我组织的报文如下:

1
2
3
4
5
6
GET /index.html HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Language: zh-CN,zh;q=0.9


发送请求报文
首先我们使用telnet 127.0.0.1 8080与nginx服务器建立连接,然后把上边报文复制出来,记得带上最后一行的空行,然后再按回车。显示如下:
image
这样子一个完整的请求响应过程就完成了。
分析应答报文
应答报文截断如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
HTTP/1.1 200 OK
Server: nginx/1.14.0
Date: Wed, 04 Sep 2019 14:01:29 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 19 Feb 2019 12:40:41 GMT
Connection: keep-alive
ETag: "5c6bf949-264"
Accept-Ranges: bytes

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>


我们可以看到在我们请求报文之后,就是应答报文部分,从”HTTP/1.1 200 OK”这一行开始。其实Http报文的应答报文和请求报文格式很相似,也是起始行,头部字段,正文这一个格式,只不过起始行有个更准确的名字叫做状态行,如下图:
image

  • 状态行的意思就是服务器响应状态,和请求行一样,同样是有三部分构成:
    1. 版本号:表示报文使用的Http协议版本;
    2. 状态码:一个三位数,用代码的形式表示处理的结果,比如200是成功,500是服务器内部错误;
    3. 原因:作为状态码的补充,是更详细的解释文字,帮助人理解原因。
      以此来分析状态行,版本号就是”HTTP/1.1”,状态码是”200”, 原因描述是”OK”代表请求成功。

正文介绍

  1. 这里直接跳过了响应报文的字段介绍,是因为和请求报文的字段差不多,不过某些字段是属于响应特有的。可以看到这里的Body就是一个html文件,细心的人可能发现了上面的请求报文中GET后面的URI我改成了”/index.html”,以为客户端要请求的服务端资源就是这个URI中的内容,其实不做修改也没事,一般服务器都会默认解释”/“为默认的index.html文件,我这么做只是为了便于理解而已。还记得上面我对nginx服务器配置默认路径为html目录,那么通过此URI就回去html目录下寻找index.html文件,找打的话就把文件内容读出并加在响应头部字段后返回。
  2. 试想一下,如果我们使用的不是telnet工具,而是使用浏览器来请求,那么浏览器收到这个应答之后,会是什么效果?其实这也就是我们平时上网,最常见的静态html文件传输,浏览器会把收到的正文内容拿出来并显示内容在界面上。

好了,这样子也算对Http报文格式有了个大概的介绍,下一章将介绍一下Http请求方法