摘要: 理解 HTTP 协议 …
- 原文:用了这么久 HTTP, 你是否了解 Content-Length 和 Transfer-Encoding ?
- 作者:朴瑞卿的博客
由 Content-Length 导致的问题引发的一系列思考:
前段时间开发 API 网关, 使用 postman 调试时出现了超时的情况, 经排查确定是请求数据被处理后Content-Length
与实际不一致导致的问题, 故有此文.
Content-Length
, HTTP 消息长度, 用 十进制数字 表示的 八位字节的数目 . 一般情况下, 很多工作都被框架完成, 我们很少去关注这部分内容, 但少数情况下发生了Content-Length
与实际消息长度不一致, 程序可能会发生比较奇怪的异常, 如:
- 无响应直到超时.
- 请求被截断, 而且下一个请求解析出现错乱.
Content-Length
是 HTTP 消息长度, 用 十进制数字 表示的 八位字节的数目 , 是 Headers 中常见的一个字段. Content-Length
应该是精确的, 否则就会导致异常 (特别地, HTTP1.0 中这个字段可有可无).
Content-Length
首部指示出报文中实体主体的字节大小. 这个大小是包含了所有内容编码的, 比如, 对文本文件进行了 gzip
压缩的话, Content-Length
首部指的就是压缩后的大小而不是原始大小.
Content-Length 是如何工作的
Content-Length
使用十进制的数字表示了消息的长度, 服务端 / 客户端通过它来得知后续要读取消息的长度.
如果这个长度不正确, 会发生如下情况:
Content-Length > 实际长度
如果 Content-Length 比实际的长度大, 服务端 / 客户端读取到消息结尾后, 会等待下一个字节, 自然会无响应直到超时.
同样地, 在响应消息中 Content-Length
超过实际长度也是一样的效果:
Content-Length < 实际长度
如果这个长度小于实际长度, 首次请求的消息会被截取, 比如参数为 param=piaoruiqing
, Content-Length
为 10, 那么这次请求的消息会被截取为: param=piao
, 如图所示:
但, 仅仅是如此吗, 当然不, 我们再来看看第二次请求会发生什么让人意外的事情, 如图:
连续的两次请求, 第一次消息被截断, 而第二次没有发生预期的截断, 而是服务端抛出了异常: Request method 'ruiqingPOST' not supported
. 刺不刺激 (ノ)゚Д゚()
那 ruiqingPOST
是个什么神仙方法??? 此时, 凭着多年开发 (DEBUG) 经验练就的敏感度, 我们大致可以猜出, 上一次请求被截取剩下的消息, 在这次请求出现了. 掏出 wireshark 来验证一下, 如图:
导致这种情况的原因就是开启了Connection:keep-alive
, 如果使用Connection:close
, 所产生的现象就是每一次的请求都被截断, 但不会产生解析混乱(如将上一次剩下的消息拼接到后续的请求消息中).
不确定 Content-Length 的值怎么办
Content-Length首部指示出报文中实体主体的字节大小. 但如在请求处理完成前无法获取消息长度, 我们就无法明确指定
Content-Length, 此时应该使用
Transfer-Encoding: chunked
什么是 Transfer-Encoding: chunked
数据以一系列分块的形式进行发送. Content-Length
首部在这种情况下不被发送. 在每一个分块的开头需要添加当前分块的长度, 以十六进制的形式表示,后面紧跟着 \r\n
, 之后是分块本身, 后面也是\r\n
. 终止块是一个常规的分块, 不同之处在于其长度为 0.
Transfer-Encoding: chunked 是如何工作的
接下来我们用一个下载文件的例子, 来探讨 Transfer-Encoding: chunked
是如何工作的. 服务端代码如下:
使用 postman 发起请求, wireshark 抓包查看, 如图:
在 wireshark 中可以很清晰地看到 chunked 的数据, 其结构大致是: 返回的消息被分为多个数据块, 每个数据块有两部分, 长度
+ 数据
, 这两部分都以 CRLF(即\r\n
) 结尾. 而终止块是一个特殊的数据块, 其长度为 0, 如图:
如此, 即完成了分块编码. 其主要应用于如下场景, 即要传输大量的数据, 但是在请求在没有被处理完之前响应的长度是无法获得的. 例如, 当需要用从数据库中查询获得的数据生成一个大的 HTML 表格、需要传输大量的图片等.
-
Content-Length
如果存在且生效, 必须是正确的, 否则会发生异常.(大于实际值会超时, 小于实际值会截断并可能导致后续的数据解析混乱) - 如果报文中包含
Transfer-Encoding: chunked
首部, 那么Content-Length
将被忽略.
参考
- https://developer.mozilla.org
- 《HTTP 权威指南》
版权声明
本文发布于朴瑞卿的博客, 允许非商业用途转载, 但转载必须保留原作者朴瑞卿 及链接:https://blog.piaoruiqing.com. 如有授权方面的协商或合作, 请联系邮箱: piaoruiqing@gmail.com.
关于 Fundebug
Fundebug 专注于 JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js 和 Java 线上应用实时 BUG 监控。自从 2016 年双十一正式上线,Fundebug 累计处理了 20 亿 + 错误事件,付费客户有阳光保险、核桃编程、荔枝 FM、掌门 1 对 1、微脉、青团社等众多品牌企业。欢迎大家免费试用!