透视HTTP协议-HTTP的传输、连接、重定向及Cookie机制

微信扫一扫,分享到朋友圈

透视HTTP协议-HTTP的传输、连接、重定向及Cookie机制

通常浏览器在发送请求时都会带着“Accept-Encoding”头字段,里面是浏览器支持的压缩格式列表,例如 gzip、deflate、br 等,这样服务器就可以从中选择一种压缩算法,放进“Content-Encoding”响应头里,再把原数据压缩后发给浏览器。

二、分块传输

将传输的文件分解成多个小块分批发给浏览器,浏览器收到后再组装复原。

HTTP协议里就是“chunked”分块传输编码,在响应报文里用头字段“Transfer-Encoding:chunked”来表示报文里的body部分不是一次性发过来的,而是分成了许多的块(chunk)逐个发送。

注:“Transfer-Encoding:chunked”和“Content-Length”这两个字段是互斥的,一个响应报文的传输长度要么已知,要么长度未知(chunked)。

下面我们来看下分块传输的编码规则,其实也很简单,同样采用了明文的方式,很类似响应头。

  1. 每个分块包含两个部分,长度头和数据块;
  2. 长度头是以CRLF(回车换行,即rn)结尾的一行明文,用16进制数字表示长度;
  3. 数据块紧跟在长度头后,最后也用CRLF结尾,但数据不包含CRLF;
  4. 最后用一个长度为0的块表示结束,即“0rnrn”。

如下图:

三、范围请求

有的时候,不需要全部的数据只需求其中的部分请求时,可使用范围请求。允许客户端在请求头里使用专用字段来表示只获取文件的一部分。

范围请求不是Web服务器必备的功能,所以服务器必须在响应头里使用字段“Accept-Range:bytes”明确告知客户端:我是支持范围请求的。不支持的话就发送“Accept-Range:none”或者不发送Accept-Range字段。

请求头Range是HTTP范围请求的专用字段,格式是“bytes=x-y”,其中x,y是以字节为单位的数据范围。x,y表示的是“偏移量”,范围必须从0计数。例:前10个字节是0-9

Range的格式很灵活,起点x和终点y可以省略:

  • “0-”表示从文档起点到文档终点,即整个文件;
  • “10-”从第10个字节开始到文档末尾
  • “-1”是文档的最后一个字节
  • “-10”是从文档末尾倒数10个字节

服务器收到Range字段后,需要做四件事:

  1. 它必须检查范围是否合法,比如文件只有100个字节,但请求“200-300”,这就是范围越界了。服务器就会返回状态码416,意思是“你请求的范围有误,我无法处理,请再检查一下”。
  2. 如果范围正确,服务器就可以根据Range头计算偏移量,读取文件的片段了,返回状态码“206 Partical Content”,跟200的意思差不多,但表示body只是原数据的一部分。
  3. 服务器要添加一个响应头字段Content-Range,告诉片段的实际偏移量和资源的总大小,格式是“bytes x-y/length”,与Range头区别在没有“=”,范围后多了总长度。例如,对于“0-10”的范围请求,值就是“bytes 0-10/100”。
  4. 最后就是发送数据了,直接把片段用TCP发给客户端,一个范围请求就算是处理完了。

四、多段数据

在Range头里使用多个“x-y”,一次性获取获取多个片段数据。

这种情况需要使用一种特殊的MIME类型:“multipart/byteranges”,表示报文的boby是由多段字节序列组成的,并且还要用一个参数“boundary=xxx”给出段之间的分隔标记。

多段数据的格式与分块传输也比较类似,但它需要用分隔标记boundary来区分不同的分段,可以通过图来对比下:

每一个分段必须以“- -boundary”开始(前面加两个“-”),之后要用“Content-Type”和“Content-Range”标记这段数据的类型和所在范围,然后就像普通的响应头一样以回车换行结束,再加上分段数据,最后用一个“- -boundary- -”(前后各有两个“-”)表示所有的分段结束。

要注意这四种方法不是互斥的,而是可以混合起来使用,例如压缩后再分块传输,或者分段后再分块。

HTTP的连接管理

短连接

每次发送请求前需要先与服务器建立连接,收到响应报文后会立即关系连接。因为客户端与服务器的整个连接过程很短暂,不会与服务器保持长时间的连接状态,所以就被称为“短连接”(short-lived connections)

短连接的缺点相当严重,因为在 TCP 协议里,建立连接和关闭连接都是非常“昂贵”的操作。TCP 建立连接要有“三次握手”,发送 3 个数据包,需要 1 个 RTT;关闭连接是“四次挥手”,4 个数据包需要 2 个 RTT。而 HTTP 的一次简单“请求 – 响应”通常只需要 4 个包,如果不算服务器内部的处理时间,最多是 2 个 RTT。这么算下来,浪费的时间就是“3÷5=60%”,有三分之二的时间被浪费掉了,传输效率低得惊人。

长连接

长连接就是保持连接,用的就是“成本均摊”的思路,既然 TCP 的连接和关闭非常耗时间,那么就把这个时间成本由原来的一个“请求 – 应答”均摊到多个“请求 – 应答”上。

连接相关的头字段

HTTP/1.1中的连接都会默认启用长连接。也可以在请求头里明确地要求使用长连接机制,使用的字段是Connection,值是“keep-alive”。不过不管客户端是否显示要求长连接,如果服务器支持长连接,它总会在响应报文里放一个“Connection:keep-alive”字段,告诉客户端:“我是支持长连接的,接下来就用这个TCP一直收发数据吧”。

长连接的缺点是,如果TCP连接长时间不关闭,服务器必须在内存里保存它的状态,这就占用了服务器的资源。所以长连接也需要在恰当的时间关闭,不能永远保持与服务器的连接,这在客户端或者服务器都可以做到。

在客户端,可以在请求头里加上“Connection:close”字段,告诉服务器:“这次通信后就关闭连接”。服务器看到这个字段,就知道客户端要主动关闭连接,于是在响应报文里也加上这个字段,发送之后就调用Socket API关闭TCP连接。

服务器端通常不会主动关闭连接,但也可以使用一些策略。拿 Nginx 来举例,它有两种方式:

  1. 使用“keepalive_timeout”指令,设置长连接的超时时间,如果在一段时间内连接上没有任何数据收发就主动断开连接,避免空闲连接占用系统资源。
  2. 使用“keepalive_requests”指令,设置长连接上可发送的最大请求次数。比如设置成 1000,那么当 Nginx 在这个连接上处理了 1000 个请求后,也会主动断开连接。

另外,客户端和服务器都可以在报文里附加通用头字段“Keep-Alive: timeout=value”,限定长连接的超时时间。但这个字段的约束力并不强,通信的双方可能并不会遵守,所以不太常见。

队头阻塞

“队头阻塞”(Head-of-line blocking)与短连接和长连接无关,而是因为HTTP规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。如果队首的请求因为处理的太慢耽误了时间,那么后面的请求也不得不跟着一起等待。

性能优化

因为“请求-应答”模型不能变,所以“队头阻塞”问题在HTTP/1.1里无法解决,只能缓解。缓解的方法就是“并发连接”,也就是同时对一个域名发起多个长连接,用数据来解决质量的问题。但是,HTTP协议建议客户端使用并发,但不能“滥用”并发,所以对并发数有限制(6~8)。

域名分片(domain sharing):由于HTTP协议和浏览器限制并发连接数量,那我就多开几个域名,都指向同一台服务器,这样实际连接的数量就又上去了。

HTTP的重定向和跳转

主动跳转:浏览器的使用者主动发起的; 被动跳转:由服务器来发起的,浏览器使用者无法控制,这在HTTP协议里有个专门的名词,叫做“重定向”(Redirection)。

重定向的过程

如下图实验,用Chrome访问URI“/18-1”,它会使用302立即跳转到“/index.html”

可以看出,这一次“重定向”实际上发送了两次HTTP请求,第一个请求返回了302,然后第二个请求就被重定向到了“/index.html”。再来看看第一个请求返回的响应报文:

这里出现了一个新的头字段“Location:/index.html”。”Location“字段属于响应字段,必须出现在响应报文里。但只有配合301/302状态码才有意义,它标记了服务器要求重定向的URI,这里就是要求浏览器跳转到”index.html“。

注意:在重定向时如果只是在站内跳转,你可以放心地使用相对URI。但如果要跳转到站外,就必须使用绝对URI。

另外,使用重定向时需要当心性能损耗,还要避免出现循环跳转(即A->B->A这种)

HTTP的Cookie机制

HTTP的Cookie机制,相当于是服务器给每个客户端都贴上一张小纸条,上面写了一些只有服务器才能理解的数据,需要的时候客户端把这些信息发给服务器,服务器看到Cookie,就能够认出对方是谁了。

Cookie的工作过程

  1. 当用户通过浏览器第一次访问服务器的时候,服务器肯定是不知道他的身份的。所以,就要创建一个独特的身份标识数据,格式是”key=value“,然后放进Set-Cookie字段里,随着响应报文一同发给浏览器。(服务器有时会在响应头里添加多个Set-Cookie,存储多个”key=value“。但浏览器这边发送时不需要用多个Cookie字段,只要在一行里用”;“隔开就行)
  2. 浏览器收到响应报文,看到里面有Set-Cookie,知道这是服务器给的身份标识,于是就保存起来,下次再请求的时候就自动把这个值放进Cookie字段里发给服务器。
  3. 因为第二次请求里面有了Cookie字段,服务器就知道这个用户不是新人,之前来过,就可以拿出Cookie里的值,识别出用户的身份,然后提供个性化的服务。 Cookie是”浏览器绑定“的,只能在本浏览器内生效。

Cookie的属性

Cookie 就是服务器委托浏览器存储在客户端里的一些数据,而这些数据通常都会记录用户的关键识别信息。所以,就需要在“key=value”外再用一些手段来保护,防止外泄或窃取,这些手段就是 Cookie 的属性。

首先,我们应该设置Cookie的生存周期,也就是它的有效期,可以使用Expires和Max-Age两个属性来设置。

  • ”Expires“俗称”过期时间“,用的是绝对时间,可以理解为deadline
  • ”Max-Age“用的是相对时间,单位是秒,浏览器用收到报文的时间点再加上Max-Age,就可以得到失效的绝对时间。 Expires 和 Max-Age 可以同时出现,两者的失效时间可以一致,也可以不一致,但浏览器会优先采用 Max-Age 计算失效期。

其次,我们需要设置Cookie的作用域,让浏览器仅发送给特定的服务器和URI。“Domain”和“Path”指定了 Cookie 所属的域名和路径,浏览器在发送 Cookie 前会从 URI 中提取出 host 和 path 部分,对比 Cookie 的属性。如果不满足条件,就不会在请求头里发送 Cookie。

最后考虑的就是Cookie的安全性了,尽量不要让服务器外的人看到。

  • ”HttpOnly“:此Cookie只能通过浏览器HTTP协议传输,禁止其他方式访问,浏览器的 JS 引擎就会禁用 document.cookie 等一切相关的 API,脚本攻击也就无从谈起了。
  • ”SameSite“:可以防范“跨站请求伪造”(XSRF)攻击,设置成“SameSite=Strict”可以严格限定 Cookie 不能随着跳转链接跨站发送,而“SameSite=Lax”则略宽松一点,允许 GET/HEAD 等安全方法,但禁止 POST 跨站发送。
  • ”Secure”:表示这个Cookie仅能用HTTPS协议加密传输,明文的HTTP协议会禁止发送。但Cookie本身不是加密的,浏览器里还是以明文的形式存在。

Cookie的应用

  1. 最基本的一个用途就是身份识别,保存用户的登录信息,实现会话事务。
  2. 另一个常见用途是广告跟踪。你上网的时候肯定看过很多的广告图片,这些图片背后都是广告商网站(例如 Google),它会“偷偷地”给你贴上 Cookie 小纸条,这样你上其他的网站,别的广告就能用 Cookie 读出你的身份,然后做行为分析,再推给你广告。

Q&A

Q:分块传输数据的时候,如果数据里含有回车换行(rn)是否会影响分块的处理呢?

Q:如果对一个被 gzip 的文件执行范围请求,比如“Range: bytes=10-19”,那么这个范围是应用于原文件还是压缩后的文件呢?

Q:在开发基于 HTTP 协议的客户端时应该如何选择使用的连接模式呢?短连接还是长连接?

Q:应当如何降低长连接对服务器的负面影响呢?

Q:301 和 302 非常相似,试着用自己的理解再描述一下两者的异同点。

Q:你能结合自己的实际情况,再列出几个应当使用重定向的场景吗?

Q:如果 Cookie 的 Max-Age 属性设置为 0,会有什么效果呢?

Q:Cookie 的好处已经很清楚了,你觉得它有什么缺点呢?

微信扫一扫,分享到朋友圈

透视HTTP协议-HTTP的传输、连接、重定向及Cookie机制

超声波触觉系统HaptiRead:让盲文能在空中得到精确投放

上一篇

Java SPI机制的理解与应用

下一篇

你也可能喜欢

透视HTTP协议-HTTP的传输、连接、重定向及Cookie机制

长按储存图像,分享给朋友