HTTP 概述
HTTP —— 因特网的多媒体信使,服务于客户端与服务器之间。
资源
媒体类型
Web 服务器会为所有 HTTP 对象数据附加一个 MIME 类型。当 Web 浏览器从服务器中取回一个对象时,会去查看相关的 MIME 类型,看看它是否知道 应该如何处理这个对象。
MIME 类型是一种文本标记,表示一种主要的对象类型和一个特定的子类型,中间 由一条斜杠来分隔。
URI
服务器资源名被称为统一资源标识符(Uniform Resource Identifier,URI),URI 有两种形式,分别称为 URL 和 URN。
- URL:统一资源定位符(URL),描述一台特定服务器上某资源的特定位置。
- URN:统一资源名(URN),作为特定内容的唯一名称使用 的,与目前的资源所在地无关。 现在,几乎所有的 URI 都是 URL。
事务
一个 HTTP 事务由一条(从客户端发往服务器的)请求命令和一个(从服务器 发回客户端的)响应结果组成。
HTTP 方法
HTTP 支持几种不同的请求命令,这些命令被称为 HTTP 方法(HTTP method)。常见的有:
状态码
每条 HTTP 响应报文返回时都会携带一个状态码。常见的有:
报文
HTTP 报文是由一行一行的简单字符串组成的。
-
请求报文:从 Web 客户端发往 Web 服务器的 HTTP 报文称为请求报文(request message)。
-
响应报文:从服务器发往客户端的报文称为响应报文(response message),
连接
了解 HTTP 报文是如何通过传输控制协议 (Transmission Control Protocol,TCP)连接从一个地方搬移到另一个地方去的。
TCP/IP
HTTP 是个应用层协议。HTTP 无需操心网络通信的具体细节;它把联网的细节都 交给了通用、可靠的因特网传输协议 TCP/IP。
TCP 提供了:
- 无差错的数据传输;
- 按序传输(数据总是会按照发送的顺序到达);
- 未分段的数据流(可以在任意时刻以任意尺寸将数据发送出去)。
用网络术语来说,HTTP 协议位于 TCP 的上层。HTTP 使用 TCP 来传输其报文数 据。与之类似,TCP 则位于 IP 的上层
协议版本
- HTTP/0.9 HTTP/0.9 定义的初衷是为了获取 简单的 HTML 对象,它很快就被 HTTP/1.0 取代了。
- HTTP/1.0 1.0 是第一个得到广泛使用的 HTTP 版本。添加了版本号、各种 HTTP 首部、一些额外的方法,以及对多媒体对象的处理。
- HTTP/1.0+ 非正式(非官方扩展)的 HTTP 扩展版本通常 称为 HTTP/1.0+。
- HTTP/1.1 HTTP/1.1 重点关注的是校正 HTTP 设计中的结构性缺陷,明确语义,引入重要 的性能优化措施,并删除一些不好的特性。是当前 使用的 HTTP 版本。
- HTTP-NG(又名 HTTP/2.0) HTTP-NG 是 HTTP/1.1 后继结构的原型建议,它重点关注的是性能的大幅优化, 以及更强大的服务逻辑远程执行框架。
Web 的结构组件
除了(Web 浏览器和 Web 服务器)这两个 Web 应用程序参与交互外,在因特网上,还有一些其他比较重要的应用程序。如下所示。
- 代理:位于客户端和服务器之间的 HTTP 中间实体。
- 缓存:HTTP 的仓库,使常用页面的副本可以保存在离客户端更近的地方。
- 网关:连接其他应用程序的特殊 Web 服务器。
- 隧道:对 HTTP 通信报文进行盲转发的特殊代理。
- Agent:代理发起自动 HTTP 请求的半智能 Web 客户端,如:Web 浏览器,网络爬虫。
URL与资源
URL 语法
大多数 URL 方案的 URL 语法都建立在这个由 9 部分构成的通用格式上:
<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>
其中最重要的是:方案(scheme)、 主机(host)和路径(path)。
常见的方案 scheme 描述
HTTP 报文
报文流
- 报文流入源端服务器,工作完成之后,会流回用户的 Agent 代理中
- 不管是请求报文还是响应报文,所有报文都会向 下游(downstream)流动
报文的组成三部分
- 对报文进行描述的起始行(start line)
- 包含属性的首部(header)块
- 包含数据的主体(body)部分
报文的语法
请求报文:
<method> <request-URL> <version> // 起始行
<headers> // 首部
<entity-body> // 主体
响应报文:
<version> <status-code> <reason-phrase> // 起始行
<headers> // 首部
<entity-body> // 主体
注意:请求报文和响应报文,只有起始行的语法有所不同
- 请求:<请求方法><请求URL><HTTP版本>
- 响应:<HTTP版本><状态码><原因短句>
假想的请求和响应报文
方法
GET
GET 是最常用的方法。通常用于请求服务器发送某个资源。
HEAD
HEAD 方法与 GET 方法的行为很类似,但服务器在响应中只返回首部。不会返回实体的主体部分。 使用 HEAD,可以:
- 在不获取资源的情况下了解资源的情况(比如,判断其类型);
- 通过查看响应中的状态码,看看某个对象是否存在;
- 通过查看首部,测试资源是否被修改了。
PUT
与 GET 从服务器读取文档相反,PUT 方法会向服务器写入文档。
POST
POST 方法起初是用来向服务器输入数据的。实际上,通常会用它来支持 HTML 的表单。
TRACE
客户端发起一个请求时,这个请求可能要穿过防火墙、代理、网关或其他一些应用程序。每个中间节点都可能会修改原始的 HTTP 请求。TRACE 方法允许客户端在最终将请求发送给服务器时,看看它变成了什么样子。
TRACE 请求会在目的服务器端发起一个“环回”诊断。行程最后一站的服务器会弹回一条 TRACE 响应,并在响应主体中携带它收到的原始请求报文。这样客户端就可以查看在所有中间 HTTP 应用程序组成的请求 / 响应链上,原始报文是否被毁坏或修改过。
OPTIONS
OPTIONS 方法请求 Web 服务器告知其支持的各种功能。可以询问服务器通常支持 哪些方法,或者对某些特殊资源支持哪些方法。(有些服务器可能只支持对一些特殊 类型的对象使用特定的操作)
DELETE
DELETE 方法所做的事情就是请服务器删除请求 URL 所指定的资源。 但是,客户端应用程序无法保证删除操作一定会被执行。因为 HTTP 规范允许服务 器在不通知客户端的情况下撤销请求。
状态码
100~199——信息性状态码
HTTP/1.1 向协议中引入了信息性状态码。这些状态码相对较新,关于其复杂性和感 知价值存在一些争论,而受到限制。
200~299——成功状态码
客户端发起请求时,这些请求通常都是成功的。服务器有一组用来表示成功的状态 码,分别对应于不同类型的请求。
300~399——重定向状态码
重定向状态码要么告知客户端使用替代位置来访问他们所感兴趣的资源,要么就提供一个替代的响应而不是资源的内容。如果资源已被移动,可发送一个重定向状态码和一个可选的 Location 首部来告知客户端资源已被移走,以及现在可以在哪里找到它。这样,浏览器就可以在不打扰使用者的情况下,透明地转入新的位置了。
如:304 (Not Modified) 服务器告知客户端可以直接从本地缓存读取资源
你可能已经注意到 302、303 和 307 状态码之间存在一些交叉。这些状 态码的用法有着细微的差别,大部分差别都源于 HTTP/1.0 和 HTTP/1.1 应用程序对 这些状态码处理方式的不同。
400~499——客户端错误状态码
有时客户端会发送一些服务器无法处理的东西,比如格式错误的请求报文,或者最常见的是,请求一个不存在的 URL。
500~599——服务器错误状态码
有时客户端发送了一条有效请求,服务器自身却出错了。这可能是客户端碰上了服 务器的缺陷,或者服务器上的子元素,比如某个网关资源,出了错。
首部
首部和方法配合工作,共同决定了客户端和服务器能做什么事情。可以将首部分为五个主要的类型:
通用首部
是客户端和服务器都可以使用的通用首部。
- 通用的信息性首部
- 通用的缓存首部
注意:从技术角度来看,Pragma 是一种请求首部。从未被指定用于响应首部。由于经常被错误地用于响应首部,很多客户端和代理都会将 Pragma 解释为响应首部,但其确切语义并未得到很好地定义。任何情况下 Cache-Control 的使用都优于 Pragma。
请求首部
就是请求报文特有的。
-
请求的信息性首部
-
请求的 Accept 首部
-
条件请求首部
-
安全请求首部
-
代理请求首部
响应首部
响应报文有自己的首部集,以便为客户端提供信息
- 响应的信息性首部
- 协商响应首部 HTTP/1.1 可以为服务器和客户端提供对资源进行协商的能力。
- 安全响应首部
实体首部
实体首部提供了有关实体及其内容的大量信息,从有关对象类型的信息,到能够对资源使用的各种有效的请求方法。
- 实体的信息性首部
-
内容实体首部 内容首部提供了与实体内容有关的特定信息,说明了其类型、尺寸以及处理它所需 的其他有用信息。
-
实体缓存首部 通用的缓存首部说明了如何或什么时候进行缓存。实体的缓存首部提供了与被缓存实体有关的信息
扩展首部
扩展首部是非标准的首部,由应用程序开发者创建,但还未添加到已批准的 HTTP 规范中去。
连接管理
TCP连接
世界上几乎所有的 HTTP 通信都是由 TCP/IP 承载的,TCP/IP 是全球计算机及网络 设备都在使用的一种常用的分组交换网络分层协议集。
TCP的可靠数据管道
HTTP 连接实际上就是 TCP 连接及其使用规则。TCP 为 HTTP 提供了一条可靠的比特传输管道。从 TCP 连接一端填入的字节会从另 一端以原有的顺序、正确地传送出来。
TCP流是分段的、由IP分组传送
HTTP 要传送一条报文时,会以流的形式将报文数据的内容通过一条打开的 TCP 连接按序传输。TCP 收到数据流之后,会将数据流砍成被称作段的小数据块,并将段封装在 IP 分组中,通过因特网进行传输。
每个 TCP 段都是由 IP 分组承载,从一个 IP 地址发送到另一个 IP 地址的。每个 IP 分组中都包括:
- 一个 IP 分组首部(通常为 20 字节);
- 一个 TCP 段首部(通常为 20 字节);
- 一个 TCP 数据块(0 个或多个字节)
保持TCP连接持续不断地运行
TCP 连接是通过 4 个值来识别的:
< 源 IP 地址、源端口号、目的 IP 地址、目的端口号 >
这 4 个值一起唯一地定义了一条连接。两条不同的 TCP 连接不能拥有 4 个完全相同 的地址组件值(但不同连接的部分组件可以拥有相同的值)
- 4 个不同的 TCP 连接
用TCP套接字编程
操作系统提供了一些操纵其 TCP 连接的工具。
套接字 API 允许用户创建 TCP 的端点数据结构,将这些端点与远程服务器的 TCP 端点进行连接,并对数据流进行读写。TCP API 隐藏了所有底层网络协议的握手细 节,以及 TCP 数据流与 IP 分组之间的分段和重装细节。
- 对TCP连接进行编程所需的常见套接字接口函数
对 TCP 性能的考虑
HTTP事务的时延
HTTP 事务的时延有以下几种主要原因:
- 客户端需要根据 URI 确定 Web 服务器的 IP 地址和端口号的耗时
- TCP 连接都会有连接建立时延
- 客户端就会通过新建立的 TCP 管道来发送 HTTP 请求。 数据到达时,Web 服务器会从 TCP 连接中读取请求报文,并对请求进行处理。
- 然后,Web 服务器会回送 HTTP 响应,这也需要花费时间
TCP 相关时延
- TCP 连接建立握手;
- TCP 慢启动拥塞控制;
- 数据聚集的 Nagle 算法;
- 用于捎带确认的 TCP 延迟确认算法;
- TIME_WAIT 时延和端口耗尽。
TCP连接的握手时延
- 在发送数据之前,TCP 要传送两个分组来建立连接
TCP 连接握手需要经过以下几个步骤。
-
请求新的 TCP 连接时,客户端要向服务器发送一个小的 TCP 分组(通常是 40 ~ 60 个字节)。这个分组中设置了一个特殊的 SYN 标记,说明这是一个连接请求。 (参见图 4-8a)。
-
如果服务器接受了连接,就会对一些连接参数进行计算,并向客户端回送一个 TCP 分组,这个分组中的 SYN 和 ACK 标记都被置位,说明连接请求已被接受 (参见图 4-8b)。
-
最后,客户端向服务器回送一条确认信息,通知它连接已成功建立(参见图 4-8c)。 现代的 TCP 栈都允许客户端在这个确认分组中发送数据。
HTTP 程序员永远不会看到这些分组——这些分组都由 TCP/IP 软件管理,对其是不 可见的。HTTP 程序员看到的只是创建 TCP 连接时存在的时延。
延迟确认
由于因特网自身无法确保可靠的分组传输(因特网路由器超负荷的话,可以随意丢弃分组),所以 TCP 实现了自己的确认机制来确保数据的成功传输。
每个 TCP 段都有一个序列号和数据完整性校验和。每个段的接收者收到完好的段时,都会向发送者回送小的确认分组。如果发送者没有在指定的窗口时间内收到确认信息,发送者就认为分组已被破坏或损毁,并重发数据。
由于确认报文很小,所以 TCP 允许在发往相同方向的输出数据分组中对其进行“捎带”。TCP 将返回的确认信息与输出的数据分组结合在一起,可以更有效地利用网络。为了增加确认报文找到同向传输数据分组的可能性,很多 TCP 栈都实现了一种“延迟确认”算法。延迟确认算法会在一个特定的窗口时间(通常是 100 ~ 200 毫秒)内将输出确认存放在缓冲区中,以寻找能够捎带它的输出数据分组。如果在那个时间段内没有输出数据分组,就将确认信息放在单独的分组中传送。
但是,HTTP 具有双峰特征的请求 - 应答行为降低了捎带信息的可能。当希望有相反方向回传分组的时候,偏偏没有那么多。通常,延迟确认算法会引入相当大的时延。根据所使用操作系统的不同,可以调整或禁止延迟确认算法。
TCP慢启动
TCP 数据传输的性能还取决于 TCP 连接的使用期(age)。TCP 连接会随着时间进行自我“调谐”,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移 提高传输的速度。这种调谐被称为 TCP 慢启动(slow start),用于防止因特网的突然过载和拥塞。
TCP 慢启动限制了一个 TCP 端点在任意时刻可以传输的分组数。简单来说,每成功接收一个分组,发送端就有了发送另外两个分组的权限。如果某个 HTTP 事务有大量数据要发送,是不能一次将所有分组都发送出去的。必须发送一个分组,等待确认;然后可以发送两个分组,每个分组都必须被确认,这样就可以发送四个分组了,以此类推。这种方式被称为“打开拥塞窗口”。
由于存在这种拥塞控制特性,所以新连接的传输速度会比已经交换过一定量数据的、 “已调谐”连接慢一些。由于已调谐连接要更快一些,所以 HTTP 中有一些可以重用 现存连接的工具。如:HTTP“持久连接”。
Nagle算法与TCP_NODELAY
每个 TCP 段中都至少装载了 40 个字节的标记和首部,所以如果 TCP 发送了大量包含少量数据的分组,网络的性能就会严重下降。
Nagle 算法(根据其发明者 John Nagle 命名)试图在发送一个分组之前,将大量 TCP 数据绑定在一起,以提高网络效率。
Nagle 算法鼓励发送全尺寸(LAN 上最大尺寸的分组大约是 1500 字节,在因特网 上是几百字节)的段。只有当所有其他分组都被确认之后,Nagle 算法才允许发送非全尺寸的分组。如果其他分组仍然在传输过程中,就将那部分数据缓存起来。只有当挂起分组被确认,或者缓存中积累了足够发送一个全尺寸分组的数据时,才会将缓存的数据发送出去。
Nagle 算法会引发几种 HTTP 性能问题。首先,小的 HTTP 报文可能无法填满一个分组,可能会因为等待那些永远不会到来的额外数据而产生时延。其次,Nagle 算 法与延迟确认之间的交互存在问题——Nagle 算法会阻止数据的发送,直到有确认 分组抵达为止,但确认分组自身会被延迟确认算法延迟 100 ~ 200 毫秒。
HTTP 应用程序常常会在自己的栈中设置参数 TCP_NODELAY,禁用 Nagle 算法, 提高性能。如果要这么做的话,一定要确保会向 TCP 写入大块的数据,这样就不会产生一堆小分组了
HTTP连接的处理
常被误解的Connection首部
上面提到 Connection 是通用首部。
HTTP 允许在客户端和最终的源端服务器之间存在一串 HTTP 中间实体(代理、高 速缓存等)。在某些情况下,两个相邻的 HTTP 应用程序会为它们共享的连接应用一组选项。HTTP 的 Connection 首部字段中有一个由逗号分隔的连接标签列表,这些标签为此连接 指定了一些不会传播到其他连接中去的选项。
Connection 首部可以承载 3 种不同类型的标签,因此有时会很令人费解:
- HTTP 首部字段名,列出了只与此连接有关的首部;
- 任意标签值,用于描述此连接的非标准选项;
- 值 close,说明操作完成之后需关闭这条持久连接。
比如,可以用 Connection: close 来说明发送完下一条报文之后必须关闭的连接。
串行事务处理时延
如果只对连接进行简单的管理,TCP 的性能时延可能会叠加起来。就导致网页加载很慢。
- 4 个事务(串行)
为此还要几种现存和新兴的方法可以提高 HTTP 的连接性能。
- 并行连接 通过多条 TCP 连接发起并发的 HTTP 请求。
- 持久连接 重用 TCP 连接,以消除连接及关闭时延。
- 管道化连接 通过共享的 TCP 连接发起并发的 HTTP 请求。
- 复用的连接 交替传送请求和响应报文(实验阶段)
并行连接
HTTP 允许客户端打开多条连接,并行地执行多个 HTTP 事务。
- 4 个事务(并行)
并行连接一些缺点。
带宽不足,打开大量连接会消耗很多内存资源,从而引发自身的性能问题。导致可打开的并行连接有限。
- 每个事务都会打开 / 关闭一条新的连接,会耗费时间和带宽。
- 由于 TCP 慢启动特性的存在,每条新连接的性能都会有所降低。
- 可打开的并行连接数量实际上是有限的。
持久连接
HTTP/1.1(以及 HTTP/1.0 的各种增强版本)允许 HTTP 设备在事务处理结束 之后将 TCP 连接保持在打开状态,以便为未来的 HTTP 请求重用现存的连接。在事务处理结束之后仍然保持在打开状态的 TCP 连接被称为持久连接。
持久连接有两种类型:
- 比较老的 HTTP/1.0+“keep-alive”连接
- 以及现代的 HTTP/1.1“persistent”连接。
HTTP/1.0+ keep-alive连接
Keep-Alive操作
实现 HTTP/1.0 keep-alive 连接的客户端可以通过包含 Connection: Keep-Alive 首部请求将一条连接保持在打开状态。
Keep-Alive首部
Keep-Alive 通用首部中指定的、由逗号分隔的选项来调节 keep-alive 的 行为。
- 这个例子说明服务器最多还会为 另外 5 个事务保持连接的打开状态,或者将打开状态保持到连接空闲了 2 分钟之后。(timeout 和 max 都是响应首部发送的)
Connection: Keep-Alive
Keep-Alive: max=5, timeout=120
Keep-Alive连接的限制和规则
- 在 HTTP/1.0 中,keep-alive 并 不 是 默 认 使 用 的。 客 户 端 必 须 发 送 一 个 Connection: Keep-Alive 请求首部来激活 keep-alive 连接。
- 严格来说,不应该与无法确定是否支持 Connection 首部的代理服务器建立 keep-alive 连接,以防止出现下面要介绍的哑代理问题。
Keep-Alive和哑代理
keep-alive 无法与不支持 Connection 首部的代理进行互操作