在http请求头中要注意的事情:

1. 编码问题:

请求头Accept-Encoding对应的是响应头Content-Encoding

Accept-Encoding用于客户端告诉服务端能客服户能够理解的内容编码方式,——通常是某种*压缩算法——进行通知(给服务端)。通过内容协商的方式,服务端会选择一个客户端提议的方式,使用并在响应头 Content-Encoding 中通知客户端该选择。

即使客户端和服务器都支持相同的压缩算法,在 identity 指令可以被接受的情况下,服务器也可以选择对响应主体不进行压缩。导致这种情况出现的两种常见的情形是:

  • 要发送的数据已经经过压缩,再次进行压缩不会导致被传输的数据量更小。一些图像格式的文件会存在这种情况;
  • 服务器超载,无法承受压缩需求导致的计算开销。通常,如果服务器使用超过80%的计算能力,微软建议不要压缩。

只要 identity —— 表示不需要进行任何编码——没有被明确禁止使用(通过 identity;q=0 指令或是 *;q=0 而没有为 identity 明确指定权重值),则服务器禁止返回表示客户端错误的 406Not Acceptable 响应。

Content-Encoding 用于对特定媒体类型的数据进行压缩。当这个首部出现的时候,它的值表示消息主体进行了何种方式的内容编码转换。这个消息首部用来告知客户端应该怎样解码才能获取在 Content-Type 中标示的媒体类型内容。

一般建议对数据尽可能地进行压缩,因此才有了这个消息首部的出现。不过对于特定类型的文件来说,比如jpeg图片文件,已经是进行过压缩的了。有时候再次进行额外的压缩无助于负载体积的减小,反而有可能会使其增大。

Content-Encoding常有的值

Content-Encoding: gzip
Content-Encoding: compress
Content-Encoding: deflate
Content-Encoding: identity
Content-Encoding: br

gzip

表示采用 Lempel-Ziv coding (LZ77) 压缩算法,以及32位CRC校验的编码方式。这个编码方式最初由 UNIX 平台上的 gzip 程序采用。出于兼容性的考虑, HTTP/1.1 标准提议支持这种编码方式的服务器应该识别作为别名的 x-gzip 指令。

compress

采用 Lempel-Ziv-Welch (LZW) 压缩算法。这个名称来自UNIX系统的 compress 程序,该程序实现了前述算法。
与其同名程序已经在大部分UNIX发行版中消失一样,这种内容编码方式已经被大部分浏览器弃用,部分因为专利问题(这项专利在2003年到期)。

deflate

采用 zlib 结构 (在 RFC 1950 中规定),和 deflate 压缩算法(在 RFC 1951 中规定)。

identity

用于指代自身(例如:未经过压缩和修改)。除非特别指明,这个标记始终可以被接受。

br

表示采用 Brotli 算法的编码方式。

通常来讲,浏览器、网络请求工具(axios..)收到响应的http流后,会首先判断Content-Encoding的值,然后根据值做不同的解压方式,最终才解析出数据。

2. http响应头里没有Content-Length:

对于http的请求返回结果要进行内容的长度校验主要有两种方式,二者互斥使用

  1. 客户端在http头加Connection:keep-alive时,服务器的response是Transfer-Encoding:chunked的形式,通知页面数据是否接收完毕,例如长连接或者程序运行中可以动态的输出内容,例如一些运算比较复杂且需要用户及时的得到最新结果,那就采用chunked编码将内容分块输出。
  2. 除了如1所述之外的情况一般都是可以获取到Content-Length的。

在HTTP协议中,Content-Length用于描述HTTP消息实体的传输长度the transfer-length of the message-body。在HTTP协议中,消息实体长度和消息实体的传输长度是有区别,比如说gzip压缩下,消息实体长度是压缩前的长度,消息实体的传输长度是gzip压缩后的长度。

在具体的HTTP交互中,客户端是如何获取消息长度的呢,主要基于以下几个规则:

响应为1xx,204,304相应或者head请求,则直接忽视掉消息实体内容。

如果有Transfer-Encoding,则优先采用Transfer-Encoding里面的方法来找到对应的长度。比如说Chunked模式。

如果head中有Content-Length,那么这个Content-Length既表示实体长度,又表示传输长度。如果实体长度和传输长度不相等(比如说设置了Transfer-Encoding),那么则不能设置Content-Length。如果设置了Transfer-Encoding,那么Content-Length将被忽视。这句话有点饶,其实关键就一点:有了Transfer-Encoding,则不能有Content-Length。

Range传输:

通过服务器关闭连接能确定消息的传输长度。(请求端不能通过关闭连接来指明请求消息体的结束,因为这样可以让服务器没有机会继续给予响应)。这种情况主要对应为短连接,即非keep-alive模式。

HTTP1.1必须支持chunk模式。因为当不确定消息长度的时候,可以通过chunk机制来处理这种情况。

在包含消息内容的header中,如果有content-length字段,那么该字段对应的值必须完全和消息主题里面的长度匹配。

“The entity-length of a message is the length of the message-body before any transfer-codings have been applied”

也就是有chunk就不能有content-length 。

其实后面几条几乎可以忽视,简单总结后如下:

1、Content-Length如果存在并且有效的话,则必须和消息内容的传输长度完全一致。(经过测试,如果过短则会截断,过长则会导致超时。)

2、如果存在Transfer-Encoding(重点是chunked),则在header中不能有Content-Length,有也会被忽视。

3、如果采用短连接,则直接可以通过服务器关闭连接来确定消息的传输长度。(这个很容易懂)

结合HTTP协议其他的特点,比如说Http1.1之前的不支持keep alive。那么可以得出以下结论:

1、在Http 1.0及之前版本中,content-length字段可有可无。因为这之前都不支持长连接.

2、在http1.1及之后版本。如果是keep alive,则content-length和chunk必然是二选一。若是非keep alive,则和http1.0一样。content-length可有可无.

综合之上总结, 我们的问题: tinyproxy HTTP服务器返回是以HTTP/1.0返回的, 而且标识了

Connection: Close头. 而浏览器请求是以HTTP/1.1请求, 并标识了Connection: Keep-Alive请求的, 所以浏览器期望收到至少带有Transfer-Encoding(重点是chunked)或者Content-Length其中一种方式的头. 而我们的代理直接发送HTTP响应应该就有问题:

尝试解决办法:

  1. hmconfig解析HTTP, 收到的HTTP响应如果带有Connection: Close的数据发送完成后, 关闭对浏览器的连接.
  2. 设备端解析HTTP, 对没有content-length的HTTP响应补充头.

3.消息实体的传输长度与Content-Length的长度不一致时:

Content-Length, HTTP消息长度, 用十进制数字表示的八位字节的数目. 一般情况下, 很多工作都被框架完成, 我们很少去关注这部分内容, 但少数情况下发生了Content-Length与实际消息长度不一致, 程序可能会发生比较奇怪的异常, 如:

  • 无响应直到超时.
  • 请求被截断, 而且下一个请求解析出现错乱.

Content-Length是HTTP消息长度, 用十进制数字表示的八位字节的数目, 是Headers中常见的一个字段. Content-Length应该是精确的, 否则就会导致异常 (特别地, HTTP1.0中这个字段可有可无).

Content-Length首部指示出报文中实体主体的字节大小. 这个大小是包含了所有内容编码的, 比如, 对文本文件进行了gzip压缩的话, Content-Length首部指的就是压缩后的大小而不是原始大小.

Content-Length是如何工作的?

Content-Length使用十进制的数字表示了消息的长度, 服务端/客户端通过它来得知后续要读取消息的长度.

如果这个长度不正确, 会发生如下情况:

1. Content-Length > 实际长度

如果Content-Length比实际的长度大, 服务端/客户端读取到消息结尾后, 会等待下一个字节, 自然会无响应直到超时.

同样地, 在响应消息中Content-Length超过实际长度也是一样的效果:

2. Content-Length < 实际长度

如果这个长度小于实际长度, 首次请求的消息会被截取, 比如参数为param=piaoruiqing, Content-Length为10, 那么这次请求的消息会被截取为: param=piao

仅仅是如此吗, 当然不, 我们再来看看第二次请求会发生报错。

连续的两次请求, 第一次消息被截断, 而第二次没有发生预期的截断, 而是服务端抛出了异常: Request method 'ruiqingPOST' not supported

ruiqingPOST的出现说明 上一次请求被截取剩下的消息, 在这次请求出现了,所以会出现异常。

导致这种情况的原因就是开启了Connection:keep-alive, 如果使用Connection:close, 所产生的现象就是每一次的请求都被截断, 但不会产生解析混乱(如将上一次剩下的消息拼接到后续的请求消息中).

最后修改:2021 年 09 月 03 日 05 : 37 PM
如果觉得我的文章对你有用,请随意赞赏