HTTP 协议

#web

协议版本

HTTP/0.9       # 1991原型版本,请求起始行中没有版本号
HTTP/1.0       # 广泛使用
HTTP/1.0+      # 非正式扩展版
HTTP/1.1       # 当前版本,校正老版的缺陷
HTTP-NG        # 也叫 HTTP/2.0,研究中,未推广

URL 语法

# 方案-服务器位置-资源路径
<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>

params:key=value 格式,多个用 ; 分隔。
query:key=value 格式,多个用 & 分隔。
匿名用户 anonymous 的密码为 IEUser (IE) 或者 mozilla (Netscape Navigator)。

HTTP 报文结构

报文结构:起始行 (start line)、首部 (header,以空行结束)、主体 (body,可选)。

请求行:请求报文的起始行。格式:方法 URL HTTP版本
响应行:响应报文的起始行。格式:HTTP版本 状态码 状态文本

HTTP 方法

GET           # 获取文档
HEAD          # 只返回首部。不返回实体的主体部分
PUT           # 保存文档
POST          # 发送表单
TRACE         # 用于查看代理等中间层如何修改报文
OPTIONS       # 询问服务器支持哪些方法。返回 Allow 首部字段
DELETE        # 删除文档
PATCH         # 更新部分资源

状态码

100-199     # 信息
200-299     # 成功
300-399     # 重定向
400-499     # 客户端错误
500-599     # 服务器错误
--------
404 Not Found
304 Not Modified
200 OK
401 需要认证
302 redirect 重定向

持久连接

在 HTTP/1.0 中使用 keep-alive 来请求打开持久连接,在 HTTP/1.1 中则默认打开。

Keep-Alive 原理:当请求首部包含 Connection: Keep-Alive 时,如果服务器支持,也会返回同样的首部,并告知将在多少个事务或时间后关闭连接 Keep-Alive: max=5, timeout=120;如果服务器不支持,就会在响应后关闭连接。

关闭持久连接:在请求中包含 Connection: close 首部。

代理

代理的作用:过滤、访问控制、防火墙、缓存、反向代理、内容路由、转码&压缩、负载均衡等。

代理配置:手动配置、PAC、WPAD。

PAC (Proxy Auto-Configuration):代理自动配置。提供 URI,指向 JavaScript 语言编写的 PAC 文件,通过返回值决定使用的代理。

WPAD (Web Proxy Autodiscovery Protocol):Web 代理自动发现协议。自动检测到配置服务器中的 PAC 文件并下载。

追踪报文经过的代理

方法一:报文每经过一个节点,都会在 Via 首部的末尾追加该节点信息,包括:可选的协议名、协议版本、节点名、可选的注释等等。

方法二:使用 Trace 方法,该方法也是利用 Via 首部,目标服务器会将 Trace 信息封装在响应主体中发送给客户端。可通过 Max-Forwards 来控制该查询报文允许经过的最大节点数。

查询服务器可支持的方法

请求行是 OPTIONS * HTTP/1.1 时,响应首部包含类似 Allow: GET, POST, PUT 的行。

* 表示查询整个服务器所支持的功能,也可用 URL 查询某个特定资源。

浏览器的工作流程

  1. 解析主机名
  2. 查询主机 IP 地址 (DNS)
  3. 获得端口号
  4. 向主机的端口发起连接
  5. 发送 GET 请求报文
  6. 读取响应报文
  7. 关闭连接

判断响应是否来自缓存

即区分命中和未命中的情况,但 HTTP 未提供直接手段来区分。

可将 Date 首部(报文创建时间)中的值跟当前时间做比较,如果较早,则来自缓存;或者利用 Age 首部(缓存代理告知客户端,源服务器在多少秒之前创建了该响应),如果来自缓存,Date + Age = 当前时间

缓存工作流程

  1. 接收请求
  2. 查询副本(没有就获取一份)
  3. 新鲜度检测(不新鲜就更新)
  4. 发送响应
  5. 记录日志

缓存会在响应首部中增加的信息

过期日期 (HTTP/1.0+) 或者过期秒数 (HTTP/1.1)。

Expires: 日期                   # 过期日期 (C/S 时间可能不同步)
Cache-Control: max-age=xxx     # 过期秒数

服务器再验证 (Revalidation)

缓存询问原始服务器文档是否发生了变化,可基于时间或版本进行比较。

If-Modified-Since 询问某个时间之后是否有更新。如果没有修改,响应 304 Not Modified,否则响应 200 OK。首部 Expires 表示过期时间。

If-None-Match: "v2.6" 询问某个版本 (ETag) 之后是否有更新。响应同上,且首部包含新 ETag 值和 Expires 时间。

强、弱缓存验证

指验证的严格程度,由客户端指定。弱验证的写法类似:W/"2.6"

强验证表示只要修改了文件(即修改时间变了)就算已更新,弱验证表示内容变化不大时(虽然修改时间变了,但 ETag 没变)不算已更新。

控制缓存的手段

即是否允许代理或客户端缓存内容,以及缓存过期的时间,服务器端的可用手段有:

  • 禁止缓存:Cache-Control: no-store
  • 在与原始服务器进行新鲜度再验证之前,缓存不能提供给客户端使用:HTTP/1.1 用 Cache-Control: no-cache,兼容 HTTP/1.0+ 用 Pragma: no-cache
  • 过期秒数:Cache-Control: max-age
  • 过期日期:Expires: 日期
  • 试探性过期:如果响应中没有指定过期日期或秒数,则缓存可根据任意算法自己计算过期时间。

服务器只能提供以上信息给客户端或代理,无法强制执行。

通过 HTML 标签控制服务端响应首部

如果 HTML 的 head 中包含如下内容,则服务端会在响应首部中插入 xxx: yyy

<head>
  <META HTTP-EQUIV="xxx" CONTENT="yyy" />
</head>

但是并不可靠,最可靠的方法还是配置 WebServer。

网关、隧道、中继

网关:抽象出了一种能到达资源的方法。

隧道:通过 HTTP 连接发送非 HTTP 流量,可穿过只允许 Web 流量通过的防火墙。

中继:没有完全遵循 HTTP 规范的简单 HTTP 代理。负责处理 HTTP 中建立连接的部分,然后对字节进行盲转发。

建立隧道

请求行是 Connect HOST:443 HTTP/1.0 发送给网关,网关建立到原始服务器的 SSL 连接后响应 200 Connection established,然后可发送任意数据直到连接关闭。

拒绝机器人访问标准

robots.txt 文件放到 WebServer 的文档根目录下(语法参考:baidu.com/robots.txt)。

机器人排斥标签

robots.txt 是站点级别的管理,而机器人排斥标签则可以让网页作者插入,然后机器人根据标签内容对文档内容进行不同的处理,比如:忽略文档、忽略页面中的外链、不要缓存等等。

<head><meta name="robots" content="noindex,nofollow"></head>

用户信息相关的 HTTP 首部

  • From:Email 地址
  • User-Agent:浏览器类型
  • Referer:链入页面
  • Authorization:登陆信息
  • Client-IP、X-Forwarded-For:代理增加的原始 IP 首部
  • Cookie

会话 Cookie 没有过期时间 (Expires, max-age),在浏览器关闭后即删除。

持久 Cookie 会保存在硬盘上。

服务器响应首部包含 Set-cookie: key=value;...,客户端再次访问时添加该信息 Cookie: key=value;...,服务器即可识别用户身份。

域属性:在 Set-cookie 中增加 domain="xxx.com",仅当访问 *.xxx.com 站点时,cookie 才会被发送出去。

路径属性:如果不同的路径要求使用不同的 cookie,比如:xxx.com/a/xxx.com/b/,可以在 Set-cookie 中再增加 path 信息,比如:domain="xxx.com"; path=/a/

Set-Cookie、Set-Cookie2

前者是网景的版本,后者是 RFC 的版本,属性更多。比如,指示客户端放弃某个 Cookie (Discard)、指定匹配端口 (Port)、服务器使用 Cookie 的目的和策略的描述 (Comment, CommentURL)。

HTTP 的认证协议

HTTP 只定义了两种认证协议:基本认证、摘要认证。

但基于 HTTP 的质询/响应 (challenge/response) 框架也可以设计新的协议。

基本认证

客户端访问页面后,服务端响应 401(需要认证),首部包含 WWW-Authenticate: Basic realm="Family"。其中 Basic 代表基本认证,realm 代表安全域。

客户端再次请求并在首部增加登陆信息 Authorization: Basic YfjkdsYLfdslfjoi。后面的乱码是 Base-64("用户名:密码") 转码后的字符串。

安全域:是受保护的资源的集合,每个安全域都有不同的授权用户集。客户端可根据安全域来确定应该输入哪个用户名和密码来访问该资源。

缺点:密码通过明文传输,很容易被拦截、破解。

摘要认证

与基本认证类似,将 Basic 替换为 Digest,然后服务端响应中会增加一个随机数 (nonce="xxx"),客户端需要利用随机数生成摘要 (一般用 MD5 算法),再发送给服务端。

预授权:为了避免用户不断重复输入用户名和密码,浏览器应该将它们缓存起来,下次自动发送。这对于基本认证不是问题,因为授权信息是不变的。但对于摘要认证,服务器每次发送的随机数不同,因此摘要也不同。解决办法就是服务器在响应时预先加入下一个随机数:Authentication-Info: nextnonce="xxx"

与报文内容相关的首部

  • 类型 (Content-Type):如果内容被编码,它表示编码前的类型。
  • 长度 (Content-Length):如果内容被编码,它表示编码后的长度。
  • 编码 (Content-Encoding)
  • 校验和 (Content-MD5)
  • 范围 (Content-Range)
  • 最后修改时间 (Last-Modified)
  • 过期日期 (Expires)
  • 版本 (ETag)
  • 缓存控制 (Cache-Control)

Content-Length 的作用

在持久连接中,通过它才知道报文在哪里结束,下一条从哪里开始。因为客户端无法再依赖连接关闭来判断报文的结束。

分块 (chunked) 编码时没有该首部,因为每块都有大小说明。

如果内容被编码,它表示编码后的长度。HTTP/1.1 规范中无法得知原始内容的长度。

表单提交原理

首先,表单的编码方式应该是 <form enctype="multipart/form-data">

请求报文首部包含 Content-Type: multipart/form-data; boundary="分隔字符串"。各个字段的数据被分隔字符串分隔成多个子块。

如果同时上传多个文件,则文件子块包含 Content-type: multipart/mixed; boundary="分隔字符串"。文件子块又被分隔字符串分隔成多个子块,每个子块包含一个 MIME 文件类型和数据。

Accept-Encoding 中的参数 Q

参数 Q 为质量因子,表示愿意接受该编码类型的程度,取值范围是 0.0~1.0(0.0 表示最不想,1.0 表示最想)。

传输编码

为了改变报文的传输方式。HTTP 规范只定义了一种传输编码,即分块编码。

分块编码:

​ 如果愿意接受分块编码需要在请求中包含:TE: chunked,响应首部将包含: Transfer-Encoding: chunked

​ 响应的主体包含多个块,每块的格式都是:[块大小、空行、块内容、空行],最后一块是:[0、空行]

为什么需要分块传输?

​ 使用持久连接时,服务器写主体前必须知道它的大小,但是对于服务器动态创建的内容,就无法提前知道,分块传输允许逐块发送,只要知道当前每块的大小即可。

范围请求

只请求文档的一部分或多个部分。

支持范围请求的服务端在响应中会包含:Accept-Ranges: bytes

如果请求中包含 Range: bytes=4000-,表示读取从偏移 4000 字节至文件尾的内容。

如果请求了多个范围,响应首部包含 Content-Type: multipart/x-byteranges; boundary="分隔字符串",各分隔块中包含范围 (Content-Range: bytes 522-761/1440) 和数据。

差异编码

只接受 diff 部分,用指定的 ETag 版本跟当前最新版本做对比。

# 请求头
If-None-Match: v2.0
A-IM: diffe         # A-IM (接受实例操控)

# 响应头
226 IM Used
IM: diffe           # diffe 是一种 diff 算法
Delta-base: v2.0    # 表示基于哪个版本做的 diff

内容协商

根据请求,服务器可给出不同的内容。

请求可包括:文件类型 Accept、语言 Accept-Language、字符集 Accept-Charset、编码 Accept-Encoding

Apache 对内容协商的支持

使用 type-map 文件:通过该映射文件指定文档对应的字符集和类型。

使用 MultiView:自动根据文件名中的语言类型进行猜测后生成 type-map 文件,比如 a.en.html、a.fr.de.html。

透明协商

让代理代表服务器进行协商,代理可缓存各种请求对应的内容并直接发送给客户端,减少服务器负载。但如果服务器不是依据 Accept 首部集进行协商,就需要告知代理它的决策依据,可使用 Vary 首部来描述。

Vary 首部

如果服务器不想依赖 Accept 首部集进行内容协商,可以用 Vary 首部来描述它是根据哪个首部来作判断的。比如根据 User-agent 来判断是否老版本浏览器(即是否支持 JavaScript)可用 Vary: User-agent

如何区分不同的虚机主机

可以通过路径、IP、端口、Host 首部。

HTTP/1.0 中没有 Host 首部,在 HTTP/1.0+ 才引入,而在 HTTP/1.1 中则必须支持。

当用户访问 www.xxx.com/index.html 时,浏览器会将它解析为请求行 GET /index.html HTTP/1.1 加首部 Host: www.xxx.com

FrontPage 与 WebDAV

FrontPage 是微软提供的 Web 写作和发布工具,C/S 之间的发布协议是在 HTTP 的 POST 请求之上实现的 RPC 层,可远程更新文档、搜索、多人协作。

WebDAV 是对 HTTP 的扩展,提供协作写作支持。现在很多平台都支持。

FrontPage 和 WebDAV 没有关系,两者作用相同,前者是工具,后者是这类工具的 C/S 端通信所需要的开源的协议。

重定向到服务器的方法

  • HTTP 重定向:响应 302 redirect。
  • DNS 重定向:一般在多个地址之间轮转,或者在自己的 DNS 服务器上设计更复杂的算法。
  • 任播寻址:因为骨干网络使用最短路径,所以每台服务器都将 IP 设为一样,然后冒充路由器给骨干网路由器发广告,骨干网收到任播分组后会转给最近的“路由器”。因此客户端会访问到最近的服务器。
  • IP 分组 MAC 转发:修改所有分组的 MAC 地址,将服务器的流量全部转给交换机,再由第 4 层交换来实现负载均衡。因为是 MAC 转发,所以服务器离交换机只能有一跳的距离。
  • IP 分组地址转发:同上,因为只修改 IP 地址,所以交换机只要在服务器上游即可,没有距离一跳的限制。其原理就是NAT。
  • 网元控制协议:该协议允许网元(路由、交换等)与服务器元素(Web 服务器、代理缓存等)交互。服务器元素可以将负载均衡信息发给网元。

重定向到代理的方法

显式配置浏览器:指定通过某个代理来访问。

代理自动配置协议 (PAC):浏览器配置 PAC 服务器,从服务器上下载 PAC 文件(其实是一个 JavaScript 程序),重定向到其中指定的代理。

Web 代理自动发现协议 (WPAD):不用在浏览器中显式配置 PAC 服务器,通过 DHCP 或 DNS A 记录查询等方式查找 PAC 文件服务器地址。

重定向到缓存代理的方法

WCCP 重定向:Web 缓存协调协议由思科公司开发,可以让路由器和缓存服务器之间通信,这样路由器就可以对缓存进行验证、负载均衡和特定类型的流量转发。

缓存代理没有命中怎么办?

ICP 协议:因特网缓存协议。可以查找附近的兄弟缓存,如果有命中的,就复制过来,没有则往上层继续查,避免一开始就查询原始服务器而带来更多的开销。因此,ICP 就是一个缓存集群协议。

HTCP 协议:超文本缓存协议。原理同上,设计 ICP 时只考虑了 HTTP/0.9,只能通过 URL 查询,而 HTCP 允许使用 1.0和 1.1 中的新首部和 URL 来查询。

单台代理压力过大怎么办?

CARP 协议:缓存分组路由协议。可以将流量分散到一组服务器上。方法是将所有的访问对象散列到不同的节点上,没有冗余。缺点是,如果某一台崩溃,需要马上更新集群,而 ICP 则不需要。

日志需要记录的信息

  • HTTP 方法
  • 客户端和服务器的 HTTP 版本
  • 资源 URL
  • 响应的 HTTP 状态码
  • 请求和响应报文的尺寸
  • 事务开始时的时间戳
  • Referer 首部
  • User-Agent 首部

日志格式

  • 常用日志格式:由 NCSA 定义,包含 7 个字段。
  • 组合日志格式:在常用日志格式的基础上扩展 2 个字段。
  • 网景扩展日志格式:对常用日志格式的扩展。
  • 网景扩展 2 日志格式
  • Squid 代理日志格式:Squid 代理缓存这个古老工具的日志格式,现在很多工具都使用该格式。