毁三观到三观回正:简单的 echo 咋就这么慢呢?

发布于 2022-09-30 00:01:39 字数 6494 浏览 168 评论 0

毁三观的图:

三观回正的图:

用了这么久HTTP, 你是否了解Content-Length和Transfer-Encoding ?

由 Content-Length 导致的问题引发的一系列思考:

前段时间开发 API 网关, 使用postman调试时出现了超时的情况, 经排查确定是请求数据被处理后Content-Length与实际不一致导致的问题, 故有此文.

前言

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

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

什么是 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

不确定 Content-Length 的值怎么办

Content-Length 首部指示出报文中实体主体的字节大小,但如在请求处理完成前无法获取消息长度,我们就无法明确指定 Content-Length,此时应该使用 Transfer-Encoding: chunked。

什么是 Transfer-Encoding: chunked

数据以一系列分块的形式进行发送,Content-Length 首部在这种情况下不被发送,在每一个分块的开头需要添加当前分块的长度,以十六进制的形式表示,后面紧跟着 \r\n,之后是分块本身,后面也是 \r\n,终止块是一个常规的分块,不同之处在于其长度为 0。

结语

  1. Content-Length 如果存在且生效,必须是正确的,否则会发生异常(大于实际值会超时,小于实际值会截断并可能导致后续的数据解析混乱)
  2. 如果报文中包含 Transfer-Encoding: chunked 首部,那么 Content-Length 将被忽略。

----------------------------------------

要计算 Content-Length 响应头就必须缓冲所有响应体数据,因为是先发送 Content-Length 响应头,然后再发送任何的响应体数据。这是一个鸡和蛋的问题。所以对于大响应来说,开销很大。既然使用了 chunked 编码,就不应再设置 Content-Length 响应头了。HTTP 1.0 协议是不支持 chunked 编码的,所以在没有 Content-Length 响应头时以连接断开作为响应体的末尾。

尽量使用 HTTP 1.1;毕竟现在连 nginx 的 proxy 模块也支持 HTTP 1.1 了。

nginx开启gzip后无法输出content-length问题请教

------------------------

在 nginx 中,如果采用 gzip,如果是 keep alive,则必然是 chunked 模式。

Response与Transfer-Encoding:chunked、Content-Length、Content-Encoding:gzip

-----------------------------

-------------------------------------

    gzip on;
    gzip_disable "MSIE [1-6]\.";
    gzip_min_length 1k;
    #gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json;
    gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json 'text/plain; charset=UTF-8' 'text/css; charset=UTF-8' 'application/x-javascript; charset=UTF-8' 'text/xml; charset=UTF-8' 'application/xml; charset=UTF-8' 'application/xml+rss; charset=UTF-8' 'text/javascript; charset=UTF-8' 'application/json; charset=UTF-8' 'application/json;charset=UTF-8';

    chunked_transfer_encoding off;

见证代码逻辑:gzip 开启了 + chunked 关闭了 => keep alive 失效了:

nginx 中开启 keepalive 后应答反而为 close 的原因

wecom-temp-442191762a47d78d0dd910edeff6cde8

------------------

在 upstream 显示配置 keepalive_requests 也有利于性能提升

Dofault kespalswo  recue sts 19603

http://nginx.org/en/docs/http/ngx_http_upstream_module.html

----------------------------------

为什么打开 gzip、关闭 chunked 会关闭连接?

因为 Nginx 会采用即时压缩(On-The-Fly Compression),整个压缩过程在内存中流式完成。也就是说,Nginx 不会等文件 GZip 完成再返回响应,而是边压缩边响应,这样可以显著提高 TTFB(Time To First Byte,首字节时间,WEB 性能优化重要指标)。这样唯一的问题是,Nginx 开始返回响应时,它无法知道将要传输的文件最终有多大,也就是无法给出 Content-Length 这个响应头部。

对于 TCP 持久连接上的 HTTP 报文,客户端需要一种机制来准确判断结束位置,所以HTTP Server不启用持久连接(关闭连接的方式)。

从 Nginx 默认不压缩 HTTP/1.0 说起

所以合理的方式是打开 gzip、chunked。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

白日梦

暂无简介

文章
评论
26 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文