HTTP协议漫谈 - HTTP协议历史和报文结构

前言

去年下半年以来各种俗事缠身,所以有段时间没有更新博客了。过完年回来事情不多,项目需求也比较少,又可以愉快的写博客了(≧∇≦)ノ

这几天在网络上搜罗了一些HTTP协议的相关知识,并对这些知识做了一番整理,由于内容较多,预计会写成数篇博客。文章中大部分内容都来源于网络,其中也加入了一些自身的理解。对有明确来源的都会给出参考链接,但由于精力有限,无法查证每个链接的最原始的出处,所以有些链接可能并非是原作者发表的。由于大部分都是一些理论知识,来源也都是网络上的各种资料,加上自身理解能力和知识面的局限,可能会有一些错误,所以如果在阅读时发现有一些谬误,欢迎各种看官指正。

HTTP协议历史和版本

HTTP的诞生

HTTP协议最早由万维网之父Tim Berners-Lee提出。

1989年3月在CERN担任研究员的Tim提交了一个提案《关于信息管理的一份提案》(Information Management: A Proposal),提案中描绘了其对万维网(World Wide Web)最初的设想,他希望在CERN内部建立这样的一个网络,满足内部信息交换的需求。不过这份提案并没有被CERN所重视,好在当时Tim的经理仍然给了他一段时间来实现这个方案。

到了1990年10月,Tim完成了万维网三大基础技术的设计:命名方案(URI),通信协议(HTTP)和用来表示信息的标记语言(HTML)。根据这些设计,他实现了一个webserver (httpd)和一个浏览器(WorldWideWeb.app) 。到1990年底,第一个网页已经能够在CERN内部的internet上浏览了。

关于Tim本人和他创立万维网的一些故事可以参考文章如何评价万维网之父Tim Berners-Lee 获得2016年度图灵奖?

HTTP/0.9

在1991年,Tim根据之前的实现,写了一篇关于HTTP协议的文章,这篇文章后来被看成是HTTP/0.9版本,但实际上这篇文章很难称得上是一个标准的协议,它并非出自某个标准化组织,只是Tim的个人作品,用来解释其之前实现的程序中的通信过程。不过鉴于Tim对万维网的开创性贡献,将他的这篇关于HTTP协议的文章作为HTTP的第一版标准,也是无可厚非的。这篇文章可以在https://www.w3.org/Protocols/HTTP/AsImplemented.html找到,但这似乎是后来重新整理过的,并非是原始版本。这篇文章相当简单,只有短短的四小节。它规定了HTTP使用的TCP/IP连接,HTTP请求只有一个请求行,只有一个GET方法加上请求的URI。HTTP响应则直接返回HTML文本,没有状态码,所以也没有办法区分错误消息和正常的文本。

HTTP/1.0

在这之后万维网经历了一段快速发展而又十分混乱的时期。由于Tim最初版本的HTTP和HTML并不完善,无法满足各种应用场景,因此很多公司和组织在其基础上做了各种扩展,但彼此并不兼容。到1994年,Tim离开了CERN成立了W3C组织,致力于HTML的标准化工作。同年,IEFT成立了HTTP工作组(HTTP Working Group: HTTP-WG),研究改进HTTP协议。HTTP-WG考察了当时市面上已有的各种HTTP协议实现,综合了其中的一些常见用法,终于在1996年5月发布了RFC 1945,这就是现在HTTP/1.0。值得注意的是RFC 1945现在的状态是INFORMATIONAL(区别于INTERNET STANDARD,PROPOSED STANDARD和DRAFT STANDARD,关于Internet Standard的标准化过程可以参见维基百科Internet Standard),所以它并非是一个正式的互联网标准。

HTTP/1.0在HTTP/0.9的基础上做了大量的扩充和改进。增加了请求头域和响应头域,增加了HEAD和POST方法,响应对象不再局限于HTML文本,支持长连接和缓存机制等等。

HTTP/1.1

在RFC 1945发布半年多之后,即1997年1月,HTTP/1.1的第一个版本RFC 2068正式发布,这是一个PROPOSED STANDARD。1999年6月,HTTP/1.1的第二个版本RFC 2616发布并取代了RFC 2068。可以看到这段时间HTTP协议的发展是很快的,在RFC 2616发布后,HTTP协议进入一个相对稳定的时期。到2014年,IETF发布了6个新的RFC文档用来取代RFC 2616, 这6个文档的编号分别为7230, 7231, 7232, 7233, 72347235。它们将原先的RFC 2616拆分成了6个部分。这6个RFC文档目前都是PROPOSED STANDARD。

这个6个文档的内容划分如下。
这里写图片描述

RFC 7230-7235在RFC 2616的基础上做了大量内容上的调整,不过大部分都是结构,名词和表述性的修改,原先的语法和语义部分没有太大变化。目前网上的大部分资料仍然是基于RFC 2616的,这里有些名词和表述已经被废弃或修改了,但这一般并不影响阅读和对协议的理解。后文的表述中也可能会同时存在新旧RFC文档的内容,部分表述上做了区分。

HTTP/2

2015年5月RFC 7540发布,这也是HTTP协议目前的最新版本HTTP/2,该RFC文档目前同样是PROPOSED STANDARD。时间回退到半年前,在2014年10月28日,W3C的HTML工作组发布了HTML5的正式推荐标准,完成了长达八年的HTML5的标准化工作。HTTP和HTML,万维网的两大基础技术,在经过多年的沉寂后终于完成了各自的大更新。

有意思的是这里的版本号并没有和之前版本一样,采用2.0这样的大版本.小版本的形式,维基百科HTTP/2中介绍最初版本命名确实是HTTP/2.0,但是后来去掉了,至于去掉的原因,在HTTP/2 FAQ中有提到,认为旧的大小版本机制造成了很多混乱,但没有指明具体是什么混乱。

HTTP/2的主要目标在于提高传输性能,实现低延迟和高吞吐量。HTTP/2没有改动旧的HTTP版本的语义,方法、状态码、URI 和头部信息。所以通常认为HTTP/2是HTTP/1.1的扩展,而不是替代。HTTP/1.1对应的RFC文档中的大部分内容对HTTP/2也是适用的,RFC 7540中也没有将这部分内容重新编排,只是在涉及到这部分内容时,给出了原先RFC文档中的链接。关于HTTP/2的更多信息参见HTTP/2 简介

HTTP/2协议本身并不要求必须加密传输,它可以基于TLS实现加密传输(HTTP/2 over TLS: h2),也可以使用明文传输(HTTP/2 without TLS: h2c),然而出于推广https的需要,以及一些技术上的考虑,目前所有的浏览器都不支持h2c。所以如果要支持HTTP/2,必须基于TLS来部署,这也在一定程度上限制了HTTP/2的普及。

此外,HTTP/2在发布后一直争议不断,关于HTTP/2的一些争议可以参见文章维基百科HTTP/2中Criticisms一节,The HTTP/2 Protocol: Its Pros & Cons and How to Start Using ItHTTP/2: In-depth analysis of the top four flaws of the next generation web protocol

由于种种原因,尽管HTTP/2发布后不久,主流的浏览器和服务端程序就已经支持,但在业界的普及率始终不高,根据w3techs的统计,到今天(2018/03/07)使用HTTP/2的网站只有24.2%,但使用HTML5的网站则达到了86.7%。

这里写图片描述
这里写图片描述

关于HTTP历史和版本的其他信息可以参考文章 History of the WebHistory of the World Wide WebBrief History of HTTP

HTTP/3

最近有关HTTP/3协议的新闻开始流传,于是也去了解了一下。介绍“HTTP/3”之前不得不先说下QUIC协议。

QUIC是Quick UDP Internet Connections的缩写,它是一个传输层协议,最早是由Google在2013年提出。大家都知道目前主流的传输层协议有两个:TCP和UDP,TCP协议是面向连接的,在实际通信前需要建立握手,同时还在每个数据包中增加了序列号和校验和以便接收端对数据进行校验,一旦通信过程中发现了数据包错误,需要对错误的数据包进行重传,这会阻塞这个TCP连接下其他有效数据包的传输,直到这个错误的数据包被成功传递到对方,其他数据包才可以继续传输。此外TCP协议并不能理解数据的内容,它不支持对数据的加密等操作,如果需要支持加密,需要在TCP上层协议中增加,例如TLS协议。这要求通信的双方都实现这个上层协议,且上层协议在TCP连接建立后还需要额外的握手操作,以完成上层协议的协商过程,一旦TCP连接被意外中断,上述所有过程又得重来一遍。这些都导致TCP协议的效率比较低,Google提出QUIC协议的主要目标就是减少基于TCP通信带来的延迟和其他开销。为此,QUIC协议不再采用面向连接的机制,而是基于UDP协议,采用了UDP一样的面向无连接的过程。然而UDP是不可靠的,没有纠错和重传机制。QUIC协议在UDP的基础上做了大量改进,试图以UDP的效率,提供类似TCP的可靠性。有关QUIC协议的一些细节可以参见https://ma.ttias.be/googles-quic-protocol-moving-web-tcp-udp/ 和 https://docs.google.com/document/d/1jdKEQMlM7ThDMDalFYFR_9-Yw91PhoBmkAPQcCicX3s/pub

Google在2015 年将QUIC提交到IETF进行标准化( https://tools.ietf.org/html/draft-tsvwg-quic-protocol-00 ),之后又经历了两个版本的修改,但这个Internet Draft (I-D)并没有被IESG所采纳。IETF在2016年成立了一个IETF QUIC工作组,虽然沿用了QUIC的名字,IETF QUIC工作组并没有采用Google的QUIC方案,而是自己又重新做了一套,个人觉得这个QUIC方案和Google的QUIC方案并没有什么本质的区别。同年IETF QUIC工作组提交了自己的I-D( https://datatracker.ietf.org/doc/draft-ietf-quic-http ),在这份草案中并没有提到任何Google之前的工作。

在10月份的时候Mark Nottingham(目前的 IETF QUIC工作组和 IETF HTTP工作组主席)提议将现有的HTTP-over-QUIC更名为HTTP/3,并在11月份的103会议上做了阐述。所以HTTP/3其实就是IETF版本的QUIC改名后的一个协议,虽然IETF版本的QUIC协议目前进展很快,但距离成为Internet Standard还有一段很长的路要走,而且即使QUIC成为了标准,它能否获得市场认可也是很难预料的。所以目前谈论HTTP/3似乎还为时尚早。

HTTP报文结构

HTTP数据单元

这里顺便复习了一下网络模型中各层的数据单元。在网络模型中,协议数据单元PDU(Protocol Data Unit)表示对等层次之间传递的数据单位。参照维基百科的描述,在OSI网络模型中,物理层PDU为比特(Bit),数据链路层PDU为帧(Frame),网络层PDU为数据包(Packet),传输层PDU对TCP协议来说称为数据段(Segment),对UDP协议来说称为(Datagram)。传输层以上的会话层,表示层和应用层的数据单元都统称为数据(Data)。

下图是维基百科OSI网络模型中的截图
这里写图片描述

这里关于传输层PDU的描述相当令人困惑,作为抽象网络模型的数据单元,PDU应当也是一个抽象的,和实现协议无关的术语,但是这里却将传输层的PDU和具体的协议关联在一起。查看了一些其他文章,有些文章中将传输层PDU称为Segment,不区分具体的协议。这里姑且采用这种说法,即物理层PDU为比特(Bit),数据链路层PDU为帧(Frame),网络层PDU为数据包(Packet),传输层PDU为数据段(Segment),会话层,表示层和应用层的PUD为数据(Data)。

HTTP协议属于应用层,所以按照之前的PDU定义,HTTP协议的数据单元应当叫做HTTP Data,即HTTP数据。但似乎HTTP协议一般都用报文(Message)来称呼其数据单元,并不使用Data。

以下是维基百科中HTTP协议的截图,可以看到其中HTTP发送和接收的消息都成为message。
这里写图片描述

HTTP报文结构(Message Format)

在RFC 2616中,将HTTP报文分为请求报文(request message)和响应报文(response message),并分别描述了它们的结构。以下是RFC 2616中关于请求报文和响应报文结构的描述。

HTTP请求报文包含三个部分:请求行(request line),请求头域(request header fields)和请求体(message body)。请求头域可以有多个,最后一个请求头域和请求体之间有一个空白行。有些文章的描述中,将空白行作为一个单独的部分,认为HTTP请求报文包含四个部分(维基百科HTTP协议)。本文采用三个部分的描述形式。

在一个HTTP请求中,请求行和请求头域都是必须有的,而请求体是可选的,但即使没有请求体,请求头域之后仍然需要有一个空白行CRLF。表示请求头域的结束。多个请求头域之间也用CRLF分割。

下图来源于博客文章HTTP请求、响应报文格式
这里写图片描述

HTTP响应报文也包含三个部分:状态行(status line),响应头域(response header fields)和响应体(message body)。响应头域可以有多个,最后一个响应头域和响应体之间有一个空白行。

在一个HTTP响应中,状态行和响应头域都是必须有的,而响应体是可选的,但即使没有响应体,响应头域之后仍然需要有一个空白行CRLF,表示响应头域的结束,多个响应头域之间也用CRLF分割。

下图同样来源于博客文章HTTP请求、响应报文格式
这里写图片描述

可以看到响应报文的结构和请求报文的结构是基本相同的,主要区别在于请求行和状态行的不同。请求体和响应体实际上是一样的,它们在RFC 2616都称为message body,这里之所以有两个名字,只是为了方便识别这个body究竟是请求报文中的,还是响应报文中的,但需要知道的是,请求体和响应体无论在语义上还是结构上都是完全相同的。

在RFC 2616中对header fields的表述有点混乱,它将消息头分为通用头(general-header),请求头(request-header),响应头(response-header)和实体头(entity-header)四种类型。HTTP协议中又预定义了一系列标准的消息头名字,每个名字都有其明确的语义。通用头指的是那些既可以作为请求头,又可以作为响应头使用的那些消息头,例如缓存控制Cache-Control,消息发送时间Date等。请求头只能用在HTTP请求的头域中,例如包含UA信息的User-Agent等。而响应头只能用在HTTP响应的头域中,例如表示服务器软件版本的Server等。在RFC 2616中还有一个实体(Entity)的概念,实体指的是要传输的对象,实体又包含两部分,实体头(entity body)和实体正文(entity message)。实体头指的是那些和实体相关联的属性组成的消息头,例如,实体的长度Content-Length,实体的类型Content-type等。实体头会放在请求头域和响应头域中,而实体正文则作为请求体或响应体放到HTTP报文中,也就是说,实体是一个包含了message body,以及头域中和message body属性相关的那部分消息头的一个逻辑上的概念。

在RFC 7230中,对报文结构的描述做了一定调整,不再区分请求报文结构和响应报文结构(只是不区分请求报文结构和响应报文结构,请求报文和响应报文还是区分的),而是将整个HTTP报文结构作为整体来描述。

在RFC 7230中,一个HTTP报文包含三个部分:起始行(start line),头域(header fields)和消息体(message body)。起始行根据是请求报文还是响应报文,又分为请求行(request line)和状态行(status line)。头域和消息体之间同样是有一个空白行CRLF。

可以看到在RFC 7230中的一个改变是将请求行和状态行合称为起始行。但更重要的改变是对头域和消息体结构描述的变化。在RFC 7230中,完全去掉了实体的概念,也就是说现在的消息体就是要传输的对象,无需再从头域中挑出一部分消息头,然后和消息体组合在一起,再脑补出一个对象来。同时还去掉了没有多大意义的消息头的分类。无论是请求头域还是响应头域都可以同等看待,大家都是一组名字和值组成的序列。

注意到RFC 7230中的这些变化都是表述上的变化,并不涉及实际结构和语义的改变。

起始行(Start Line)

如前所述,起始行是RFC 7230中增加的一个概念,RFC 7230中对起始行的结构描述为:start-line = request-line / status-line。也就是它表示一个请求行或一个状态行。

RFC2616中对HTTP请求行的结构描述为:Request-Line = Method SP Request-URI SP HTTP-Version CRLF
翻译过来就是:请求方法+空格+请求URI+空格+HTPP协议版本+回车换行。

这里和上面的配图中的表述稍有不一样,按照RFC文档的描述,请求方法后应当是URI,而不是URL。这里采用RFC文档中的表述,使用URI。但实际上使用URL也是没错的。

RFC2616中对HTTP状态行的结构描述为:Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
翻译过来就是:HTTP版本+空格+状态码+空格+状态码描述+回车换行。

请求行和状态行中HTTP版本的语义和表示方法都是一样的。HTTP协议版本的表示方式为"HTTP/" + 版本号。其中"HTTP/“是固定的字符串,区分大小写,也就是只能是"HTTP/”,而不能是"http/"或其他形式。版本号由两个十进制数字组成,中间用.分割,第一个数字为大版本号,第二个数字为小版本号。到目前为止,一共有四个可用的版本:0.9,1.0,1.1和2,结合前面的"HTTP/“就是"HTTP/0.9”,“HTTP/1.0”,“HTTP/1.1"和"HTTP/2”。目前应用最广泛的仍然是"HTTP/1.1"版本。关于HTTP版本的更多讨论参见RFC 7230 2.6节。

头域(Header Fields)

HTTP头域包含了请求或响应过程中需要用到的一些额外的信息,它通常包含一个名字和对应的值。在HTTP报文中,多个头域之间用CRLF(回车换行)来分割,一个头域的名字和值之间则用冒号:分割,值前面可以包含任意多个空白字符,一般用一个空格,也就是"名字: 值"的形式来表示,这些空白字符在解析时都会被自动忽略。在RFC 2616中允许一个头域的值跨越多行,只需要在每行的开头,也就是名字前加上至少一个空格(SP)或水平制表符(HT),但是这一条在RFC 7230中被标记为deprecated。

头域中的名字不区分大小写,也就是说cache-control,Cache-Control和Cache-control都是一样的,表示同一个含义,不过一般来说都采用每个单词首字母大写,其他字母小写的形式,对缓存控制就是Cache-Control。

头域的值一般区分大小写,值也可以没有,但即使没有值,名字后面的冒号也不能少。值前后的空格在解析时都会被移除,值中的多个连续的空白字符一般也会在解析时被替换成一个空格。

多个头域的名字如果不同,则它们之间没有先后顺序,但协议同时规定多个头域中允许出现重复的名字,如果有重复名字的头域,那么它们有先后顺序之分。

如果出现重复名字的多个头域,协议要求能够将它们组合成一个头域,组合的方式为,将这些头域的值按照顺序排列,中间用逗号分隔,这样组合之后的头域和原先的多个头域含义必须相同。

举例来说,如下几组头域,前三组是等价的,后面两组也是等价的,但前三组中任意一个和后两组中的任意一个都不等价。
这里写图片描述

HTTP报文大小限制

如前所述,一个HTTP报文包含起始行,头域和消息体,HTTP协议本身并没有对报文中任一部分的长度做限制,也就是说,理论上一个请求URI可以无限长,头域可以无限多,请求体可以无限大。但在实际场景下,请求URI的长度会受到浏览器的限制,如果在浏览器中输入过长的URL,那么浏览器会自动进行截断。而服务器出于安全性和效率的考虑,也会对头域和消息体的大小作出一定的限制。

后记

这篇文章是从2月28号开始写的,到今天正好是一周时间。原本是打算将所有内容写在一篇文章中,题目是"HTTP协议漫谈",也就是没有这里的副标题。一开始是资料查到哪里写到哪里,没有固定的提纲。但是随着资料越查越多,原先零散的内容也开始有了一些关联,并且有了一些结构化的东西。后来索性填充了一个大体的框架,然后不断补充内容。在这期间一直都在断断续续的查找资料,补充文章和修改之前写好的内容。但是到今天发现仍然有十多节的内容没有写,要全部写完估计还得需要不少时间,项目又开始有一些需求要做了。为了避免时间拖太长,也避免文章太长,所以就先把前面部分完善了,作为一篇博客发布,后面的部分预计还会分成四篇博客:请求方法,请求URI,头域和HTTPS。

下一篇会介绍HTTP请求方法的一些相关知识。

参考链接:

[1]: https://www.w3.org/History/1989/proposal.html
[2]: https://tools.ietf.org/html/rfc2068
[3]: https://tools.ietf.org/html/rfc2616
[4]: https://tools.ietf.org/html/rfc7230
[5]: https://tools.ietf.org/html/rfc7231
[6]: https://tools.ietf.org/html/rfc7232
[7]: https://tools.ietf.org/html/rfc7233
[8]: https://tools.ietf.org/html/rfc7234
[9]: https://tools.ietf.org/html/rfc7235
[10]: https://tools.ietf.org/html/rfc7540

[11]: https://en.wikipedia.org/wiki/OSI_model
[12]: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
[13]: https://en.wikipedia.org/wiki/History_of_the_World_Wide_Web
[14]: https://en.wikipedia.org/wiki/Internet_Standard
[15]: https://en.wikipedia.org/wiki/HTTP/2

[16]: http://blog.csdn.net/a19881029/article/details/14002273
[17]: https://webfoundation.org/about/vision/history-of-the-web/
[18]: https://www.zhihu.com/question/58037961/answer/155413736
[19]: http://www.infoq.com/cn/news/2014/06/http-11-updated
[20]: https://http2.github.io/faq/
[21]: https://www.upwork.com/hiring/development/the-http2-protocol-its-pros-cons-and-how-to-start-using-it/
[22]: https://www.imperva.com/docs/Imperva_HII_HTTP2.pdf
[23]: https://w3techs.com/

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页