HTTP权威指南(Web基础)

136 阅读1小时+

HTTP(Hypertext Transfer Protocol,超文本传输协议)是在万维网上进行通信时所使用的协议方案。HTTP有很多应用,但最著名的是用于Web浏览器和Web服务器之间的双工通信。

第一部分 HTTP:Web的基础

第1章 HTTP概述

Web浏览器、服务器和相关的Web应用程序都是通过HTTP相互通信的。HTTP是现代全球因特网中使用的公共语言。

1. Web客户端与服务器是如何通信的

1.1. HTTP-因特网的多媒体信使

HTTP可以从遍布全世界的Web服务器上将各种信息块迅速、便捷、可靠地搬移到人们桌面上的Web浏览器上去。HTTP使用的是可靠的数据传输协议,因此能够确保数据在传输的过程中不会被损坏或产生混乱。

1.2. Web客户端和服务器

Web内容都是存储在Web服务器上的。Web服务器所使用的是HTTP协议,因此经常会被称为HTTP服务器。这些服务器存储了因特网中的数据,如果HTTP客户端发出请求,它们会提供数据。HTTP客户端和HTTP服务器共同构成了万维网的基本组件。最常见的客户端就是Web浏览器。Web浏览器向服务器请求HTTP对象,并将这些对象显示在你的屏幕上。

2. 资源来自何方

2.1. 资源

Web服务器是Web资源的宿主,Web资源是Web内容的源头。最简单的Web资源就是Web服务器文件系统中的静态文件。但资源不一定非得是静态文件,还可以是根据需要生成内容的软件程序。这些动态内容可以根据你的身份、所请求的信息或每天的不同时段来产生内容

2.1.1 媒体类型
  • MIME(Multipurpose Internet Mail Extension,多用途因特网邮件扩展)是为了解决在不同的电子邮件系统之间搬移报文时存在的问题。
  • HTTP也采纳了它,用来描述并标记多媒体内容。
  • Web服务器会为所有HTTP对象数据附加一个MIME类型。
  • 当Web浏览器从服务器取回一个对象时,会去查看相关的MIME类型,看看它是佛欧指导应该如何处理这个对象。
  • MIME类型是一种文本标记,表示一种主要的对象类型和一个特定的子类型。中间由一条斜杠来分隔
    • text/html HTML格式的文本文档
    • text/plain 普通的ASCII文本文档
    • image/jpeg JPEG格式的图片
    • image/gif GIF格式的图片
    • video/quicktime Apple的QuickTime电影
    • application/vnd.ms-powerpoint 微软的PowerPoint演示文件
2.1.2 URI

每个Web服务器资源都有一个名字,这样客户端就可以说明感兴趣的资源是什么了。服务器资源名被称为统一资源标识符(Uniform Resource Identifer,URI)。URI在世界范围内唯一标识并定位信息资源。URI有两种形式,分别称为URL和URN。

2.1.3 URL

统一资源定位符(URL)是资源标识符最常见的形式。URL描述了一台特定服务器上某资源的特定位置。可以明确说明如何从一个精确、固定的位置获取资源。大部分URL都遵循一种标准格式,这种格式包含三个部分:(现在几乎所有的URI都是URL)

  • 第一部分是方案(scheme),说明了访问资源所使用的协议类型。通常就是HTTP协议(http://)
  • 第二部分给出了服务器的因特网地址
  • 其余部分指定了Web服务器上某个资源
2.1.4 URN

统一资源名(URN)是作为特定内容的唯一名称使用的,与目前的资源所在地无关。使用这些与位置无关的URN,就可以将资源四处搬移。还可以通过URN用同一个名字通过多种网络访问协议来访问资源。URN仍处于试验阶段,缺乏支撑架构来解析资源的位置。

3. Web事务是怎样工作的

3.1. 事务

一个HTTP事务由一条请求命令(从客户端发往服务器的)和一个相应结果(从服务器发回客户端的)组成。这种通信是通过名为HTTP报文的格式化数据块进行的。

3.1.1 方法

HTTP支持几种不同的请求命令,这些命令被称为HTTP方法。每条HTTP请求报文都包含一个方法。这个方法会告诉服务器要执行什么动作。常见的HTTP方法:

HTTP方法描述
GET从服务器向客户端发送命名资源
PUT将来自客户端的数据存储到一个命名的服务器资源中去
DELETE从服务器中删除命名资源
POST将客户端数据发送到一个服务器网关应用程序
HEAD仅发送命名资源响应中的HTTP首部
3.1.2 状态码

每条HTTP响应报文返回时都会携带一个状态码。状态码是一个三位数字的代码,告知客户端请求是否成功,或者是否需要采取其他动作。伴随每个数字状态码,HTTP还会发送一条解释性的“原因短语”文本。包含文本短语主要是为了进行描述,所有的处理过程使用的都是数字码。常见的状态码:

HTTP状态码描述
200OK。文档正确返回
302Redirect(重定向)。到其他地方去获取资源
404Not Found(没找到)。无法找到这个资源
3.1.3 Web页面中可以包含多个对象

应用程序完成一项任务时通常会发布多个HTTP事务。一个“Web页面”通常并不是单个资源,而是一组资源的集合。

4. HTTP通信所使用的报文格式

4.1. 报文

HTTP报文是由一行一行的简单字符串组成的。HTTP报文都是纯文本,不是二进制代码,所以人们可以很方便地对其进行读写。从Web客户端发往Web服务器的HTTP报文称为请求报文。从服务器发往客户端的报文称为响应报文。HTTP请求和响应报文的格式很类似,包括以下三个部分:

4.1.1 起始行

报文的第一行就是起始行,在请求报文中用来说明要做些什么,在响应报文中说明出现了什么情况

4.1.2 首部字段

起始行后面有0个或多个首部字段。每个首部字段都包含一个名字和一个值,两者之间用冒号来分隔。首部以一个空行结束。

4.1.3 主体

空行之后就是可选的报文主体了,其中包含了所有类型的数据。请求主体中包含了要发送给Web服务器的数据;响应主体中装载了要返回给客户端的数据。起始行和首部都是文本形式且都是结构化的,而主体中可以包含任意的二进制数据(如图片、视频、音轨、软件程序),当然也可以包含文本。

5. 底层TCP网络传输

5.1. 连接

5.1.1 TCP/IP

HTTP是个应用层协议。HTTP无需操心网络通信的具体细节,把联网的细节都交给了通用、可靠的因特网传输协议TCP/IP。TCP提供了:

  • 无差错的数据传输
  • 按序传输
  • 未分段的数据流(可以在任意时刻以任意尺寸将数据发送出去)

因特网自身就是基于TCP/IP的,TCP/IP是全世界的计算机和网络设备常用的层次化分组交换网络协议集。只要建立了TCP连接,客户端和服务器之间的报文交换就不会丢失、不会被破坏,也不会在接收时出现错序了。

5.1.2 连接、IP地址及端口号

在HTTP客户端向服务器发送报文之前,需要用网际协议(Internet Protocol,IP)地址和端口号在客户端和服务器之间建立一条TCP/IP连接。在TCP中,需要知道服务器的IP地址以及与服务器上运行的特定软件相关的TCP端口号。通过URL就可以获取:

  • http://207.200.83.29:80/index.html 使用机器的IP地址及端口号
  • http://www.netscape.com:80/index.html 没有数字形式的IP地址,使用文本形式的域名,或者称为主机名。主机名是IP地址比较人性化的别名。可以通过一种称为域名服务(Domain Name Service,DNS)的机制方便的将主机名转换为IP地址。
  • http://www.netscape.com/index.html 如果HTTP的URL中没有端口号,默认是80端口。

浏览器通过HTTP显示位于远端服务器中某个简单的HTML资源的过程:

  1. 浏览器从URL中解析出服务器的主机名
  2. 浏览器将服务器的主机名转换成服务器的IP地址
  3. 浏览器从URL中解析出端口号
  4. 浏览器建立一条与Web服务器的TCP连接
  5. 浏览器向服务器发送一条HTTP请求报文
  6. 服务器向浏览器回送一条HTTP响应报文
  7. 关闭连接,浏览器显示文档
5.1.3 使用Telnet和netcat
  • Telnet程序可以将键盘连接到某个目标TCP端口,并将此TCP端口的输出回送到显示屏上。可以通过Telnet程序直接与Web服务器进行对话,可以打开一条到某台机器上某个端口的TCP连接,然后直接向那个端口输入一些字符。Web服务器会将Telnet程序作为一个Web客户端来处理,所有回送给TCP连接的数据都会显示在屏幕上。Telnet几乎可以连接所有的TCP服务器,包括HTTP服务器。可以很好地模拟HTTP客户端,但不能作为服务器使用
  • nc(netcat)作为更灵活的工具,可以很方便地操纵基于UDP和TCP(包括HTTP)的流量,还可以为其编写脚本。

6. 不同的HTTP协议变体

6.1. 协议版本

HTTP应用程序要尽量强健地处理各种不同的HTTP协议变体。目前仍在使用的版本如下:

6.1.1 HTTP/0.9

HTTP的1991原型版本。这个协议有很多严重的设计缺陷,只应该用于与老客户端的交互。只支持GET方法,不支持多媒体内容的MIME类型、各种HTTP首部,或者版本号。

6.1.2 HTTP/1,0

第一个得到广泛应用的HTTP版本。添加了版本号、各种HTTP首部、一些额外的方法、以及对多媒体对象的处理。

6.1.3 HTTP/1.0+

20世纪90年代中叶,很多流行的Web客户端和服务器都向HTTP中添加各种特性。其中很多特性成为非官方的事实标准,这种非正式的HTTP扩展版本通常称为HTTP/1.0+

6.1.4 HTTP/1.1

重点关注校正HTTP设计中的结构性缺陷,明确语义,引入重要的性能优化措施,并删除一些不好的特性。还包含了对20世纪90年代末正在发展中的更复杂的Web应用程序和部署方式的支持。是当前使用的HTTP版本。

6.1.5 HTTP-NG(又名HTTP/2.0)

是HTTP/1.1后继结构的原型建议,重点关注性能的大幅优化,以及更强大的服务逻辑远程执行框架。它的研究工作终止于1998年。

7. 因特网上安装的大量HTTP架构组件中的一部分

7.1. Web的结构组件

除了Web浏览器和Web服务器这两个Web应用程序之外,还有其他一些比较重要的应用程序

7.1.1 代理

位于客户端和服务器之间的HTTP中间实体。HTTP代理服务器是Web安全、应用集成以及性能优化的重要组成模块。接收所有客户端的HTTP请求,并将这些请求转发给服务器(可能会对请求进行修改之后转发)。出于安全考虑,通常会将代理作为转发所有Web流量的可信任中间节点使用,代理还可以对请求和响应进行过滤。

7.1.2 缓存

HTTP的仓库,使常用页面的副本可以保存在离客户端更近的地方。Web缓存(Web cache)或代理缓存(proxy cache)是一种特殊的HTTP代理服务器,可以将经过代理传送的常用文档复制保存起来,下一个请求同一文档的客户端就可以享受缓存的私有副本所提供的服务了。

7.1.3 网关

连接其他应用程序的特殊Web服务器。网关(gateway)是一种特殊的服务器,作为其他服务器的中间实体使用。通常用于将HTTP流量转换成其他的协议。网关接受请求时就好像自己是资源的源端服务器一样。例如,一个HTTP/FTP网关会通过HTTP请求接收对FTP URI的请求,但通过FTP协议来获取文档。得到的文档会被封装成一条HTTP报文,发送给客户端。

7.1.4 隧道

对HTTP通信报文进行盲转发的特殊代理。隧道是建立起来之后,就会在两条连接之间对原始数据进行盲转发的HTTP应用程序。HTTP隧道常用来在一条或多条连接上转发非HTTP数据,转发时不会窥探数据。常见用途是通过HTTP连接承载加密的安全套接字层(SSL,Secure Sockets Layer)流量,这样SSL流量就可以穿过只允许Web流量通过的防火墙了。

7.1.5 Agent代理

发起自动HTTP请求的半智能Web客户端。用户代理是代表用户发起HTTP请求的客户端程序。所有发布Web请求的应用程序都是HTTP Agent代理。Web浏览器就是一种HTTP Agent代理,还有很多其他类型。如会自己在Web上闲逛的自动用户代理,这些自动代理的名称是“网络蜘蛛”或“Web机器人”,它们搜集信息以构建有效的Web内容档案。

8. 万维网信息

8.1. www.w3.org

W3C是Web的科技驱动团队,W3C致力于促进Web演化的互操作性技术(规范、准则、软件及工具)研究。W3C站点是一个包含了Web技术简介和详细文档的宝库。

8.2. www.ietf.org/rfc/rfc2396…

RFC 2396 是URI和URL的详细参考。“统一资源标识符:通用语法”

8.3. www.ieft.ofg/rfc/rfc2141…

RFC 2141 写于1997年的描述URN的规范。“URN的语法”

8.4. www.ietf.org/rfc/rfc2046…

RFC 2046 是为进行多媒体内容管理而定义的多用途因特网邮件扩展标准的五部因特网规范中的第二部。“MIME第二部分:媒体类型”

8.5. www.wrec.org/Drafts/draf…

这个因特网草案(因特网Web复制和缓存分类法)解释了Web结构组件中的标准术语

第2章 URL与资源

URL就是因特网资源的标准化名称。URL指向一条条电子信息片段,告诉你它们位于何处,以及如何与之进行交互

1. URL语法,各种URL组件的含义及其所做的工作

1.1. 浏览因特网资源

  • URL是浏览器寻找信息时所需的资源位置。
  • URL是人们对HTTP和其他协议的常用访问点:将浏览器指向一个URL,浏览器就会在幕后发送适当的协议报文来获取人们所期望的资源。
  • URI是更通用的资源标识符,由URL和URN两个主要的子集构成。
  • URL提供了一种统一的资源命名方式。大多数URL都有同样的结构:“方案://服务器位置/路径”。

1.1. URL的语法

网络资源可以通过各种不同的方案来访问,因此URL语法会随方案的不同而有所不同。大多数URL方案的URL语法都建立在这个由9个部分构成的通用格式上:<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>

组件描述默认值
方案scheme访问服务器以获取资源时要使用哪种协议
用户user某些方案访问资源时需要的用户名匿名
密码password用户名后面可能要包含的密码,中间用冒号分隔<E-mail地址>
主机host资源宿主服务器的主机名或点分IP地址
端口port资源宿主服务器正在监听的端口号。很多方案都有默认端口号每个方案特有
路径path服务器上资源的本地名,由一个斜杠将其与前面的URL组件分隔开
参数params某些方案会用这个组件来指定输入参数,参数为名/值对。可以包含多个参数字段,它们之间以及与路径的其余部分之间用分号分隔
查询query某些方案会用这个组件传递参数以激活应用程序,用问号将其与URL其余部分分隔开
片段frag一小片或一部分资源的名字。引用对象时,不会将frag字段传送给服务器;这个字段是在客户端内部使用的。通过井号将其与URL其余部分分隔
1.1.1 方案-使用什么协议

方案实际上是规定如何访问指定资源的主要标识符,告诉负责解析URL的应用程序应该使用什么协议。方案组件必须以一个字母符号开始,由第一个:符号将其与URL的其余部分分隔开,方案名是大小写无关的。

1.1.2 主机与端口
  • 主机组件标识了因特网上能够访问资源的宿主机器。可以用主机名或者IP地址来表示。
  • 端口组件标识了服务器正在监听的网络端口。对下层使用了TCP协议的HTTP来说,默认端口号为80
1.1.3 用户名和密码

很多服务器都要求输入用户名和密码才会允许用户访问数据。FTP服务器就是这样一个常见实例。

  • ftp://ftp.prep.ai.mit.edu/pub/gnu 没有用户或密码组件,如果某应用程序使用的URL方案要求输入用户名和密码,而用户没有提供,通常会插入一个默认的用户名anonymous(匿名用户),和一个默认的密码(IE发送IEUser,Netscape Navigator会发送mozilla)
  • ftp://anonymous@ftp.prep.ai.mit.edu/pub/gnu 指定为anonymous的用户名,用@字符将用户和密码组件与URL其余部分分隔开
  • ftp://anonymous:my_passwd@ftp.prep.ai.mit.edu/pub/gnu
  • http://joe:joepasswd@www.joes-hardware.com/sales_info.txt 用户和密码组件之间用:分隔
1.1.4 路径

路径通常很像一个分级的文件系统路径。是服务器定位资源时所需的信息。可以用/字符将HTTP URL的路径组件划分成一些路径段,每个路径段都有自己的参数组件

1.1.5 参数

很多方案需要更多的信息才能工作,负责解析URL的应用程序需要这些协议参数来访问资源,否则,服务器可能就不会为请求提供服务,或者提供错误的服务。

  • ftp://ftp.prep.ai.mit.edu/pub/gnu;type=d 参数type,值为d。
  • http://www.joes-hardware.com/hammers;sale=false/index.html;graphics=true HTTP URL的路径组件可以分成若干路径段,每段都可以有自己的参数。
1.1.6 查询字符串

很多资源都是可以通过提问题或进行查询来缩小所请求资源类型范围的。?右边的内容是查询组件,URL的查询组件和标识网关资源的路径组件一起被发送给网关资源。按照常规,很多网关都希望查询字符串以一系列“名/值”对的形式出现,名值对之间用字符“&”分隔:http://www.joes-hardware.com/inventory-check.cgi?item=12731&color=blue

1.1.7 片段

有些资源类型,除了资源级之外,还可以做进一步的划分。为了引用部分资源或资源的一个片段,URL支持使用片段组件来表示资源内部的片段。片段挂在URL的右手边,最前面有一个字符“#”,如:http://www.joes-hardware.com/tools.html#drills。HTTP服务器通常只处理整个对象,而不是对象的片段,客户端不能将片段传送给服务器。浏览器从服务器获得了整个资源之后,会根据片段来显示你感兴趣的那部分资源。

2. URL的快捷方式,包括相对URL和自动扩展URL

Web客户端可以理解并使用几种URL快捷方式。

2.1. 相对URL

  • 相对URL是在某资源内部指定一个资源的便捷缩略方式。
  • 绝对URL中包含有访问资源所需的全部信息。
  • 相对URL是不完整的,需要相对于另一个称为基础的URL解析出获取访问资源所需的全部信息。
  • 使用缩略形式的相对URL语法,HTML的编写者就可以省略URL中的方案、主机和其他一些组件了。这些组件可以从它们所属资源的基础URL中推导出来。相对URL只是URL的片段或一小部分,处理URL的应用程序要能够在相对和绝对URL之间进行转换。相对URL为保持一组资源的可移植性提供了一种便捷方式,这样就可以实现在其他服务器上提供镜像内容之类的功能。
2.1.1 基础URL

转换处理的第一步就是找到基础URL。可以来自以下几个不同地方:

  • 在资源中显示提供:比如,HTML文档中可能会包含一个定义了基础URL的HTML标记<base>,通过它来转换那个HTML文档中的所有相对URL
  • 封装资源的基础URL:如果在一个没有显示指定基础URL的资源中发现一个相对URL,可以将它所属资源的URL作为基础。
  • 没有基础URL:这通常意味着有一个相对URL,但有时可能只是一个不完整或损坏了的URL
2.1.2 解析相对引用

下一步要做的就是将相对URL和基础URL划分成组件段,通常会称做分解URL。只要将基础和相对URL划分成了组件,就可以应用算法来完成转换了。

2.2. 自动扩展URL

有些浏览器会在用户提交URL之后,或者在用户输入的时候尝试着自动扩展URL。这些“自动扩展”特性有以下两种方式:

2.2.1 主机名扩展

只要有一些小提示,浏览器通常就可以在没有帮助的情况下,将输入的主机名扩展为完整的主机名。如:输入yahoo,浏览器会自动在主机名中插入www..com。如果找不到匹配的站点,有些浏览器在放弃之前尝试几种扩展形式。但这些主机名扩展技巧可能会为其他一些HTTP应用程序带来问题,比如代理。

2.2.2 历史扩展

将以前用户访问过的URL历史存储起来。当输入URL时,它们就可以将输入的URL与历史记录中URL的前缀进行匹配,并提供一些完整的选项供你选择。与代理共同使用时,URL自动扩展的行为可能会有所不同。

3. 各种令人头疼的字符

  • URL是可移植的。要统一地命名因特网上所有的资源,就意味着要通过各种不同的协议来传送这些资源。这些协议在传输数据时都会使用不同的机制,所以URL需要可以通过任意协议安全地传输。有些协议,比如SMTP由7位二进制编码造成的传输方法会剥去一些特定的字符,为了避开这些问题,URL智能使用一些相对较小的、通用的安全字母表中的字符。
  • 还希望URL可供人类阅读,因此,不可见、不可打印的字符(包括空格符)不能在URL中使用。
  • URL还得是完整的,有时人们可能会希望URL中包含除通用的安全字母表之外的二进制数据或字符。因此,需要有一种转义机制,能够将不安全的字符编码为安全字符,再进行传输。

3.1. URL字符集

  • US-ASCII字符集,使用7位二进制码来表示英文打字机提供的大多数按键和少数用于文本格式和硬件通知的不可打印控制字符。
  • 转移序列集,通过转移序列就可以用US-ASCII字符集的有限子集对任意字符值或数据进行编码了。

3.2. 编码机制

编码机制通过一种“转义”表示法来表示不安全字符,这种转义表示法包含一个百分号,后面跟着两个表示字符ASCII码的十六进制数。一些编码字符示例:

字符ASCII码示例URL
~126(0x7E)www.joes-hardware.com/%7Ejoe
空格32(0x20)www.joes-hardware.com/more%20tool…
%37(0x25)www.joes-hardware.com/100%25satis…

3.3. 字符限制

URL中有一个字符被保留起来,有着特殊的含义。有些字符不在定义的US-ASCII可打印字符集中。有些会与某些因特网网关和协议产生混淆,因此不赞成使用。下表列出了一些字符,将其用于保留用途之外的场合时,要在URL中对其进行编码。

字符保留/受限
%保留作为编码字符的转义标志
/保留作为路径组件中分隔路径段的定界符
.保留在路径组件中使用
..保留在路径组件中使用
#保留作为分段定界符使用
?保留作为查询字符串定界符使用
;保留作为参数定界符使用
:保留作为方案、用户/口令,以及主机/端口组件的定界符使用
$ , +保留
@ & =在某些方案的上下文中有特殊的含义,保留
{ } \ ^ ~ [ ] '由于各种传输用户代理,比如各种网关的不安全处理,使用受限
< > "不安全,这些字符在URL范围之外通常是有意义的,如在文档中对URL自身进行定界,所以应该对其进行编码
0x00-0x1F,0x7F受限,这些十六进制值范围内的字符都在US-ASCII字符集的不可打印区间内
>0x7F受限,十六进制值在此范围内的字符都不在US-ASCII字符集的7比特范围内

3.4. 另外一点说明

  • 使用一些不安全字符对某些传输协议来说,并不是什么问题。但对应用程序开发人员来说,对非安全字符进行编码仍然是明智的。
  • 客户端应用程序在向其他应用程序发送任意URL之前最好把所有不安全或受限字符都进行转换。
  • 只要对所有不安全字符都进行了编码,这个URL就是可在各应用程序之间共享的规范形式。
  • 最适合判断是否需要对字符进行编码的程序就是从用户出回去URL的源端应用程序。
  • 另一种极端做法是对所有字符都进行编码,但不建议这么做。有时,有些人会恶意地对额外的字符进行编码,以绕过那些对URL进行模式匹配的应用程序。
  • 总之,解释URL的应用程序必须在处理URL之前对其进行解码

4. 支持各种因特网信息系统的常见URL方案

常见的方案格式 |方案|描述| |---||--- |http|超文本传输协议方案,除了没有用户名和密码之外,与通用的URL格式相符。端口默认80。基本格式:http://<host>:<port>/<path>?<query>#<frag>| |https|方案https和方案http是一对。唯一的区别在于方案https使用了网景的SSL,SSL为HTTP连接提供了端到端的加密机制。其语法与HTTP的语法相同,默认端口443| |mailto|Mailto URL指向E-mail地址。由于E-mail的行为与其他方案都有所不同(并不指向任何可以直接访问的对象),所以mailto URL的格式与标准URL的格式也有所不同。基本格式:mailto:<RFC-822-addr-spec>| |ftp|在Web和URL出现之前FTP就已经存在了。Web应用程序将FTP作为一种数据访问方案使用。基本格式:ftp://<user>:<password>@<host>:<port>/<path>;<params>| |rtsp,rtspu|RTSP URL是可以通过实时流传输协议(Real Time Streaming Protocol)解析的音/视频媒体资源的标识符。方案rtspu中的u表示它使用UDP协议来获取资源。基本格式:rtsp://<user>:<password>@<host>:<port>/<path> rtspu://<user>:<password>@<host>:<port>/<path>| |file|方案file表示一台指定主机上可直接访问的文件。如果省略了主机名,就默认正在使用URL的本地主机。基本格式:file://<host>/<path>| |news|方案news用来访问一些特定的文章或新闻组。有一个很独特的性质:news URL自身包含的信息不足以对资源进行定位。news URL中没有提供主机名或机器名称。从用户那里获取此类信息是解释程序的工作。如在网景浏览器的”选项“菜单中可以指定自己的NNTP(news)服务器。新闻资源可以从多台服务器中获得,它们被称为位置无关的。news URL中保留了字符"@",用来区分指向新闻组的news URL和指向特定新闻文章的news URL。基本格式:news://<newsgroup> news://<news-article-id>| |telnet|方案telnet用于访问交互式业务。它表示的并不是对象自身,而是可通过telnet协议访问的交互式应用程序。基本格式:telnet://<user>:<password>@<host>:<port>/|

5. URL的未来,包括URN

  • URL是一种强有力的工具。他可以用来命名所有现存对象,而且可以很方便地包含一些新格式。URL还提供了一种可能在各种因特网协议间共享的统一命名机制。
  • 但URL并不完美。它表示的是实际的地址,而不是准确的名字,这种方案的缺点在于如果资源被移走了,URL也就不再有效了。
  • 为了应对这个问题,因特网工程任务组已经对一种名为URN的新标准做了一段时间的研究了,无论对象搬移到什么地方,URN都能为对象提供一个稳定的名称
  • 永久统一资源定位符(Persistent uniform resource locators,PURL)是用URL来实现URN功能的一个例子。其基本思想是在搜索资源的过程中引入另一个中间层,通过一个中间资源定位符服务器对资源的实际URL进行登记和跟踪。客户端可以向定位符请求一个永久URL,定位符可以以一个资源作为响应,将客户端重定向到资源当前实际的URL上去。
  • 从URL转换成URN是一项巨大的工程。标准化工作的进程很缓慢。支持URN需要进行很多改动。URL有一些限制,但这并不是Web开发社区所面临的最紧迫的问题。其他命名方案想要取代URL还要一段时间,但URL确实有其局限性,可能会出现新的标准(可能就是URN),对这种标准进行部署会解决其中的某些问题。

第3章 传输Web内容的HTTP报文

如果说HTTP是因特网的信使,那么HTTP报文就是它用来搬东西的包裹。

1. 报文是如何流动的

HTTP报文是在HTTP应用程序之间发送的数据块。这些数据块以一些文本形式的元信息开头,这些信息描述了报文的内容及含义,后面跟着可选的数据部分。这些报文在客户端、服务器和代理之间流动。

1.1. 报文流入源端服务器

HTTP使用术语流入(inbound)和流出(outbound)来描述事务处理(transaction)的方向。报文流入源端服务器,工作完成之后,会流回用户的Agent代理中

1.2. 报文向下游流动

HTTP报文会像河水一样流动。不管是请求报文还是响应报文,所有报文都会向下游(downstream)流动,所有报文的发送者都在接收者的上游(upstream)。

2. HTTP报文的三个组成部分

HTTP报文是简单的格式化数据块。每条报文都包含一条来自客户端的请求,或者一条来自服务器的响应。由三部分组成:起始行(start line)、首部(header)、主体(body)。起始行和首部就是由行分隔的ASCII文本。每行都以一个由两个字符(一个回车符ASCII码13和一个换行符ASCII码10)组成的行终止序列CRLF作为结束。尽管HTTP规范说明应该用CRLF来表示行终止,但有些老的或不完整的HTTP应用程序并不总是既发送回车符又发送换行符。

2.1. 报文的语法

所有的HTTP报文都可以分为两类,报文的基本结构相同

2.1.1 请求报文(request message)

会向Web服务器请求一个动作。请求报文的格式:

<method> <request-URL> <version>
<headers>

<entity-body>
2.1.2 响应报文(response message)

会将请求的结果返回给客户端。响应报文的格式:

<version> <status> <status-phrase>
<headers>

<entity-body>

2.2. 起始行

所有的HTTP报文都以一个起始行开始,对报文进行描述。请求报文的起始行说明了要做些什么;响应报文的起始行说明发生了什么。

  • 请求行:请求报文请求服务器对资源进行一些操作,请求行包含了一个方法和一个请求URL以及HTTP的版本,用空格符分隔这些字段。
  • 响应行:响应报文承载了状态信息和操作产生的所有结果数据,将其返回给客户端。响应行包含了响应报文使用的HTTP版本、数字状态码以及描述操作状态的文本形式的原因短语,用空格符分隔这些字段。
2.2.1 方法method

请求行以方法作为开始,用来告知服务器客户端希望服务器对资源执行的动作。是一个单独的词。HTTP规范中定义了一组常用的请求方法。并不是所有服务器都实现了这7种方法,而且由于HTTP设计得易于扩展,除了这些方法之外,其他服务器可能还会实现一些自己的请求方法。这些附加的方法是对HTTP规范的扩展,因此被称为扩展方法。

方法描述是否包含主体
GET从服务器获取一份文档
HEAD只从服务器获取文档的首部
POST向服务器发送需要处理的数据
PUT将请求的主体部分存储在服务器上
TRACE将可能经过代理服务器传送到服务器上去的报文进行追踪
OPTIONS决定可以在服务器上执行哪些方法
DELETE从服务器上删除一份文档
2.2.2 请求URL

命名了所请求资源,或者URL路径组件的完整URL。描述了要对哪个资源执行请求方法

2.2.3 版本version
  • 版本号以HTTP/<major>.<minor>的形式出现在请求行和响应行中。
  • 为HTTP应用程序提供一种将自己所遵循的协议版本告知对方的方式。其目的是以便使用HTTP的应用程序互相了解对方的能力和报文格式。
  • 版本号说明了应用程序支持的最高HTTP版本。
  • 其中主要版本号major和次要版本号minor都是整数,在比较HTTP版本时,每个数字都必须单独进行比较。比如HTTP/2.22就比HTTP/2.3版本要高
  • 在HTTP/1.0之前,并不要求请求行中包含HTTP版本号;并不要求在响应中包含响应行
2.2.4 状态码status-code
  • 响应行中的状态码用来告诉客户端请求过程中发生了什么事情。
  • 三位数字的数字码便于程序进行差错处理,每个状态码的第一位数字都用于描述状态的一般类别(“成功”、“出错”等)。
    • 200到299之间的状态码表示成功;
    • 300到399之间的代码表示资源已经被移走了;
    • 400到499之间的代码表示客户端的请求出错了;
    • 500到599之间的代码表示服务器出错了。
  • 当前的HTTP版本只为每类状态定义了几个代码,如果收到了不认识的状态码,可能是有人将其作为当前协议的扩展定义的。可根据其所处范围,将它作为那个类别中一个普通的成员来处理。 |整体范围|已定义范围|分类| |---|---|---| |100~199|100~101|信息提示| |200~299|200~206|成功| |300~399|300~305|重定向| |400~499|400~415|客户端错误| |500~599|500~505|服务器错误|
2.2.5 原因短语reason-phrase

响应行中的最后一个组件,包含行终止序列之前的所有文本。为数字状态码提供了文本形式的解释,更便于人们理解。原因短语和状态码是成对出现的。最常见的状态码:

状态码原因短语含义
200OK成功。请求的所有数据都在响应主体中
401Unauthorized需要输入用户名和密码
404Not Found服务器无法找到所请求URL对应的资源

2.3. 首部块

跟在起始行后面的是0个、1个或多个HTTP首部字段,首部字段向请求和响应报文中添加了一些附加信息。

2.3.1 首部语法
  • 每个首部都包含一个名字
  • 后跟一个冒号:
  • 然后是一个可选的空格
  • 接着是一个值
  • 最后是一个CRLF
2.3.2 首部分类

HTTP规范定义了几种首部字段,应用程序也可以随意发明自己所用的首部。有些HTTP版本,比如HTTP/1.1,要求有效的请求或响应报文中必须包含特定的首部。首部可以分为几类:

  • 通用首部:既可以出现在请求报文中,也可以出现在响应报文中
  • 请求首部:提供更多有关请求的信息
  • 响应首部:提供更多有关响应的信息
  • 实体首部:描述主体的长度和内容,或者资源自身
  • 扩展首部:规范中没有定义的新首部
2.3.3 常见的首部实例
首部实例描述
Date:Tue, 3 Oct 1997 02:16:03 GMT服务器产生响应的日期
Content-length:15040实体的主体部分包含了15040字节的数据
Content-type:image/gif实体的主体部分是一个GIF图片
Accept:image/gif, image/jpeg, text/html客户端可以接收GIF图片和JPEG图片以及HTML
2.3.4 首部延续行

将长的首部分为多行可以提高可读性,多出来的每行前面至少要有一个空格或制表符(tab)

2.3.5 首部列表的结束

首部是由一个空行(CRLF)结束的,表示了首部列表的结束和实体主体部分的开始。

  • 注意,一组HTTP首部总是应该以一个CRLF结束,甚至即使没有首部和实体的主体部分也应如此。
  • 但由于历史原因,很多客户端和服务器都在没有实体的主体部分时省略了最后CRLF
  • 为了与这些流行但不符合规则的实现进行互通,客户端和服务器都应该接受那些没有最后那个CRLF的报文。

2.4. 主体

  • HTTP报文的第三部分是可选的、包含数据的实体的主体部分。
  • 主体是HTTP报文的负荷,就是HTTP要传输的内容。
  • HTTP报文可以承载很多类型的数字数据:图片、视频、HTML文档、软件应用程序、信用卡事务、电子邮件等。
  • 这也是主体与起始行和首部的不同,它可以包含文本或二进制数据,也可以为空。有时,报文只是以一个CRLF结束。

2.5. 版本0.9的报文

HTTP/0.9是HTTP协议的早期版本。是当今HTTP所拥有的请求及响应报文的鼻祖,但其协议要简单得多。

2.5.1 HTTP/0.9的请求报文

报文中只包含方法和请求URL,没有版本信息(是第一个,也是当时唯一的版本)

2.5.2 HTTP/0.9的响应报文

报文中只包含实体,没有状态码或原因短语,也没有首部

3. 请求报文支持的各种方法

  • 并不是每个服务器都实现了所有的方法。
  • 如果一台服务器要与HTTP 1.1兼容,那么只要为其资源实现GET方法和HEAD方法就可以了。
  • 即使服务器实现了所有这些方法,这些方法的使用很可能也是受限的。这些限制通常都是在服务器的配置中进行设置的,因此会随着站点和服务器的不同而有所不同。

3.1. 安全方法

  • GET方法和HEAD方法都被认为是安全的,意味着使用GET或HEAD方法的HTTP请求都不会产生什么动作,就是不会在服务器上产生什么结果。
  • 安全方法并不一定是什么动作都不执行的(由Web开发者决定)。使用安全方法的目的就是当使用可能引发某一动作的不安全方法时,允许HTTP应用程序开发者通知用户

3.2. GET

最常用的方法。通常用于请求服务器发送某个资源。

3.3. HEAD

与GET方法的行为很类似,但服务器在响应中只返回首部,不会返回实体的主体部分。服务器开发者必须确保HEAD返回的首部与GET请求所返回的首部完全相同。这就允许客户端在未获取实际资源的情况下,对资源的首部进行检查:

  • 在不获取资源的情况下了解资源的情况
  • 通过查看响应中的状态码,看看某个对象是否存在
  • 通过查看首部,测试资源是否被修改了

3.4. PUT

  • 会向服务器写入文档。
  • 其语义就是让服务器用请求的主体部分来创建一个由所请求的URL命名的新文档,或者,如果那个URL已经存在,就用这个主题来替代它。
  • 因为PUT允许用户对内容进行修改,所以很多Web服务器都要求在执行PUT之前,用密码登录。

3.5. POST

起初是用来向服务器输入数据的。实际上,通常会用它来支持HTML的表单。表单中填好的数据通常会被送给服务器,然后由服务器将其发送到它要去的地方。

3.6. TRACE

  • 客户端发起一个请求时,可能要穿过防火墙、代理、网关或其他一些应用程序。每个中间节点都有可能会修改原始的HTTP请求。
  • TRACE方法允许客户端在最终将请求发送给服务器时,看看它变成了什么样子
  • TRACE请求会在目的服务器端发起一个“环回”诊断。行程最后一站的服务器会弹回一条TRACE响应,并在响应主体中携带它收到的原始请求报文。这样客户端就可以查看在所有中间HTTP应用程序组成的请求/响应链上,原始报文是否,以及如何被毁坏或修改过。
  • TRACE方法主要用于诊断,也就是说用于验证请求是否如愿穿过了请求/响应链。也是一种很好的工具,可以用来查看代理和其他应用程序对用户请求所产生效果
  • 但TRACE方法也有缺点,它假定中间应用程序对各种不同类型的请求(GET、HEAD、POST等)的处理是相同的。然而,很多HTTP应用程序员会根据方法的不同做出不同的事情。TRACE并不提供区分这些方法的机制。通常,中间应用程序会仔细个决定对TRACE请求的处理方式

3.7. OPTIONS

请求Web服务器告知其支持的各种功能,可以询问服务器通常支持哪些方法,或者对某些特殊资源支持哪些方法。这为客户端应用程序提供了一种手段,使其不用实际访问那些资源就能判断访问各种资源的最优方式。

3.8. DELETE

请服务器删除请求URL所指定的资源。但客户端应用程序无法保证删除操作一定会被执行。因为HTTP规范允许服务器在不通知客户的情况下撤销请求

3.9. 扩展方法

  • HTTP被设计成字段可扩展的,这样新的特性不会使老的软件失效。
  • 扩展方法指的是没有在HTTP/1.1规范中定义的方法。
  • 服务器会为它所管理的资源实现一些HTTP服务,这些方法为开发者提供了一种扩展这些HTTP服务能力的手段。
  • 下表列出了一些常见的扩展方法实例,这些方法是WebDAV HTTP扩展包含的所有方法,这些方法有助于通过HTTP将Web内容发布到Web服务器上去
方法描述
LOCK允许用户“锁定”资源-比如,在编辑某个资源的时候将其锁定以防止别人同时对其进行修改
MKCOL允许用户创建资源
COPY便于在服务器上复制资源
MOVE在服务器上移动资源
  • 如果能够在不破坏端到端行为的情况下将带有未知方法的报文传递给下游服务器的话,代理应尝试传递这些报文。如果可能破坏端到端行为,则应以501(Not Implemented)状态码进行响应。最好按惯例“对所发送的内容要求严一点,对所接收的内容宽容一些”来处理扩展方法以及一般的HTTP扩展

4. 和响应报文一起返回的各种状态码

状态码为客户端提供了一种理解事务处理结果的便捷方式。尽管并没有实际的规范对原因短语的确切文本进行说明,但列出了HTTP/1.1规范推荐使用的原因短语

4.1. 100~199 信息性状态码

HTTP/1.1向协议中引入了信息性状态码。这些状态码相对较新,关于其复杂性和感知价值存在一些争议

状态码原因短语含义
100Continue说明收到了请求的初始部分,请客户端继续。发送了这个状态码之后,服务器在收到请求之后必须进行响应
101Switching Protocols说明服务器正在根据客户端的指定,将协议切换成Update首部所列的协议

100 Continue状态码的目的是优化这样的情况:HTTP客户端应用程序有一个主体部分要发送给服务器,但希望在发送之前查看一下服务器是否会接受这个实体。

4.1.1 客户端与100 Continue
  • 如果客户端在向服务器发送一个实体,且愿意在发送实体之前等待100 Continue响应,那么客户端就要发送一个携带了值为100 Continue的Expect请求首部。
  • 客户端应用程序只有在避免向服务器发送一个服务器无法处理或使用的大实体时,才应该使用100 Continue,因为从很多方面来看,100 Continue都是一种优化
  • 发送了值为100 Continue的Expect首部的客户端不应该永远等待服务器发送100 Continue响应。超过一定时间之后,客户端应该直接将实体发送出去。
  • 客户端程序的实现者也应该做好应对非预期100 Continue响应的准备,有些出错的HTTP应用程序会不合时宜地发送这个状态码
4.1.2 服务器与100 Continue
  • 如果服务器收到了一条带有值为100 Continue的Expect首部的请求,会用100 Continue响应或一条错误码来进行响应
  • 服务器永远也不应该向没有发送100 Continue的Expect的客户端发送100 Continue状态码
  • 如果服务器在有机会发送100 Continue响应之前就收到了部分或全部的实体,说明客户端已经决定继续发送数据了,这样,服务器就不需要发送这个状态码了。但服务器读完请求之后,还是应该为请求发送一个最终状态码(可以跳过100 Continue状态)
  • 如果服务器收到了带有100 Continue的Expect的请求,而且决定在读取主体之前结束请求,就不应该仅仅是发送一条响应并关闭连接,这样会妨碍客户端接收响应
4.1.3 代理与100 Continue
  • 如果代理从客户端收到了一条带有100 Continue的Expect的请求
    • 如果代理知道下一跳服务器是HTTP/1.1兼容的,或者并不知道下一跳服务器与哪个版本兼容,应该将Expect首部放在请求中向下转发
    • 如果知道下一跳服务器只能与HTTP/1.1之前的版本兼容,应该以417 Expectation Failed错误进行响应。或者向客户端先返回100 Continue,在向服务器转发请求时,删掉Expect首部
  • 如果代理决定代表与HTTP/1.0或之前版本兼容的客户端,在其请求中放入Expect首部和100 Continue值,那么,如果它从服务器收到了100 Continue响应,就不应该将100 Continue响应转发给客户端,因为客户端可能无法处理
  • 代理维护一些有关下一跳服务器及其所支持的HTTP版本的状态信息是有好处的,这样就可以更好地处理收到的那些带有100 Continue的Expect的请求了

4.2. 200~299 成功状态码

状态码原因短语含义
200OK请求没问题,主体部分包含了所请求的资源
201Created用于创建服务器对象的请求(PUT)。响应的主体中应该包含各种引用了已创建的资源的URL,Location首部包含的则是最具体的引用。服务器必须在发送这个状态码之前创建好对象
202Accepted请求已被接受,但服务器还未对其执行任何动作。不能保证服务器会完成这个请求,只是意味着接受请求时,它看起来是有效的。服务器应该在主体部分包含对请求状态的描述,或许还应该有对请求完成时间的估计(或者包含一个指向可以获取此信息的位置的指针)
203Non-Authoritative Information实体首部包含的信息不是来自于源端服务器,而是来自资源的一份副本。如果中间节点上有一份资源的副本,但无法或没有对它所发送的首部进行验证,就会出现这种情况。
204No Content响应报文中没有主体,主要用于在浏览器不转为现实新文档的情况下对其进行更新(比如刷新一个表单页面)
205Reset Content另一个主要用于浏览器的代码。负责告知浏览器清除当前页面中的所有HTML表单元素
206Partial Content成功执行了一个部分或Range请求。客户端可以通过一些特殊的首部来获取部分或某个范围内的文档,这个状态码就说明范围请求成功了。206响应中必须包含Content-Range、Date、ETag或Content-Location首部

4.3. 300~399 重定向状态码

  • 重定向状态码要么告知客户端使用替代位置来访问他们所感兴趣的资源,要么就提供一个替代的响应而不是资源的内容。
  • 如果资源已被移动,可发送一个重定向状态码和一个可选的Location首部来告知客户端资源已被移走,以及现在可以在哪里找到它。浏览器就可以在不打扰使用者的情况下,透明地转入新的位置了
  • 可以通过某些重定向状态码对资源的应用程序本地副本与源端服务器上的资源进行验证。如HTTP应用程序可以查看其本地副本是否仍然是最新的,或者在源端服务器上资源是否被修改过。
  • 对那些包含了重定向状态码的非HEAD请求进行响应时,最好要包含一个主体,主体中包含描述信息和指向重定向URL的链接
状态码原因短语含义
300Multiple Choices客户端请求一个实际指向多个资源的URL时会返回这个状态码。返回这个代码时会带有一个选项列表,这样用户就可以选择他希望使用的那一项了。服务器可以在Location首部包含首选URL
301Moved Permanently在请求的URL已被移除时使用。响应的Location首部应该包含资源现在所处的URL
302Found与301状态码类似;但客户端应该使用Location首部给出的URL来临时定位资源。将来的请求仍使用老的URL
303See Other告知客户端应该用另一个URL来获取资源。新的URL位于响应报文的Location首部。主要目的是允许POST请求的响应将客户端定向到某个资源上去
304Not Modified客户端可以通过包含的请求首部,使请求编程有条件的。如果客户端发起一个条件GET请求,而最近资源未被修改,就可以用这个状态码说明资源未被修改。带有这个状态码的响应不应该包含主体
305Use Proxy用来说明必须通过一个代理来访问资源,代理的位置由Location首部给出。很重要的一点是,客户端是相对某个特定资源来解析这条响应的,不能假定所有请求甚至所有对持有所请求资源的服务器的请求都通过这个代理进行。如果客户端错误地让代理介入了某条请求,可能会引发破坏性的行为,而且会造成安全漏洞
306(未使用)当前未使用
307Temporary Redirect与301状态码类似;但客户端应该使用Location首部给出的URL来临时定位资源。将来的请求仍使用老的URL
  • 当HTTP/1.0客户端发起一个POST请求,并在响应中收到302状态码,它会接受Location首部的重定向URL,并向那个URL发起一个GET请求。HTTP/1.0服务器希望HTTP/1.0客户端这么做(如果HTTP/1.0服务器收到来自HTTP/1.0客户端的POST请求之后发送了302,服务器就期望客户端能够接受重定向URL,并向重定向的URL发送一个GET请求)。
  • 而HTTP/1.1规范使用303状态码来实现同样的行为,为了避开这个问题,HTTP/1.1规范指出,对于HTTP/1.1客户端,用307状态码取代302状态码进行临时重定向。这样服务器就可以将302保留起来为HTTP/1.0客户端使用了。

4.4. 400~499 客户端错误状态码

有时客户端会发送一些服务器无法处理的东西。很多客户端错误都是由浏览器来处理的,甚至不会打扰到你。只有少量错误,如404,还是会穿过浏览器来到用户面前

状态码原因短语含义
400Bad Request用于告知客户端它发送了一个错误的请求
401Unauthorized与适当的首部一同返回,在这些首部中请求客户端在获取对资源的访问权之前,对自己进行认证
402Payment Required现在这个状态码还未被使用,但已经被保留,以作未来之用
403Forbidden用于说明请求被服务器拒绝了。如果服务器想说明为什么拒绝请求,可以包含主体来对原因进行描述。但这个状态码通常是在服务器不想说明拒绝原因的时候使用的
404Not Found用于说明服务器无法找到所请求的URL。通常会包含一个主体,以便客户端应用程序显示给用户看
405Method Not Allowed发起的请求中带有所请求的URL不支持的方法时,使用此状态码。应该在响应中包含Allow首部,以告知客户端对所请求的资源可以使用哪些方法。
406Not Acceptable客户端可以指定参数来说明它们愿意接收什么类型的实体。服务器没有与客户端可接受的URL相匹配的资源时,使用此代码。通常,服务器会包含一些首部,以便客户端弄清楚为什么请求无法满足
407Proxy Authentication Required与401状态码类似,但用于要求对资源进行认证的代理服务器
408Request Timeout如果客户端完成请求所花的时间太长,服务器可以回送此状态码,并关闭连接。超时时长随服务器的不同有所不同,但通常对所有的合法请求来说,都是够长的
409Conflict用于说明请求可能在资源上引发的一些冲突。服务器担心请求会引发冲突时,发送此状态码。响应中应包含描述冲突的主体
410Gone与404类似,指示服务器曾经拥有过此资源。主要用于Web站点的维护,这样服务器的管理者就可以在资源被移除的情况下通知客户端了
411Length Required服务器要求在请求报文中包含Content-Length首部时使用
412Precondition Failed客户端发起了条件请求,且其中一个条件失败了的时候使用。客户端包含了Expect首部时发起的就是条件请求
413Request Entity Too Large客户端发送的实体主体部分比服务器能够或者希望处理的要大时,使用此状态码
414Request URI Too Long客户端所发送请求中的请求URL比服务器能够或者希望处理的要长时,使用此状态码
415Unsupported Media Type服务器无法理解或无法支持客户端所发实体的内容类型时,使用此状态码
416Request Range Not Satisfiable请求报文所请求的是指定资源的某个范围,而此范围无效或无法满足时,使用此状态码
417Expectation Failed请求的Expect请求首部包含了一个期望,但服务器无法满足此期望时,使用此状态码。如果代理或其他中间应用程序有确切证据说明源端服务器会为某请求产生一个失败的期望,就可以发送这个响应状态码

4.5. 500~599 服务器错误状态码

  • 可能是客户端碰上了服务器的缺陷,或者服务器上的子元素,比如某个网关资源,出了错。
  • 代理尝试着代表客户端与服务器进行交流时,经常会出现问题。代理发布5XX服务器错误状态码来描述所遇到的问题
状态码原因短语含义
500Internal Server Error服务器遇到一个妨碍它为请求提供服务的错误时,使用此状态码
501Not Implemented客户端发起的请求超出服务器的能力范围时,使用此状态码
502Bad Geteway作为代理或网关使用的服务器从请求响应链的下一条链路上收到了一条伪响应时,使用此状态码
503Service Unavaiable用来说明服务器现在无法为请求提供服务,但将来可以。如果服务器知道什么时候资源会变为可用的,可以在响应中包含一个Retry-After首部
504Geteway Timeout与状态408类似,指示这里的响应来自一个网关或代理,它们在等待另一服务器对其请求进行响应时超时了
505HTTP Version Not Supported服务器收到的请求使用了它无法或不愿支持的协议版本时,使用此状态码。有些服务器应用程序会选择不支持协议的早期版本

5. 各种各样的HTTP首部都是用来做什么的

首部和方法配合工作,共同决定客户端和服务器能做什么事情。首部可以分为5个主要类型:

5.1. 通用首部

客户端和服务器都可以使用的通用首部。可以在客户端、服务器和其他应用程序之间提供一些非常有用的通用功能。提供了与报文相关的最基本的信息。

5.1.1 通用的信息性首部:
首部描述
Connection允许客户端和服务器指定与请求/响应连接有关的选项
Date提供日期和时间标志,说明报文是什么时间创建的
MIME-Version给出了发送端使用的MIME版本
Trailer如果报文采用了分块传输编码方式,就可以用这个首部列出位于报文拖挂部分的首部集合
Transfer-Encoding告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式
Update给出了发送端可能想要“升级”使用的新版本或协议
Via显示了报文经过的中间节点(代理、网关)
5.1.2 通用缓存首部

HTTP/1.0引入了第一个允许HTTP应用程序缓存对象本地副本的首部,这样就不需要总是直接从源端服务器获取了。

首部描述
Cache-Control用于随报文传送缓存指示
Pragma另一种随报文传送指示的方式,但并不专用于缓存

5.2. 请求首部

请求报文特有的,为服务器提供了一些额外信息,用于说明是谁或什么在发送请求、请求源自何处,或者客户端的喜好及能力。服务器可以根据请求首部给出的客户端信息,试着为客户端提供更好的响应。

5.2.1 请求的信息性首部
首部描述
Client-IP提供了运行客户端的机器的IP地址
From提供了客户端用户的E-mail地址
Host给出了接收请求的服务器的主机名和端口号
Referer提供了包含当前请求URI的文档的URL
UA-Color提供了与客户端显示器的显示颜色有关的信息
UA-CPU给出了客户端CPU的类型或制造商
UA-Disp提供了与客户端显示器能力有关的信息
UA-OS给出了运行在客户端机器傻姑娘的操作系统名称及版本
UA-Pixels提供了客户端显示器的像素信息
User-Agent将发起请求的应用程序名称告知服务器
5.2.2 Accept首部

为客户端提供了一种将其喜好和能力告知服务器的方式,包括它们想要什么,可以使用什么,以及最重要的,它们不想要什么。这样,服务器就可以根据这些额外信息,对要发送的内容做出更明智的决定。

首部描述
Accept告诉服务器能够发送哪些媒体类型
Accept-Charset告诉服务器能够发送哪些字符集
Accept-Encoding告诉服务器能够发送哪些编码方式
Accept-Language告诉服务器能够发送哪些语言
TE告诉服务器可以使用哪些扩展传输编码
5.2.3 条件请求首部

有时客户端希望为请求加上某些限制。要求服务器在对请求进行响应之前,确保某个条件为真

首部描述
Expect允许客户端列出某请求所要求的服务器行为
If-Match如果实体标记与文档当前的实体标记相匹配,就获取这份文档
If-Modified-Since除非在某个指定的日期之后资源被修改过,否则就限制这个请求
If-None-Match如果提供的实体标记与当前文档的实体标记不相符,就获取这个文档
If-Range允许对文档的某个范围进行条件请求
If-Unmodified-Since除非在某个指定日期之后资源没有被修改过,否则就限制这个请求
Range如果服务器支持范围请求,就请求资源的指定范围
5.2.4 安全请求首部

HTTP本身就支持一种简单的机制,可以对请求进行质询/响应认证。这种机制要求客户端在获取特定的资源之前,先对自身进行认证,这样就可以使事务稍微安全一些。

首部描述
Authorization包含了客户端提供给服务器,以便对其自身进行认证的数据
Cookie客户端用它向服务器传送一个令牌--它并不是真正的安全首部,但确实隐含了安全功能
Cookie2用来说明请求端支持的cookie版本
5.2.5 代理请求首部

随着因特网上代理的普遍应用,人们定义了几个首部来协助其更好地工作

首部描述
Max-Forward在通往源端服务器的路径上,将请求转发给其他代理或网关的最大次数--与TRACE方法一同使用
Proxy-Authorization与Authorization首部相同,但这个首部是在与代理进行认证时使用的
Proxy-Connection与Connection首部相同,但这个首部是在与代理建立连接时使用的

5.3. 响应首部

响应报文特有的首部集,为客户端提供一些额外的信息,比如谁在发送响应、响应者的功能,甚至与响应相关的一些特殊指令。这些首部有助于客户端处理响应,并在将来发起更好的请求。

5.3.1 响应的信息性首部
首部描述
Age(从最初创建开始)响应持续时间
Public服务器为其资源支持的请求方法列表
Retry-After如果资源不可用的话,在此日期或时间重试
Server服务器应用程序软件的名称和版本
Title对HTML文档来说,就是HTML文档的源端给出的标题
Warning比原因短语中更详细一些的警告报文
5.3.2 协商首部

如果资源由多种表示方法,HTTP/1.1可以为服务器和客户端提供对资源进行协商的能力。

首部描述
Accept-Ranges对此资源来说,服务器可接受的范围类型
Vary服务器查看的其他首部的列表,可能会使响应发生变化;也就是说,这是一个首部列表,服务器会根据这些首部的内容挑选出最适合资源版本发送给客户端
5.3.3 安全响应首部

HTTP的质询/响应认证机制的响应侧。介绍一些基本的质询首部

首部描述
Proxy-Authenticate来自代理的对客户端的质询列表
Set-Cookie不是真正的安全首部,但隐含有安全功能;可以在客户端设置一个令牌,以便服务器对客户端进行标识
Set-Cookie2与Set-Cookie类似,RFC 2965 Cookie定义
WWW-Authenticate来自服务器的对客户端的质询列表

5.4. 实体首部

用来描述HTTP报文的负荷。提供了有关实体机器内容的大量信息,从有关对象类型的信息,到能够对资源使用的各种有效的请求方法。

5.4.1 实体的信息性首部
首部描述
Allow列出了可以对此实体执行的请求方法
Location告知客户端实体实际上位于何处;用于将接收端定向到资源的位置上去
5.4.2 内容首部

提供了与实体内容有关的特定信息,说明了其类型、尺寸以及处理它所需的其他有用信息。

首部描述
Content-Base解析主体中的相对URL时使用的基础URL
Content-Encoding对主体执行的任意编码方式
Content-Language理解主体时最适宜使用的自然语言
Content-Length主体的长度或尺寸
Content-Location资源实际所处的位置
Content-MD5主体的MD5校验和
Content-Range在整个资源中此实体表示的字节范围
Content-Type这个主体的对象类型
5.4.3 实体缓存首部

通用的缓存首部说明了如何或什么时候进行缓存。实体的缓存首部提供了与被缓存实体有关的信息--比如,验证已缓存的资源副本是否仍然有效所需的信息,以及更好地估计已缓存资源何时失效所需的线索

首部描述
ETag与此实体相关的实体标记
Expires实体不再有效,要从原始的源端再次获取此实体的日期和时间
Last-Modified这个实体最后一次被修改的日期和时间

5.5. 扩展首部

非标准的首部,由应用程序开发者创建,但还未添加到已批准的HTTP规范中去。即使不知道这些扩展首部的含义,HTTP程序也要接受它们并对其进行转发。

第4章 连接管理

HTTP连接是HTTP报文传输的关键通道。

1. HTTP是如何使用TCP连接的

世界上几乎所有的HTTP通信都是由TCP/IP承载的,TCP/IP是全球计算机及网络设备都在使用的一种常用的分组交换网络分层协议集。客户端应用程序可以打开一条TCP/IP连接,连接到可能运行在世界任何地方的服务器应用程序。

  • Web浏览器通过TCP连接Web服务器: image.png

1.1. TCP的可靠数据管道

HTTP实际上就是TCP连接及其使用规则。TCP连接是因特网上的可靠连接。TCP为HTTP提供了一条可靠的比特传输管道。从TCP连接一端填入的字节会从另一端以原有的顺序、正确地传送出来

1.2. TCP流是分段的、由IP分组传送

  • TCP的数据是通过名为IP分组(或IP数据报)的小数据块来发送的。
  • HTTP就是“HTTP over TCP over IP”这个“协议栈”中的最顶层了。其安全版本HTTPS就是在HTTP和TCP之间插入了一个(称为TLS或SSL的)密码加密层。
  • HTTP要传送一条报文时,会以流的形式将报文数据的内容通过一条打开的TCP连接按序传输。TCP收到数据流之后,会将数据流砍成被称作段的小数据块,并将段封装在IP分组中,通过因特网进行传输。
  • 每个TCP段都是由IP分组承载,从一个IP地址发送到另一个IP地址的。每个IP分组都包括:
    • 一个IP分组首部(包含了源和目的IP地址、长度和其他一些标记)
    • 一个TCP段首部(包含了TCP端口号、TCP控制标记,以及用于数据排序和完整性检查的一些数字值)
    • 一个TCP数据块

1.3. 保持TCP连接的持续不间断地运行

在任意时刻计算机都可以有几条TCP连接处于打开状态。TCP是通过端口号来保持所有这些连接的正确运行的。TCP连接通过4个值来识别<源IP地址、源端口号、目的IP地址、目的端口号>,这4个值一起唯一地定义了一条连接。

1.4. 用TCP套接字编程

操作系统提供了一些操作其TCP连接的工具。套接字API最初是为Unix操作系统开发的,但现在几乎所有的操作系统和语言中都有其变体存在。对TCP连接进行编程所需的常见套接字接口函数:

套接字API调用描述
s=socket(<parameters>)创建一个新的、未命名、未关联的套接字
bind(s,<local IP:port>)向套接字赋一个本地端口号和接口
connect(s,<remote IP:port>)创建一条连接本地套接字与远程主机及端口的连接
listen(s,...)标识一个本地套接字,使其可以合法接受连接
s2=accept(s)等待某人建立一条道本地端口的连接
n=read(s,buffer,n)尝试从套接字向缓冲区读取n个字节
n=write(s,buffer,n)尝试从缓冲区中向套接字写入n个字节
close(s)完全关闭TCP连接
shutdown(s,<side>)只关闭TCP连接的输入或输出端
getsockopt(s,...)读取某个内部套接字配置选项的值
setsockopt(s,...)修改某个内部套接字配置选项的值
  • 套接字API允许用户创建TCP的端点数据结构,将这些端点与远程服务器的TCP端点进行连接,并对数据流进行读写。
  • 通过套接字API来实现客户端和服务器在实现HTTP事务时的步骤: image.png

2. 对TCP性能的考虑

HTTP事务的性能在很大程度上取决于底层TCP通道的性能。

2.1. HTTP事务的时延

HTTP事务主要的连接、传输以及处理时延:(与建立TCP连接,以及传输请求和响应报文的时间相比,事务处理时间可能是很短的。除非客户端或服务器超载,或正在处理复杂的动态资源,否则HTTP时延就是由TCP网络时延构成的)这些TCP网络时延的大小取决于硬件速度、网络和服务器的负载,请求和响应报文的尺寸,以及客户端和服务器之间的距离。TCP协议的技术复杂性也会对时延产生巨大影响。 image.png

  1. 客户端受限需要根据URI确定Web服务器的IP地址和端口号。如果最近没有对URI中的主机名进行访问,通过DNS解析系统将URI中的主机名转换成一个IP地址可能要花费数十秒的时间。
  2. 接下来,客户端会向服务器发送一条TCP连接请求,并等待服务器回送一个请求接收应答。每条新的TCP连接都会有连接建立时延。这个值通常最多只有一两秒钟,但如果有数百个HTTP事务的话,这个值会快速叠加上去。
  3. 一旦连接建立起来了,客户端就会通过新建立的TCP管道来发送HTTP请求。数据到达时,Web服务器会从TCP连接中读取请求报文,并对请求进行处理。因特网传输请求报文以及服务器处理请求报文都需要时间。
  4. 然后,Web服务器会回送HTTP响应,这也需要花费时间。

2.2. 性能聚焦区域

一些会对HTTP程序员产生影响、最常见的TCP相关时延:

2.2.1 TCP连接建立握手时延

建立一条新的TCP连接时,甚至是在发送任意数据之前,TCP软件之间会交换一系列的IP分组,对连接的有关参数进行沟通。如果连接只用来传送少量数据,这些交换过程就会严重降低HTTP的性能。image.pngTCP连接握手步骤:

  1. 请求新的TCP连接时,客户端要向服务器发送一个小的TCP分组(通常是40~60字节)。这个分组中设置了一个特殊的SYN标记,说明这是一个连接请求。
  2. 如果服务器接收了连接,就会对一些连接参数进行计算,并向客户端回送一个TCP分组,其中的SYN和ACK标记都被置位,说明连接请求已被接受。
  3. 最后,客户端向服务器回送一条确认信息,通知它连接已成功建立。现代的TCP栈都允许客户端在这个确认分组中发送数据
  • 通常HTTP事务都不会交换太多数据,此时,SYN/SYN+ACK握手会产生一个可测量的时延。TCP连接的ACK分组通常都足够大,可以承载整个HTTP请求报文,而且很多HTTP服务器响应报文都可以放入一个IP分组中去
  • 最后的结果是,小的HTTP事务可能会在TCP建立上花费50%甚至更多时间。
2.2.2 TCP慢启动拥塞控制
  • TCP数据传输的性能还取决于TCP连接的使用期。TCP连接会随着时间进行自我“调谐”,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度。这种调谐被称为TCP慢启动,用于防止因特网的突然过载和拥塞。
  • TCP慢启动限制了一个TCP端点在任意时刻可以传输的分组数。简单来说,每成功接收一个分组,发送端就有了发送另外两个分组的权限。这种方式被称为“打开拥塞窗口”
  • 由于存在这种拥塞控制特性,所以新连接的传输速度会比已经交换过一定量数据的、“已调谐”连接慢一些。由于已调谐连接要更快一些,所以HTTP中有一些可以重用现存连接的工具。
2.2.3 数据聚集的Nagle算法与TCP_NODELAY
  • TCP有一个数据流接口,应用程序可以通过它将任意尺寸的数据放入TCP栈中。但是,每个TCP段中都至少装载了40个字节的标记和首部,所以如果TCP发送了大量包含少量数据的分组,网络的性能就会严重下降。
  • Nagle算法试图在发送一个分组之前,将大量TCP数据绑定在一起,以提高网络效率。
  • Nagle算法鼓励发送全尺寸的段(LAN上最大尺寸的分组大约是1500字节,因特网上是几百字节)。只有当所有其他分组都被确认之后,Nagle算法才允许发送非全尺寸的分组。如果其他分组仍然在传输过程中,就将那部分数据缓存起来。只有当挂起分组被确认,或者缓存中积累了足够发哦那个一个全尺寸分组的数据时,才会将缓存的数据发送出去。
  • Nagle算法会引发几种HTTP性能问题。首先,小的HTTP报文可能无法填满一个分组,可能会因为那些永远不会到来的额外数据而产生时延。其次,Nagle算法与延迟确认之间的交互存在问题——Nagle算法会阻止数据的发送,直到有确认分组抵达,但确认分组自身会被延迟确认算法延迟100~200毫秒
  • HTTP应用程序常常会在自己的栈中设置参数TCP_NODELAY,禁用Nagle算法,提高性能。如果要这么做的话,一定要确保会向TCP写入大块的数据,这样就不会产生一堆小分组了。
2.2.4 用于捎带确认的TCP延迟确认算法
  • 由于因特网自身无法确保可靠的分组传输(因特网路由器超负荷的话,可以随意丢弃分组),所以TCP实现了自己的确认机制来确保数据的成功传输。
  • 每个TCP段都有一个序列号和数据完整性校验和。每个段的接收者收到完好的段时,都会向发送者回送小的确认分组。如果发送者在指定窗口时间内没有收到确认信息,就认为分组已被破坏或损毁,重发数据。
  • 由于确认报文很小,所以TCP允许在发往相同方向的输出数据分组中进行“捎带”,可以更有效地利用网络。为了增加确认报文找到同向传输数据分组的可能性,很多TCP栈都实现了一种“延迟确认”算法。延迟确认算法会在一个特定的窗口时间(通常是100~200毫秒)内将输出确认放在缓冲区中,以寻找能够捎带它的输出数据分组。如果在那个时间段内没有输出数据分组,就将确认信息放在单独的分组中传送。
  • 但是,当希望有相反方向回传分组的时候,偏偏没有那么多。通常,延迟确认算法会引入相当大的时延。根据所使用操作系统的不同,可以调整或禁止延迟确认算法。
  • 在对TCP栈的任何参数进行修改之前,一定要对自己在做什么有清醒的认识。TCP中引入这些算法的目的是防止设计欠佳的应用程序对因特网造成破坏。对TCP配置进行的任意修改,都要绝对保证应用程序不会引发这些算法所要避免的问题。
2.2.5 TIME_WAIT时延和端口耗尽
  • TIME_WAIT端口耗尽是很严重的性能问题,会影响到性能基准,在现实中相对较少出现。
  • 大多数遇到性能基准问题的人最终都会碰到这个问题,而且性能都会变得出乎意料地差,所以这个问题值得特别关注。
  • 当某个TCP端点关闭连接时,会在内存中维护一个小的控制块,用来记录最近所关闭的IP地址和端口号。这类信息只会维持一小段时间,通常是所估计的最大分段使用期的两倍(2MSL,通常为2分钟)左右,以确保在这段时间内不会创建具有相同地址和端口号的新连接。
  • 现在高速路由器的使用,使得重复分组几乎不可能在连接关闭的几分钟之后,出现在服务器上。有些操作系统会将2MSL设置为一个较小的值。但修改此值时要特别小心。分组确实会被复制,如果来自之前连接的复制分组插入了具有相同连接值的新TCP流,会破坏TCP数据。
  • 2MSL的连接关闭延迟通常不是什么问题,但在性能基准环境下就可能会成为一个问题。进行性能基准测试时,通常只有一台或几台用来产生流量的计算机连接到某系统中去,这样就限制了连接到服务器的客户端IP地址数。而且,服务器通常会在HTTP的默认TCP端口80上进行监听。用TIME_WAIT防止端口号重用时,这些情况也限制了可用的连接值组合。
  • 在只有一个客户端和一台Web服务器的异常情况下,构建一条TCP连接的4个值,其中3个都是固定的,只有源端口号可以随意改变。客户端每次连接到服务器上去时,都会获得一个新的源端口,以实现连接的唯一性。但由于可用源端口的数量是有限的(比如,60000个),而且在2MSL秒内连接是无法重用的,连接率就被限制在了60000/120=500次/秒。如果再不断进化优化,并且服务器的连接率不高于500次/秒,就可确保不会遇到TIME_WAIT端口耗尽问题。
  • 要修正这个问题,可以增加客户端负载生成机器的数量,或者确保客户端和服务器在循环使用几个虚拟IP地址以增加更多的连接组合
  • 即使没有遇到端口耗尽问题,也要特别小心有大量连接处于打开状态的情况,或者为处于等待状态的连接分配了大量控制块的情况。在这些情况下,有些操作系统的速度会严重减缓。

3. HTTP连接的处理

3.1. 常被误解的Connection首部

  • HTTP允许在客户端和最终的源端服务器之间存在一串HTTP中间实体(代理、高速缓存等)。可以从客户端开始,逐跳地将HTTP报文经过这些中间设备,转发到源端服务器上去(或反向传输)。
  • 某些情况下,两个相邻的HTTP应用程序会为它们共享的连接应用一组选项。HTTP的Connection首部字段中有一个由逗号分隔的连接标签列表,这些标签为此链接指定了一些不会传播到其他连接中去的选项。
  • Connection首部可以承载3种不同类型的标签:
    • HTTP首部字段名,列出了只与此连接有关的首部
    • 任意标签值,用于描述此连接的非标准选项
    • 值close,说明操作完成之后需要关闭这条持久连接
  • 如果连接标签中包含了一个HTTP首部字段的名称,那么这个首部字段就包含了一些连接有关的信息,不能将其转发出去。在将报文转发出去之前,必须删除Connection首部列出的所有首部字段。由于Connection首部可以防止无意中对本地首部的转发,因此将逐跳首部名放入Connection首部被称为“对首部的保护”
  • HTTP应用程序收到一条带有Connection首部的报文时,接收端会解析发送端请求的所有选项,并将其应用。然后会在将此报文转发给下一跳地址之前,删除Connection首部以及Connection中列出的所有首部。而且,可能还会有少量没有作为Connection首部值列出,但一定不能被代理转发的逐跳首部。其中包括Proxy-Authenticate、Proxy-Connection、Transfer-Encoding、Upgrade。

3.2. 串行事务处理时延

image.png

  • 如果只对连接进行简单的管理,TCP的性能时延可能会叠加起来。如果每个事务都需要一条新的连接,那么连接时延和慢启动时延就会叠加起来。
  • 除了串行加载引入的实际时延之外,加载一幅图片时,页面上其他地方都没有动静也会让人觉得速度很慢。用户希望能够同时加载多幅图片。
  • 串行加载的另一个缺点是,有些浏览器在对象加载完毕之前无法获知对象的尺寸,而且它们可能需要尺寸信息来决定将对象放在屏幕上的什么位置上,所以在加载了足够多的对象之前,无法在屏幕上显示任何内容。这种情况下,可能浏览器串行加载对象的进度很正常,但用户面对的却是一个空白的屏幕,对装载的进度一无所知。(HTML的设计者可以在图片等嵌入式对象的HTML标签中显示地添加宽高属性,以消除这种“布局时延”。)
  • 还有几种现存和新兴的方法可以提高HTTP的连接性能:
    • 并行连接:通过多条TCP连接发起并发的HTTP请求
    • 持久连接:重用TCP连接,以消除连接及关闭时延
    • 管道化连接:通过共享的TCP连接发起并发的HTTP请求
    • 复用的连接:交替传送请求和响应报文(实验阶段)

3.3. 并行连接

HTTP允许客户端打开多条连接,并行地执行多个HTTP事务。

3.3.1 并行连接可能会提高页面的加载速度

包含嵌入对象的组合页面如果能克服单条连接的空载时间和带宽限制,加载速度也会有所提高。时延可以叠加起来,而且如果单条连接没有充分利用客户端的因特网带宽,可以将未用带宽分配来装载其他对象。 image.png

3.3.2 并行连接不一定更快
  • 客户端的网络带宽不足时,大部分时间可能都是用来传送数据的。这种情况下,一个连接到速度较快服务器上的HTTP事务就会很容易地耗尽所有可用的Modem带宽。如果并行加载多个对象,每个对象都会去竞争这有限的带宽,每个对象都会以较慢的速度按比例加载,这样带来的性能提升就很小,甚至没什么提升。
  • 而且打开大量连接会消耗很多内存资源,从而引发自身的性能问题。
  • Web服务器通常要同时处理很多其他用户的请求,多个用户都是用并行连接,会造成服务器性能的严重下降,对高负荷的代理来说也是如此。
  • 实际上,浏览器确实使用了并行连接,但他们会将并行连接的总数限制为一个较小的值(通常是4个)。服务器可以随意关闭来自特定客户端的超量连接
3.3.3 并行连接可能让人“感觉”更快一些

即使实际上并行连接并没有加快页面的传输速度,并行连接通常也会让用户觉得页面加载得更快了,因为多个组件对象同时出现在屏幕上时,用户能够看到加载的进展。

3.4. 持久连接

  • Web客户端经常会打开到同一个站点的连接。因此,初始化了对某服务器HTTP请求的应用程序很可能会在不久的将来对那台服务器发起更多的请求。这种性质被称为站点局部性。
  • 因此,HTTP/1.1(以及HTTP/1.0的各种增强版本)允许HTTP设备在事务结束之后将TCP连接保持在打开状态,以便为未来的HTTP请求重用现存的连接。
  • 在事务处理结束之后仍然保持在打开状态的TCP连接被称为持久连接。直到客户端或服务器决定将其关闭。
  • 重用已对目标服务器打开的空闲持久连接,就可以避开缓慢的连接建立阶段。而且,已经打开的连接还可以避免慢启动的拥塞适应阶段,以便更快速地进行数据的传输
3.4.1 持久以及并行连接
  • 并行连接有一些缺点:
    • 每个事务都会打开/关闭一条新的连接,会耗费时间和带宽
    • 由于TCP慢启动特性的存在,每条新连接的性能都会有所降低
    • 可打开的并行连接数量实际上是有限的
  • 持久连接有一些比并行连接更好的地方:
    • 降低了时延和连接建立的开销,将连接保持在已调谐状态
    • 减少了打开连接的潜在数量
  • 但是管理持久连接时要特别小心,不然就会累积出大量的空闲连接,耗费本地以及远程客户端和服务器上的资源。
  • 持久连接与并行连接配合使用可能是最高效的方式。现在很多Web应用程序都会打开少量的并行连接,其中的每一个都是持久连接。
  • 持久连接有两种类型:较老的HTTP/1.0+ “keep-alive”连接、现代的HTTP/1.1 “presistent”连接
3.4.2 HTTP/1.0+ keep-alive连接

大约从1996年开始,很多HTTP/1.0浏览器和服务器都进行了扩展,以支持“keep-alive连接”的早期实验型持久连接。 image.png

3.4.3 Keep-Alive操作

浏览器和服务器对keep-alive握手的使用仍然相当广泛。

  • 实现HTTP/1.0 keep-alive连接的客户端可以通过包含Connection:Keep-Alive首部请求将一条连接保持在打开状态。
  • 如果服务器愿意为下一条请求将谅解保持在打开状态,就在响应中包含相同的首部。如果响应中没有Connection:Keep-Alive首部,客户端就认为服务器不支持keep-alive,会在发回响应报文后关闭连接。
3.4.4 Keep-Alive选项

Keep-Alive首部完全是可选的,但只有在提供Connection:Keep-Alive时才能使用它。可以用Keep-Alive通用首部中指定的、由逗号分隔的选项来调节keep-alive的行为:

  • 参数timeout是在Keep-Alive响应首部发送的。它估计了服务器希望将连接保持在活跃状态的时间。这并不是一个承诺值。
  • 参数max是在Keep-Alive响应首部发送的。它估计了服务器还希望多少个事务保持此连接的活跃状态。这并不是一个承诺值。
  • Keep-Alive首部还可支持任意未经处理的属性,这些属性主要用于诊断和调试。语法为name[=value]
3.4.5 Keep-Alive连接的限制和规则
  • 在HTTP/1.0中,keep-alive并不是默认使用的。客户端必须发送一个Connection:Keep-Alive请求首部来激活keep-alive连接
  • Connection:Keep-Alive首部必须随所有希望保持持久连接的报文一起发送。如果客户端没有发送Connection:Keep-Alive首部,服务器就会在那条请求之后关闭连接。
  • 通过检测响应中是否包含Connection:Keep-Alive响应首部,客户端可以判断服务器是否会在发出响应之后关闭连接
  • 只有在无需检测到连接的关闭即可确定报文主体部分长度的情况下,才能将连接保持在打开状态——也就是说主体部分必须有正确的Content-Length,有多部件媒体类型,或者用分块传输编码的方式进行了编码。在一条keep-alive信道中回送错误的Content-Length是很糟糕的事,这样事务处理的另一端就无法精确地检测出一条报文的结束和另一条报文的开始了。
  • 代理和网关必须执行Connection首部的规则。必须在将报文转发出去或将其告诉缓存之前,删除在Connection首部中命名的所有首部字段集Connection首部自身
  • 严格来说,不应该与无法确定是否支持Connection首部的代理服务器建立keep-alive连接,以防止哑代理问题。在实际应用中不是总能做到这一点
  • 从技术上来讲,应该忽略所有来自HTTP/1.0设备的Connection首部字段,因为他们可能是由比较老的代理服务器误转发的。但实际上,尽管可能会有在老代理上挂起的危险,有些客户端和服务器还是会违反这条规则
  • 除非重复发送请求会产生其他一些副作用,否则如果在客户端收到完整的响应之前连接就关闭了,客户端就一定要做好重试请求的准备
3.4.6 Keep-Alive和哑代理

Web客户端的Connection:Keep-Alive首部应该只会对这条离开客户端的TCP链路产生影响。

  1. Connection首部和盲中继
    • 问题出在代理上——尤其是那些不理解Connection首部,而且不知道在沿着转发链路将其发送出去之前,应该将首部删除的代理。很多老的或简单的代理都是盲中继,他们只是将字节从一个连接转发到另一个连接中去,不对Connection首部进行特殊的处理。
  2. 代理和逐跳首部
    • 为避免此类代理通信问题的发生,现代的代理都绝不能转发Connection首部和所有名字出现在Connection值中的首部。另外还有几个不能作为Connection首部值列出,也不能被代理转发或作为缓存响应使用的首部。其中包括Proxy-Authenticate、Proxy-Connection、Transfer-Encoding、Upgrade。
3.4.7 插入Proxy-Connection

Netscape的浏览器及代理实现者们提出了一个对盲中继问题的变通做法,这种做法并不要求所有Web应用程序支持高版本的HTTP。引入一个新首部Proxy-Connection,解决了在客户端后面紧跟着一个盲中继所带来的问题——并没有解决所有其他情况下存在的问题。在显示配置了代理的情况下,现代浏览器都实现了Proxy-Connection,很多代理都能够理解它。

  • 浏览器向代理发送非标准的Proxy-Connection首部,而不是官方支持的著名的Connection首部。如果代理是盲中继,它会将无意义的Proxy-Connection首部转发给Web服务器,服务器会忽略此首部,不会带来任何问题。但如果代理是个聪明的代理(能够理解持久连接的握手动作),就用一个Connection首部取代无意义的Proxy-Connection首部,然后将其发送给服务器以收到预期效果。
  • 在客户端和服务器之间只有一个代理时可以用这种方案来解决问题。但如果在哑代理的任意一侧还有一个聪明的代理,这个问题就会再次露头了。 image.png
  • 而且,网络中出现“不可见”代理的情况现在变得很常见了,这些代理可以是防火墙、拦截缓存,或者是反向代理服务器的加速器。这些设备对浏览器是不可见的,所以浏览器不会向它们发送Proxy-Connection首部。
3.4.8 HTTP/1.1持久连接

HTTP/1.1逐渐停止了对keep-alive连接的支持,用一种名为持久连接的改进型设计取代了它。

  • HTTP/1.1持久连接在默认情况下是激活的。除非特别指明,否则HTTP/1.1假定所有连接都是持久的。要在事务处理结束之后将连接关闭,HTTP/1.1应用程序必须向报文中显示地添加一个Connection:close首部。
  • HTTP/1.1客户端假定在收到响应后,除非响应中包含了Connection:close首部,不然HTTP/1.1连接就仍维持在打开状态。但是,客户端和服务器仍然可以随时关闭空闲的连接。不发送Connection:close并不意味着服务器承诺永远将连接保持在打开状态。
3.4.9 持久连接的限制和规则
  • 发送了Connection:close请求首部之后,客户端就无法在那条连接上发送更多的请求了
  • 如果客户端不想在连接上发送其他请求了,就应该在最后一条请求中发送一个Connection:close请求首部
  • 只有当连接上所有的报文都有正确的、自定义报文长度时(主体的长度都和相应的Content-Length一致,或用分块传输编码方式编码的),连接才能持久保持。
  • HTTP/1.1的代理必须能够分别管理与客户端和服务器的持久连接(每个持久连接都只适用于一跳传输)
  • HTTO/1.1的代理服务器不应该与HTTP/1.0客户端建立持久连接,除非它们了解客户端的处理能力。实际上,这一点是很难做到的,很多厂商都违背了这一原则。
  • 尽管服务器不应该试图在传输报文的过程中关闭连接,而且在关闭之前至少应该响应一条请求,但不管Connection首部取了什么值,HTTP/1.1设备都可以在任意时刻关闭连接。
  • HTTP/1.1应用程序必须能够从异步关闭中恢复出来,只要不存在可能会累积起来的副作用,客户端都应该重试这条请求。
  • 一个用户客户端对任何服务器或代理最多只能维护两条持久连接,以防止服务器过载。代理可能需要更多到服务器的连接来支持并发用户的通信,所以,如果有n个用户试图访问服务器,代理最多要维持2n条到任意服务器或父代理的连接。

3.5. 管道化连接

image.png HTTP/1.1允许在持久连接上可选地使用请求管道。这是相对于keep-alive连接的又一性能优化。在响应到达之前,可以将多条请求放入队列。在高时延的网络条件下,这样做可以降低网络的环回时间,提高性能。对管道化连接有几条限制:

  • 如果HTTP客户端无法确认连接是持久的,就不应该使用管道
  • 必须按照与请求相同的顺序回送HTTP响应。HTTP报文中没有序列号标签,因此如果收到的响应失序了,就没办法将其与请求匹配起来了。
  • HTTP客户端必须做好连接会在任意时刻关闭的准备,还要准备好重发所有未完成的管道化请求。
  • HTTP客户端不应该用管道化的方式发送会产生副作用的请求(如POST)。总之,出错的时候,管道化方式会阻碍客户端了解服务器执行的是一系列管道化请求中的哪一些。由于无法安全地重试POST这样的非幂等请求,所以出错时,就存在某些方法永远不会被执行的风险。

4. 关闭连接的奥秘

连接管理——尤其是知道在什么时候以及如何去关闭连接——是HTTP的实用魔法之一。

4.1. “任意”解除连接

  • 所有HTTP客户端、服务器或代理都可以在任意时刻关闭一条TCP传输连接。通常会在一条报文结束时关闭连接,但出错的时候,也可能在首部行的中间,或其他奇怪的地方关闭连接。
  • 对管道化持久连接来说,这种情形是很常见的。HTTP应用程序可以在经过任意一段时间之后,关闭持久连接。比如,在持久连接空闲一段时间之后,服务器可能会决定将其关闭。但服务器永远都无法确定在它关闭“空闲”连接的那一刻,线路那一头的客户端有没有数据要发送。

4.2. Content-Length及截尾操作

  • 每条HTTP响应都应该有精确的Content-Length首部,用以描述响应主体的尺寸。一些老的服务器会省略Content-Length首部,或者包含错误的长度指示,这样就要依赖服务器发出的连接关闭来说明数据的真实末尾。
  • 客户端或代理收到一条随连接关闭而结束的HTTP响应,且实际传输的实体长度与Content-Length并不匹配时,接收端就应该质疑长度的正确性
  • 如果接收端是个缓存代理,接收端就不应该缓存这条响应(以降低今后将潜在的错误报文混合起来的可能)。代理应该将有问题的报文原封不动地转发出去,而不应该试图去“校正”Content-Length,以维护语义的透明性

4.3. 连接关闭容限、重试以及幂等性

  • 即使在非错误情况下,连接也可以在任意时刻关闭。HTTP应用程序要做好正确处理非预期关闭的准备。如果在客户端执行事务的过程中,传输连接关闭了,那么,除非事务处理会带来一些副作用,否则客户端就应该重新打开连接,重试一次。对管道化连接来说,这种情况更严重一些。客户端可以将大量请求放入队列中排队,但源端服务器可以关闭连接,这样就会留下大量未处理的请求,需要重新调度
  • 副作用是很重要的问题。如果在发送一些请求数据之后,收到返回结果之前,连接关闭了,客户端就无法百分百确定服务器实际激活了多少事务。有些事务可以执行多次,如GET。而其他一些事务就不能重复执行,如POST,不然会有下多张订单的危险。
  • 如果一个事务,不管执行一次还是多次,得到的结果都相同,这个事务就是幂等的。实现者们可以认为GET、HEAD、PUT、DELETE、TRACE和OPTIONS方法都共享这一特性。客户端不应该以管道化的方式传送非幂等请求。否则,传输连接的过早终止就会造成一些不确定的后果。要发送一条非幂等请求,就需要等待来自前一条请求的响应状态。
  • 尽管用户Agent代理可能会让操作员来选择是否对请求进行重试,但一定不能自动重试非幂等方法或序列。

4.4. 正常关闭连接

TCP连接是双向的。TCP连接的每一端都有一个输入队列和一个输出队列,用于数据的读或写。

4.4.1 完全关闭与半关闭

应用程序可以关闭TCP输入和输出信道中的任意一个,或者将两者都关闭了。套接字调用close()会将TCP连接的输入和输出信道都关闭,这被称作“完全关闭”。还可以用套接字调用shutdown()单独关闭输入或输出信道,这被称为“半关闭”。

4.4.2 TCP关闭及重置错误
  • 简单的HTTP应用程序可以只使用完全关闭。但当应用程序开始与很多其他类型的HTTP客户端、服务器和代理进行对话且开始使用管道化持久连接时,使用半关闭来防止对等实体收到非预期的写入错误就变得非常重要了。
  • 总之,关闭连接的输出信道总是很安全的。连接另一端的对等实体会在从其缓冲区中读出所有数据之后收到一条通知,说明流结束了,这样它就知道你将连接关闭了
  • 关闭连接的输入信道比较危险,除非你知道另一端不打算再发送其他数据了。如果另一端向你已关闭的输入信道发送数据,操作系统就会向另一端的机器回送一条TCP“连接被对端重置”的报文。大部分操作系统都会将这种情况作为很严重的错误来处理,删除对端还未读取的所有缓存数据。对管道化连接来说,这是非常糟糕的事情。
  • 比如你已经在一条持久连接上发送了10条管道式请求了,响应也已经收到了,正在操作系统的缓冲区中存着呢(但应用程序还未将其读走)。现在,假设你发送了第11条请求,但服务器认为你使用的这条连接的时间已经够长了,决定将其关闭。那么你发送的第11条请求就会被发送到一条已关闭的连接上去,并会向你回送一条重置信息。这个重置信息会清空你的输入缓冲区。当你最终要去读取数据的时候,会得到一个连接被对端重置的错误,已缓存的未读响应数据都丢失了。
4.4.3 正常关闭

HTTP规范建议,当客户端或服务器突然要关闭一条连接时,应该“正常地关闭传输连接”,但它并没有说明应该如何去做。

  • 总之,实现正常关闭的应用程序首先应该关闭它们的输出信道,然后等待连接另一端的对等实体关闭它的输出信道。当两端都告诉对方它们不会再发送任何数据之后,连接就会完全关闭,而不会有重置的危险。
  • 但不幸的是,无法确保对等实体会实现半关闭,或对其进行检查。因此,想要正常关闭连接的应用程序应该先关闭其输出信道,然后周期性地检查其输入信道的状态(查找数据,或流的末尾)。如果在一定的时间内对端没有关闭输入信道,应用程序可以强制关闭连接,以节省资源。