HTTP 协议简介

2016-09-09 linux network

HTTP 协议在 TCP/IP 协议之上,目前已经成为了互联网的基础协议,也是网页开发的必备知识,这里简单介绍下 HTTP 协议的历史演变、设计思路,以及相关的解析实现。

http introduce

简介

HTTP 协议到现在为止总共经历了 3 个版本的演化,第一个 HTTP 协议诞生于 1989.3 。

换行符

在 Web 的发展过程中,UNIX 系统从一开始到现在一直是主要的开发和运行平台,但是在 HTTP 协议中,各个 header 之间用的却是 CRLF (\r\n) 这样的 Windows/DOS 换行方式,而不是 UNIX 普遍的的 LF (\n) 换行方式。

实际上 ASCII 沿袭了传统打字机的设计,也就是说在某种意义上来说 CR-LF 才是正统。

http introduce

左侧墨带盒上边的金属手柄了就是换行手柄。

打完一行字之后,滚筒已经基本移出了打字机主体,需要推左手边的换行手柄,然后滚筒会被推到最右边,打字头对准了行首,这就是 CR 。

推到头之后,继续用力,换行手柄大约会被扳动 30 度左右,这时候你会发现纸往上卷了一行,这就是 LF 。

所以遵循传统,ASCII 设计了 CR-LF 顺序。

HTTP/0.9

规定了客户端和服务器之间的通信格式,默认使用 80 端口,最早版本是 1991 年发布的 0.9 版。

该版本极简单,只允许客户端发送 GET 这一种请求,不支持请求头。由于没有协议头,造成了 HTTP 0.9 协议只支持一种内容,即纯文本,网页仍支持 HTML 格式化,不过无法插入图片。

HTTP 0.9 具有典型的无状态性,每个事务独立进行处理,事务结束时就释放这个连接,也就是说,HTTP 协议的无状态特点在其第一个版本 0.9 中已经成型。

该版本只有一个命令 GET 。

GET /index.html

上面命令表示,TCP 连接建立后,客户端向服务器请求网页index.html ;协议规定,服务器只能回应 HTML 格式的字符串,不能回应别的格式。

<html>
    <body>Hello World</body>
</html>

服务器发送完毕,就关闭 TCP 连接。

HTTP/1.0

1996.05 该版本发布,内容大大增加,主要包括了如下特性:

  1. 除了数据部分,请求与响应支持头信息 (HTTP Header),用来描述一些元数据。
  2. 响应对象以一个响应状态行开始,而且支持图像、视频、二进制文件等多种格式。
  3. 还引入了 POST、HEAD 方法,丰富了浏览器与服务器的互动手段。
  4. 默认仍然使用短连接,但是支持长连接。

其它新增功能还包括状态码 (Status Code)、多字符集支持、多部分发送 (Multi-part Type)、权限 (Authorization)、缓存 (Cache)、内容编码 (Content Encoding) 等。

请求格式

下面是一个 1.0 版的 HTTP 请求的例子。

GET / HTTP/1.0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*

第一行是请求命令,尾部添加协议版本 HTTP/1.0,后面就是多行头信息,描述客户端的情况。

响应格式

服务器的回应如下。

HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84

<html>
	<body>Hello World</body>
</html>

格式是 头信息 + 一个空行 (\r\n) + 数据,第一行是 协议版本 + 状态码 (status code) + 状态描述

Content-Type 字段

关于字符的编码,1.0 版规定,头信息必须是 ASCII 码,后面的数据可以是任何格式,因此,服务器响应时候,必须告诉客户端,数据是什么格式,这就是 Content-Type 字段的作用。

下面是一些常见的 Content-Type 字段的值。

text/plain
text/html
text/css
image/jpeg
image/svg+xml
audio/mp4
video/mp4
application/pdf
application/atom+xml

这些数据类型总称为 MIME Type,每个值包括一级类型和二级类型,之间用斜杠分隔。

MIME Type 不仅用在 HTTP 协议,还可以用在其它地方,比如 HTML 网页。

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- 等同于 -->
<meta charset="utf-8" />

Content-Encoding 字段

由于发送的数据可以是任何格式,因此可以把数据压缩后再发送,Content-Encoding 字段说明数据的压缩方法。

Content-Encoding: gzip
Content-Encoding: compress
Content-Encoding: deflate

客户端在请求时,用 Accept-Encoding 字段说明自己可以接受哪些压缩方法,例如:

Accept-Encoding: gzip, deflate

Content-Range 字段

一般断点下载时才会用到 RangeContent-Range 实体头;其中 Range 用于请求头,指定第一个字节的位置和最后一个字节的位置,如 Range: 200-300;而 Content-Range 用于响应头。

其示例请求头如下:

GET /test.rar HTTP/1.1
Connection: close
Host: 116.1.219.219
Range: bytes=0-100

其中,Range 头可以请求实体的一个或者多个子范围,第一个字节是 0 ,常见的表示如下:

bytes=0-499      头500个字节
bytes=500-999    第二个500字节
bytes=-500       最后500个字节
bytes=500-       500字节以后的范围
bytes=0-0,-1     第一个和最后一个字节
bytes=500-600,601-999  同时指定几个范围

一般正常响应如下:

HTTP/1.1 206 OK
Content-Length:  801
Content-Type:  application/octet-stream
Content-Location: http://www.onlinedown.net/hj_index.htm
Content-Range: bytes 0-100/2350
Last-Modified: Mon, 16 Feb 2009 16:10:12 GMT
Accept-Ranges: bytes

上述的响应头中,其中的 2350 表示文件总大小,如果用户的请求中含有 Range 且服务器支持断点续传,那么服务器的相应代码为 206

缺点

HTTP/1.0 版的主要缺点是,每个 TCP 连接只能发送一个请求,发送数据完毕,连接就关闭,如果还要请求其它资源,就必须再新建一个连接。

TCP 连接的新建成本很高,因为需要客户端和服务器三次握手,并且开始时发送速率较慢 (slow start)。所以,HTTP 1.0 版本的性能比较差,尤其随着网页加载的外部资源越来越多,这个问题就愈发突出了。

为了解决这个问题,有些浏览器在请求时,用了一个非标准的 Connection 字段。

Connection: keep-alive

这个字段要求服务器不要关闭 TCP 连接,以便其他请求复用,服务器同样回应这个字段。

Connection: keep-alive

一个可以复用的TCP连接就建立了,直到客户端或服务器主动关闭连接。但是,这不是标准字段,不同实现的行为可能不一致,因此不是根本的解决办法。

HTTP/1.1

1997.01 HTTP/1.1 版本发布,只比 1.0 版本晚了半年。它进一步完善了 HTTP 协议,一直用到了 20 年后的今天,直到现在还是最流行的版本。

持久连接

1.1 版的最大变化,就是引入了持久连接 (Persistent Connection),即 TCP 连接默认不关闭,可以被多个请求复用,不用声明 Connection: keep-alive

客户端和服务器发现对方一段时间没有活动,就可以主动关闭连接。不过,规范的做法是,客户端在最后一个请求时,发送 Connection: close 明确要求服务器关闭 TCP 连接。

目前,对于同一个域名,大多数浏览器允许同时建立 6 个持久连接。

管道机制

1.1 版还引入了管道机制 (Pipelining),即在同一个 TCP 连接里面,客户端可以同时发送多个请求,这样就进一步改进了 HTTP 协议的效率。

举例来说,客户端需要请求两个资源。

以前的做法是,在同一个 TCP 连接里面,先发送 A 请求,然后等待服务器做出回应,收到后再发出 B 请求。管道机制则是允许浏览器同时发出 A 请求和 B 请求,但是服务器还是按照顺序,先回应 A 请求,完成后再回应 B 请求。

Content-Length 字段

一个 TCP 连接现在可以传送多个回应,势必就要有一种机制,区分数据包是属于哪一个回应的。这就是 Content-length 字段的作用,声明本次回应的数据长度。

Content-Length: 3495

上面代码告诉浏览器,本次回应的长度是 3495 个字节,后面的字节就属于下一个回应了。

在 1.0 版中,Content-Length 字段不是必需的,因为浏览器发现服务器关闭了 TCP 连接,就表明收到的数据包已经全了。

分块传输编码

使用 Content-Length 字段的前提条件是,服务器发送回应之前,必须知道回应的数据长度。

对于一些很耗时的动态操作来说,这意味着,服务器要等到所有操作完成,才能发送数据,显然这样的效率不高。更好的处理方法是,产生一块数据,就发送一块,采用"流模式" (stream) 取代"缓存模式" (buffer)。

参考

The Original HTTP as defined in 1991