3.1 HTTP请求报文结构
请求和响应报文的结构如下:
下面是有关请求报文请求和响应的案例。
3.2 报文和主体差异
为了提高HTTP传输效率,在请求中可以通过HTTP请求报文和实体加工的方式对于报文原文进行“编码”,这里的编码并不是单指文本字符串,而是更抽象意义上的编码。
介绍具体的内容之前我们需要先分清楚两个术语:报文和实体。
报文:是HTTP通信中的基本单位,由8位组 字节流(octet sequence,其中octet为8个比特)组成,通过HTTP通信传输。
实体(entity):作为请求或响应的 有效载荷数据(补充项)被传输,其内容由实体首部和实体主体组成。
为了理解实体的概念,需要了解有效载荷是怎么一回事:
负载(英语:Payload):负载指的是需要传输的实体数据信息,这也是为什么叫数据实体的原因。当然也可以叫做信头与元数据,或称为开销数据,仅用于辅助数据传输。
头(header):指的是在一块数据存储或传输之际在头追加的数据,这些信息是对数据区的描述。
元数据(英语:metadata):……为描述其他数据信息的数据。
之所以划掉实体是因为术语实体(entity)被有效载荷(payload)代替,书中所提到2616版本很多解释已经被废弃了,现在**RFC 2616 **已经被 RFC 7230 、 7235 取代了。(Clarify entity / representation / variant terminology)
有关负载的解释
原文:
Replaced entity with payload and variant with representation. Cleaned up description of 204 status code (related to ticket #22) Rewrote section on Content-Location and refer to def in RFC2557.
另外维基上有一个关于生活当中“有效载荷”的术语解释,通过描述可以从侧面理解官方为什么突然要把实体的概念重新解释。
摘自维基百科“有效载荷”:有效载荷是飞机或运载火箭携带的物体。有时,有效载荷也指飞机或运载火箭能够承载的重量。根据任务的性质,载具的有效载荷可能是货物、乘客、机组人员、弹药、科学仪器或实验或其他设备。如果可以选择性携带,那额外的燃料也会被视为有效载荷的一部分,如空中加油任务。
个人认为负载(叫负荷也可以)这个解释要比实体这个解释好理解一些(实体稍显抽象),并且不会丢失实体本身的含义。
接着我们通过对比Chrome和Edge浏览器,发现在目前的版本中均存在负载的概念,过去的版本中实际上这部分内容被放到报文的请求实体 中,很显然这是不严谨的,在那个时候被称作实体。
当然这两年这部分悄悄做了调整,显然在后续RFC修订协议过程中这些浏览器也对于这些概念进行跟进,不知道有多少人关注过,嗯,又是一个小细节。
所以负载概念取代实体概念目的是防止混淆(因为确实很容易搞混),实际上实体也分为首部和其他信息,实体首部是对该负载的描述,而负载和其它一些信息(请求行/状态行、各种首部字段等等)组织成报文进行传输。
书中有这样的图帮助我们了解实体和报文的差别,这张图也能说明为什么很多解释会把报文和实体(有效负载)看做是订单和货物的关系。
更头疼的概念
实际上还用更容易混淆的概念,message body
和 payload body
。
根据 RFC 7230:
HTTP 报文的报文主体(message body)(如果存在的话)是用来运载请求或响应的有效载荷主体(payload body)的。除非应用了传输编码,报文主体等价于有效载荷主体。
换句话说只有在应用了传输编码的时候,负载=实体首部+实体主体,目前主要应用的传输编码是Transfer-Encoding: chunked
,也就是分块传输的去看下负载的概念会出现转变,否则可以简单看做是报文的请求Body。
HTTP报文的主体用于传输请求或响应的实体主体,对于主体的处理优化HTTP在后续的版本中实现了下面这些特性:
- 压缩传输
- 分块传输编码
- 多数据多对象集合
压缩传输
首先需要明确到的是压缩是在负载上面完成的,并且压缩需要保证信息不遗失的原样压缩,否则压缩不完整的数据会导致数据发生错误。
常见的压缩方式是下面几种,其中gzip是图片经常使用的压缩方式:
- `gzip(GNU zip)``
compress(UNIX 系统的标准压缩)
deflate(zlib)
identity(不进行编码)
压缩传输是有代价的,因为这个操作需要计算机完成,所以会增加服务器的工作量,不过这一点开销完全可以接受。
分块传输编码
实体主体分块的功能称为分块传输编码(Chunked TransferCoding),分块传输指的是传输编码会将实体内容拆分为多个块(chunck),也就是前文提到的Transfer-Encoding: chunked
。
需要注意在负载主体的最后一块会使用“0(CR+LF)”来标记块的大小。
多数据多对象集合
多数据多对象集主要包含如下内容:
mulitpart/form-data
:在 Web 表单文件上传时使用;mulitpart/byteranges
:状态码 206(Partial Content,部分内容)响应报文包含了多个范围的内容时使用;
需要使用多数据多对象集合,需要在HTTP中指定Content-Type
首部字段。
enctype 属性
多数据多对象集合的一个代表属性,主要的作用是告知服务器自己将会传输什么类型的数据。
最常见的多部分对象集合的实际应用就是使用 HTML 表单发送文件。文件是二进制数据(或被视为二进制数据),而所有其他数据都是文本数据。由于 HTTP 是一种文本协议,因此对处理二进制数据有特殊要求。
3.3 内容协商
内容协商比较典型的案例是国际化,内容协商有点类似转译,服务器和客户端之间需要协商出一种最为合适的“中间”语言进行交流,然后按照字符集和编码格式进行交互。
基准和判断的基准是下面这几个首部字段的信息:
Accept Accept-Charset Accept-Encoding Accept-Language Content-Language
比如下面的维基在请求请求首部中就用到了这些信息。
content-encoding: gzip content-language: zh content-length: 17396 accept-ch: Sec-CH-UA-Arch,Sec-CH-UA-Bitness,Sec-CH-UA-Full-Version-List,Sec-CH-UA-Model,Sec-CH-UA-Platform-Version
3.3.1 内容协商方式
内容协商的基本准则如下:
- 依靠客户端设置HTTP首部(也叫服务驱动内容协商或者说主动协商),内容协商最为标准的方式。
- 服务器返回300或406,代理驱动方式或者响应协商机制。
服务器驱动协商(Server-driven Negotiation)
由服务器端进行内容协商。服务端协商中客户端请求随同URL会发送一份消息头表明自己的倾向性,服务器按照这个倾向性选择合适的资源返回。
服务器驱动的优点是充分利用HTTP协议规范减少额外的行为,因为是内容协商而不是格式协商,决定权实际上还是在服务端这一边。
当然这样的优点导致的代价是服务端的复杂性增加,因为需要“猜测”客户端的信息,同时可能会导致客户端发送报文越来越复杂。
客户端驱动协商(Agent-driven Negotiation)
由客户端进行内容协商的方式,用户协商类似用户选择浏览器的类型自动进行切换。
注意客户端驱动如果服务端不能回应客户端的请求,会退化为 服务器驱动协商,客户端驱动为了获取自己想要的内容需要 第二次发送请求(第一次获取列表,第二次才是得到资源),可见客户端的驱动模式并不是一种常用的方式。
代理驱动型内容协商机制
针对透明代理的改良方案,代理驱动主要是解决服务端协商的比较显著的痛点:规模化问题。
所谓的规模化问题指的当服务端请求出现大量资源并且需要添加首部情况下,会出现请求体积膨胀并且精确信息的发送也带来信息泄露问题。
注意代理驱动和透明代理存在一定区别,它使用了HTTP协议自创建依赖就支持又称为响应代理机制的东西,这种机制也是和客户端驱动协商类似,返回资源列表给用户进行选择然后需要第二次请求获取需要的资源。而透明代理借用了 Vary首部完成协议兼容,有点类似“旁外招”。
所以代理驱动虽然减轻了服务端和客户端形成“中间商”参考的模式,但是也避免不了第二次请求的问题。
透明协商(Transparent Negotiation)
透明代理被代理驱动型内容协商机制取代。
透明协商机制试图从服务器上去除服务器驱动协商所需的负载,并用中间代理来代表客户端以使与客户端的报文交换最小化。
这是服务器驱动和客户端驱动的结合体,是由服务器端和客户端各自进行内容协商的一种方法。但是因为后续历史没被认可所以被遗弃。
透明协商在HTTP并没有提供相应的规范,所以HTTP/1.1规范中没有定义任何透明协商机制,但定义了Vary
首部,所以透明代理主要使用了Vary
这个额外的字段完成协议兼容。
Vary 响应首部是什么? 在HTTP1.1协议中被添加,是通过服务器响应给客户端协商内容的时候一并返回的,服务端最终使用了那个首部清单。 最大的受益者不是客户端反倒是缓存服务器,缓存服务器检查发现Vary字段之后启用透明协商机制委托传输。
缓存服务器是啥?请看这篇文章[[《网络是怎么样连接的》读书笔记 - 汇总篇]]中关于负载均衡概念的介绍。
Alternates 首部
同样没受到认可被遗弃。网上都搜不到啥资料,忽略即可。
3.4 小结
上面介绍了众多的 内容协商方式,实际上仔细观察现在的网站会发现服务器驱动协商和代理驱动型内容协商机制为主。
前者是WEB服务提供商可以根据用户的请求推送喜欢的内容,并且不需要二次发送请求节省带宽,适合绝大多数WEB用户,当然用户体验取决于服务端应用程序开发者的水平。
代理驱动型内容协商机制则多用于支持国际化的网站,比如一些大商城或者百科等,比较典型的比如Apple和维基百科等这些网站,提供了“建议”选项询问用户选择哪种语言进行浏览。
而客户端代理主动权掌握在用户手上,服务端无法把控的同时不利于商业推广,所以大部分WEB网站会“屏蔽”这种方式,另一方面代理驱动能减轻服务器压力同时兼容了客户端驱动的特点,所以被代理驱动取代也十分正常。
最后是透明代理,透明代理使用的“旁门左路”的自定义的协议不怎么通用的,所以被淘汰以及被代理驱动取代也很正常。
3.5 参考资料
3.5.1 负载的概念
前三个回答基本能透彻了解到HTTP协议后续的发展中为什么要替换实体的概念为负载,以及在语义定义的内容。
另外本文所有内容建议用“负载”代替“实体”的概念,不要再用“实体”去看待实体。与时俱进嘛。
3.5.2 内容协商概念参考
MDN上面有关内容协商更为详细的解释:developer.mozilla.org/zh-CN/docs/…