Http/1.1 (RFC 2616)阅读笔记

来源:互联网 发布:java入门教程下载 编辑:程序博客网 时间:2024/06/05 18:07

我们每天都在跟http打交道,却未必敢说对它了如指掌。最近花了点时间阅读了这份十年前的协议,借此机会分享感受较深的几点。

1.请求/响应链(Request and Response Chain)

如图,一则请求在最终抵达我们的应用服务器之前,可能会经过像A、B、C那样层层的代理服务器、缓存服务器的中转,请求和响应就像接力棒一样在整个网络中穿梭。这种结构给分布式缓存埋下了伏笔,也让我意识到CDN不是后人的发明,而是前人精心的设计。虽然缓存相关的语法破坏了HTTP语义的透明,但其对性能深厚的影响迫使协议的制定者们不惜将其“浮出水面”。Caching in http是专门的一章,主要介绍了两种模型,一是Expiration Model,二是Validation Model。前者即直接复用本地缓存的策略,彻底省去了网络上来回的开销;后者则在302等响应码中得以体现,确保内容最新的同时省去了重复传输的开销。

      request chain ----------> UserAgent -----v----- A -----v----- B - - - - - - C - - - - - - Orignal Sever      <--------- response chain

2.消息和实体(Message and Entity)

可能是因为我们的业务少有涉及原生的http文件上传,即使需要也大多出于性能和交互的考量转而采用flash(细节被封装),实体的概念几乎无人问津。
下面是我在stackoverflow.com中试图回答“What's an HTTP Entity”:

Here are 3 simple cases:

Case 1. You're uploading 3 files in a single request. Those 3 files are 3 entities. Each of them has its own Content-Type to indicate what kind of file it is.

Case 2. You're viewing a web page. Browser has downloaded an html file as the entity in the background. Since the page could be updated continuously, you may get a totally different entity later.

Case 3. You've got a 304 Not Modified. No entity has been transferred.

In a word, Entity is an optional payload inside an http message(either request or response), so it is a "part-whole" relation between Entity and Message.

简单说来,消息是面向传输的,而实体是面向业务的。当我们键入url浏览一张网页,其完整的html文件即作为实体被封装在响应的消息中传输回来。由于消息是载体,请求响应链上沿途的每一个经手的设备都要关心消息的各个属性,然而终端用户只关心实体的内容。

此前,我一直以为http协议限制了每次只能上传/下载单个文件,理由是HTML5之前的file uploader()不能多选;此外点击“下载”按钮后即使一下蹦出多个“另存为”对话框,也会认定是js在起作用。

然而3.7.2 Multipart Types一节中提到,multipart的目的是在单一消息中封装多个实体,因此除了Get和Head请求由于语法受限不含消息体,一次请求或响应中上传/下载多个文件是协议本身所支持的。
来看一次post请求上传多个文件的例子:http://www.cnblogs.com/kaixuan/archive/2008/01/31/1060284.html 
再来看一次响应中下载多个文件的例子(注意不是所有浏览器都支持,因为multipart在语义上是给MIME定义的电子邮件和http定义的post请求所用):http://stackoverflow.com/questions/1041542/how-to-download-multiple-files-with-one-http-request

3.头字段的分类

头字段(Header Fields,或简称Headers)有以下几种分类方式:

  • 分类一)以前只知道头字段可以分成请求专用、响应专用和通用三大类。
  • 分类二)前面提到,概念上分离了消息和实体后,有些头字段是消息专属(如Transfer-Encoding决定了消息的传输方式),有些则是实体专用(如某些“Content-”开头的,用于描述实体的类型、长度等)。且协议中建议消息头字段统一排在实体头字段之前。这样一来,概念上我们就有了消息头和实体头,从HTTP传输的内容来看,它们之间没有明显的边界,纯粹靠语义区分。
  • 分类三)13.5.1一节又给出了另一种分类方法:End-to-end 和 Hop-by-hop Headers。如前述请求响应链图中所示,A,B,C之间都由独立的连接维持,消息在穿透每一跳的过程中,甚至可以采用不同的传输方式,因此诸如某些表述传输方式的头字段是可以在跳与跳之间任意增删的。顾名思义,这些被称为Hop-by-hop Headers,而那些全程伴随直到终点的即End-to-end Headers。

分类二说明了消息头和实体头是平行的关系,然而消息主体(message-body)和实体主体(entity-body)却是包含关系:

message-body = Transfer-Encoding( Content-Encoding(entity-body) )

其中Transfer-Encoding是传输编码方式,如chunked模式,即块状分割后传输;而Content-Encoding是内容编码方式,如gzip压缩实体。
可见,实体主体是毛,消息主体是皮,两者受体和载体的关系可见一斑。

分类三则强调了某些头字段可以在中间环节增减。

4.chunked传输模式及如何确定消息长度

前面提到的chunked传输模式,是HTTP/1.1的一大特性。消息得以拆成小块,并逐块传输。优点是相对减轻了消息发送者缓冲的压力、允许其发送的内容在发送的同时动态产生,甚至长度不限。抛开兼容性和细节,可以简单将其消息结构大致描述为:
chunk-1,chunk-2,chunk-3...final-chunk,trailer
其中每一块chunk都标识了本块的长度;最后的trailer用于存放仅当内容彻底发送完后才能计算的头字段,如长度、md5等信息。

简述chunked后,再来看HTTP消息长度是如何确定的(译自4.4节):

传输长度transfer-length即消息主体的长度,且这是经过transfer-code传输编码后的结果。当出现消息体时,传输长度由下列因素决定(序号小的优先)

1、前述不应出现消息体的情况( 1xx, 204和304响应码或响应HEAD请求 ),都以整个头字段之后的第一个空行终止消息,并忽略头字段中所有的实体头。
(译注:不应出现消息体的情况。皮之不存毛将焉附,语义就不涉及实体,所以一并忽略实体头)

2、若Transfer-Encoding存在且其值不为“identity”,则传输长度以“chunked”模式为准,除非消息通过关闭连接来终止。
(译注:消息体分块传输的情况。)

3、若 Content-Length存在,其值既表示实体长度也表示传输长度的字节数。两者不等的情况下不应该发送 Content-Length(例如已经应用了Transfer-Encoding时)。若一条消息同时包含了Transfer-Encoding和Content-Length,后者应当被忽略。
(译注:消息体与实体主体相同,未经传输编码转换的情况。)

4、若消息采用的媒体类型(media type)为"multipart/byteranges",且传输长度未能以上述方式指明,那么这种自分割的媒体类型已经定义了如何确定传输长度。对客户端而言,发送这种格式前应该确认接收者有能力解析;对服务端而言,收到一个由HTTP 1.1客户端发来的含有Range头字段且指定了multiple byte-range 的消息,即说明该客户端有能力解析针对该格式的响应。

Range头可能被1.0的代理转发,它对“multipart/byteranges”一无所知。服务器必须按本节1、3、5条所述对消息进行分割。

5、靠服务端关闭连接来确定。(关闭连接不能用来表示请求主体的结束,因为关闭后服务端无法发送响应了)

为了和HTTP1.0的应用兼容,所有附带了消息主体的HTTP/1.1请求必须包含有效的Content-Length,除非能确定服务端是1.1兼容的。若请求附带消息体但未给出Content-Length,服务器应该在无法推断消息长度时响应400(bad request),或者411 (length required)表示服务器期望收到有效的Content-Length。

所有HTTP1.1应用在接收实体时必须支持chunked作为传输编码,以应对消息长度无法预知的场景。

消息不能既包含Content-Length头,又指定非identity的传输编码方式。上述情况下必须忽略Content-Length。(译注:同上述第3点)

5.pipeline为何不能落地

8.1.2.2一节介绍了流水线技术(pipeline,有别于facebook的BigPipe,后者其实是chunked模式的应用层体现)。协议中提到,支持长连接的客户端可以复用单个连接,不等响应返回连续发出多个请求,由服务器保证响应逐个返回时的顺序与请求一致。这种方式显然比每个请求占用一个连接来得高效,而我在实践过程中,却从未观察到。

原来现实环境中,请求、响应链沿途不少的服务器和代理不支持流水线,会把后续的请求吃掉;此外,由于要保证响应返回的顺序,会造成队头阻塞(Head-of-line blocking)的问题,即靠前的响应一旦阻塞,会耽误后续的响应发送。由于上述两个原因,当代浏览器从未将流水线全面铺开,而是普遍把并发连接数从协议规定的2提升到了6。

相比之下,SPDY协议采用了多路复用(multiplexing)技术,请求的发送类似于pipeline,但允许乱序返回,且序列上的各个响应可以分块后间歇交错地返回。参考链接

1 0
原创粉丝点击