2.2.1 Overview of HTTP
超文本传输协议(HyperText Transfer Protocol. HTTP)是一个网络层的协议。它是 Web 的核心,由两个程序组成,一个是服务端程序,一个是客户端程序。他们通过交换 HTTP 消息通信。 HTTP 协议定义了消息的结构。
Web page (也称作 document)由对象(object)组成。一个对象就是一个可以用URL寻址的文件,比如 HTML 文件, JPEG 图像,视频文件等。Web browser 实现了 HTTP 的客户端,而 Web server 实现了服务端,并存储 Web object。
HTTP 使用 TCP 而非 UDP 作为传输层协议。
- HTTP 客户端与服务端初始化 TCP 连接
- 连接建立,客户端和服务端通过 socket 使用 TCP 连接收发消息
使用 TCP 协议可以保证每个客户端发送的 HTTP 请求最终都到达服务端,同样的,每个服务端发送的 HTTP 响应都最终到达客户端。
需要重点指出的是,HTTP 是一个 无状态 协议。server 并不保存 client 的任何状态。如果某个 client 在一小段时间内连续请求同一个 object,server 会当作全新的请求处理。
2.2.2 Non-Persistent and Persistent Connections
网络应用的开发者需要做一个重要的决定——每一对请求和响应应该复用现有的 TCP 连接吗?
如果每次都建立并销毁一个 TCP 连接,这就叫做 non-persistent connections,如果复用连接,就叫做 persistent connections。HTTP 协议可以支持这两种连接方式,默认使用的是 persistent connections。
HTTP with Non-Persistent Connections
如果我们需要传送一个网页,假设该网页包含一个基础的 HTML 文件和 10 个 JPEG 图像。这 11 个对象都存放在同一个 server 中。基础 HTML 文件的 URL 是:
http://www.someschool.edu/someDepartment/home.index
对于 non-persistent connections,步骤为:
- HTTP client 与
www.someschool.edu
的 80 端口初始化 TCP 连接(80 是 HTTP 的默认端口号)。 - HTTP client 通过 socket 向 server 发送 HTTP 请求。请求消息中包含了路径
/someDepartment/home.index
。 - HTTP server 从 socket 接收请求,从内存或者硬盘中获取
/someDepartment/home.index
对应的 HTML 对象。把这个对象封装到 HTTP 响应消息中,并通过 socket 发送响应消息 - HTTP server 请求关闭 TCP 连接,但是会等确认 client 收到响应消息后才会关闭。
- HTTP client 收到响应消息,关闭 TCP 连接。响应消息中指明了封装的对象是一个 HTML 文件。 client 会把这个文件从响应消息中解压并检查,发现该 HTML 引用了 10 个 JPEG 对象。
- 重复 1 至 4 步骤传输被引用的 10 个 JPEG 对象。
浏览器经过以上步骤收到网页后,就将其展示给用户。不同浏览器可能以不同的方式展示。HTTP 并不规定 client 如何展示网页。
在上面的过程中,对于每一个对象,我们都建立并销毁了一个 TCP 连接。因此总共建立了 11 个 TCP 连接。对于后面 10 个 JPEG 对象的连接,他们可能并行。在现代浏览器中,一般维护 5 到 10 个并行的 TCP 连接。这样可以减少响应时间。
我们可以粗略估算这个过程的时间。首先定义 round-trip time (RTT),它表示将一个小数据包从 client 发送到 server 再发送回 client 的时间。
在步骤 1 建立 TCP 链接时,需要三次握手,当完成前两次时,已经消耗了 1 个 RTT 时间。第三次握手会随着 HTTP 请求一起发出,server 响应到达 client 时,第二个 RTT 时间也过去了。剩下的就是传输时间了。
因此总共花费的时间是 2 个 RTT 时间 + server 端传输时间。
estimate time for HTTP requestHTTP with Persistent Connections
non-persistent connections 显然有些缺点。
首先,对于每个请求的对象都需要重新建立 TCP 连接。对于这些连接,TCP 协议的缓冲区以及状态变量都需要 client 和 server 维护。这对于需要同时响应许多 client 的 server 端是非常大的负荷。
其次,如我们刚才计算的,每个对象传输都需要至少 2 个 RTT的时间,一个 RTT 用于建立 TCP 连接,另一个用于请求和接收对象。
HTTP 1.1 引入了 persistent connections,server 会在发送完响应后维持 TCP 连接。对于同一个 client 和 server,接下来的请求和响应可以通过同一个连接发送。因此,一整个网页可以通过一个 TCP 连接传输完毕。不止如此,对于多个在同一个 server 的网页,也可以通过一个 TCP 连接发送给同一个 client。
client 的多个请求可以连续发送,不需要等待响应。当 server 收到连续请求时,也连续发送对象。
通常,HTTP server 会在一个可配置的时间后关闭不再活跃的 TCP 连接。
2.2.3 HTTP Message Format
有两种类型的 HTTP 消息,请求消息和响应消息。
HTTP Request Message
下面是一个典型的 HTTP 请求消息:
GET /somedir/page.html HTTP/1.1
Host: www.someschool.edu
Connection: close
User-agent: Mozzila/5.0
Accept-language: fr
我们可以看出:
- HTTP 消息是 ASCII 码文本
- 第一行称为请求行(request line)
- 其他行称为 header line
- 最后一行只有换行符
请求行包括:
- 方法 (GET/POST/HEAD/PUT/DELETE)
- 请求对象的 URL
- HTTP 版本
header lines 的解释:
-
Host: www.someschool.edu
指明请求的对象所在的 host。虽然我们已经建立了 TCP 连接,似乎不需要再指明 host,但是这个信息是 Web 代理缓存。 -
Connection: close
表示不需要保持连接,即使用 non-persistent connections。 -
User-agent: Mozzila/5.0
浏览器信息是火狐5.0。由于 Web server 可能根据浏览器不同发送不同的对象,所以这个信息也是需要的。 -
Accept-language: fr
表示 client 希望收到的语言类型。fr
表示法语。Web server 如果有该版本的对象则返回那个对象,否则则返回默认版本。
HTTP 请求的消息格式如下图所示:
HTTP request message
我们需要注意的是最后的 Entity body
。当我们使用 GET
方法时,它为空。但是当使用 POST
时,它会被使用。
例如当用户使用搜索引擎时,会将搜索的内容填入 Entity body
并用 POST
方法给 server。但是,使用 GET
也同样可以实现这个功能。比如将用户输入的内容包含在请求的 URL 中。例如用户输入了 monkeys
和bananas
,GET
方法的 URL 可能是 www.somesite.com/animalseach?monkeys&bananas
。
HEAD
方法和 GET
方法相似,当 server 收到 HEAD
方法请求时,会回复 HTTP 消息但是并不传输请求的对象。通常会用于开发者的调试。PUT
方法通常结合 Web 发布工具使用。它允许用户上传文件到指定的路径。DELETE
方法允许从 �Web 服务器上删除某个对象。
HTTP Response Message
server 收到请求后,发出响应消息。如下所示:
HTTP/1.1 200 OK
Connection: close
Date: Tue, 18 Aug 2015 15:44:04 GMT
Server: Apache/2.2.3 (CentOS)
Last-Modified: Tue, 18 Aug 2015 15:11:03 GMT
Content-Length: 6821
Content-Type: text/html
(data data data data data ...)
响应消息有三个部分:
- 状态行(status line)
包含协议版本,状态码,状态消息。该行表示 server 使用 HTTP 版本 1.1,运行正常,已经找到并正在发送请求对象。 - header lines
见下文逐行分析。 - entity body
请求对象。
header lines 有 6 行。
-
Connection: close
server 告诉 client 在发送完请求对象后将关闭 TCP 连接。 -
Date: Tue, 18 Aug 2015 15:44:04 GMT
表示 HTTP 响应被创建的日期和时间。 -
Server: Apache/2.2.3 (CentOS)
响应的 server 使用的程序类型,类似于请求消息中的User-agent
。 -
Last-Modified: Tue, 18 Aug 2015 15:11:03 GMT
对于缓存至关重要。包括 client 的本地缓存以及网络缓存服务器。 -
Content-Length: 6821
要发送的请求对象的 byte 数。 -
Content-Type: text/html
请求对象的类型是一个 HTML 文本。
响应消息的格式如下图:
HTTP response message一些常见的状态码及其含义如下表所示:
status code | phrase | meaning |
---|---|---|
200 | OK | 请求成功,请求对象通过响应返回 |
301 | Moved Permanently | 请求对象已经被移动另一个 URL(在 Location 中)。 client 会自动尝试新的 URL |
400 | Bad Request | server 不识别该请求 |
404 | Not Found | 请求的网页不存在 |
505 | HTTP Version Not Supported | server 不支持 client 请求的 HTTP 协议版本 |
2.2.4 User-Server Interaction: Cookies
HTTP server 是无状态的。这使得 server 可以处理成千上万个同时的 TCP 连接。然而,有时我们希望网站能识别用户,例如通过用户的权限来限制访问。HTTP 用 cookies 来实现这个需求。
cookies 技术包含四个组件:
- HTTP 响应消息中的 cookie header line
- HTTP 请求消息中的 cookie header line
- 浏览器维护的 client 端 cookie 文件
- server 端的数据库
其工作原理如下图所示:
Keeping user state with cookies
假设某个网购者曾经登录过 eBay,但是是首次登录 amazon,当请求发送到 amazon 时,amazon server 会创建一个唯一的 id,并以此为索引存储在数据库中。然后发送响应消息给网购者。该响应消息包含一个额外的 header line:
Set-cookie: 1678
这个 1678
就是 amazon 为用户创建的唯一 id。网购者的浏览器会将其存储在本地的 cookie 文件中。包含 server 的主机名和对应的 id。注意,cookie 文件中原本就存在 eBay 的 id,因为网购者曾经访问过。当他再次访问 amazon 时,浏览器会读取 cookie 文件并将 amazon 的 id 加入到请求消息中:
Cookie: 1678
这样,amazon 就能记录用户的活动。如果网购者已经注册并填写了信息,那 amazon 也能将这些信息存放到数据库,并与 id 相关联。这就是为什么不需要每次都输入信息。
虽然 cookies 优化了用户的浏览体验,但是它也破坏了隐私性。网站可以通过 cookies 追踪用户行为,或者出售用户个人信息。
2.2.5 Web Caching
Web 缓存,又被称作代理服务器 (proxy server) 是某个代替原本的 server 响应 client 请求的网络实体。
代理服务器将最近请求对象的拷贝存放在自己的存储器中。用户可以配置浏览器使 HTTP 请求首先被发送到代理服务器。
- 浏览器与代理服务器建立 TCP 连接并向其发送 HTTP 请求
- 代理服务器检查对象是否已经存储在本地,如果是则返回该对象,否则进行第 3 步
- 代理服务器没有在本地找到请求对象,它会与原始的 server 建立 TCP 连接并请求对象。
- 代理服务器收到对象后,在本地存储并发送 HTTP 响应给浏览器。
值得注意的是,代理服务器是一个 server,也是一个 client。当它发送对象给浏览器时是一个 server,当它从原始 server 接收对象时,是一个 client。
代理服务器的主要作用是:
- 减少 client 请求所需的响应时间
- 减少网络流量
代理服务器应用在内容分发网络 (Content Distribution Networks, CDNs)中。
The Conditional GET
代理服务器引入了一个新的问题——它本地存储的对象可能是过期的。即,原始 server 上可能已经更改。HTTP 有一个机制可以检查缓存的对象是否是最新的。这个机制就叫做 conditional GET。
- 某个浏览器发送 HTTP 请求给代理服务器,代理服务器并没有找到本地存储,于是向原始 server 发送了如下 HTTP request:
GET /fruit/kiwi.gif HTTP/1.1
Host: www.exotiquecuisine.com
原始 server 发送了如下响应给代理服务器:
HTTP/1.1 200 OK
Date: Sat, 3 Oct 2015 15:39:29
Server: Apache/1.3.0 (Unix)
Last-Modified: Wed, 9 Sep 2015 09:23:24
Content-Type: image/gif
(data data data data data ...)
-
代理服务器将对象存储在本地,并转发给浏览器。关键的,
Last-Modified
也和对象一起存储。 -
当代理服务器收到浏览器对同一个对象的请求时,它首先利用 conditional GET 做一个检查:
GET /fruit/kiwi.gif HTTP/1.1
Host: www.exotiquecuisine.com
If-modified-since: Wed, 9 Sep 2015 09:23:24
注意这里 If-modified-since
使用了之前的 Last-Modified
的值。
如果原始 server 端没有更新,则会发送以下响应给代理服务器:
HTTP/1.1 304 Not Modified
Date: Sat, 10 Oct 2015 15:39:29
Server: Apache/1.3.0 (Unix)
(empty entity body)
这个响应并不发送请求对象,通过状态码 304 Not Modified
告诉 代理服务器可以直接将本地拷贝的对象发送给浏览器。
(HTTP 协议部分到此结束。我将跳过 2.3 节关于电子邮件的介绍)
网友评论