[HTTP] HTTP 协议 Response Header 之 Content-Length、Transfer-Encoding与Content-Encoding

news/2024/9/23 9:34:08/文章来源:https://www.cnblogs.com/johnnyzen/p/18331991

0 引言

  • 在近期项目一场景中,一 Web API (响应内容:7MB - 40MB、数据项:5W-20W条)的网络传输耗时较大,短则 5s,长则高达25s,前端渲染又需要耗时 9s-60s。
  • 在这个场景中,前端的问题暂且不表。那么针对后端的问题,个人认为还是有较大的优化空间:
    • 1)启用HTTP 内容压缩策略 【最重要】
    • 2)调整数据结构(参考JDBC,fields:list<string> + values:list<list<object>>,分2个独立的数据集,数据集内的object的属性值保证有序,减少了object内各字段名称的反复描述,以此降低网络带宽) 【次之】

  • 此处,主要探讨分析、实施HTTP 内容压缩策略中重点需要关注的 HTTP Response Header:
    • Content-Length :(如果启用压缩,压缩后)的内容长度
    • Transfer-Encoding : 传输编码
    • Content-Encoding : 内容编码

1 概述篇

1.1 Transfer-Encoding

  • Transfer-Encoding,是一个 HTTP 头部字段,字面意思是「传输编码」。
  • Transfer-Encoding 则是用来改变报文格式,它不但不会减少实体内容传输大小,甚至还会使传输变大,那它的作用是什么呢?本文接下来主要就是讲这个。我们先记住一点,Content-Encoding 和 Transfer-Encoding 二者是相辅相成的,对于一个 HTTP 报文,很可能同时进行了内容编码和传输编码。

1.2 Content-Encoding

  • 实际上,HTTP 协议中还有另外一个头部与编码有关:Content-Encoding(内容编码)
  • Content-Encoding 通常用于对实体内容进行压缩编码,目的是优化传输。例如,用 gzip 压缩文本文件,能大幅减小体积。
  • 内容编码通常是选择性的,例如 jpg / png 这类文件一般不开启,因为图片格式已经是高度压缩过的,再压一遍没什么效果不说还浪费 CPU。
Content-Encoding
Transfer-Encoding 值
描述
gzip (推荐) 表明实体采用 GNU zip 编码
compress 表明实体采用 Unix 的文件压缩程序
deflate 表明实体是用 zlib 的格式压缩的
br (推荐) 指示响应数据采用Brotli压缩编码。
示例:Transfer-Encoding: br / Content-Encoding: br
identity 表明没有对实体进行编码。当没有 Content-Encoding 首部时,就默认为这种情况

HTTP 定义了一些标准的内容编码类型,并允许用扩展编码的形式增添更多的编码。
由互联网号码分配机构(IANA)对各种编码进行标准化,它给每个内容编码算法分配了唯一的代号。
Content-Encoding 首部就用这些标准化的代号来说明编码时使用的算法。

gzip、compress 以及 deflate 编码都是无损压缩算法,用于减少传输报文的大小,不 会导致信息损失。
这些算法中,gzip 通常是效率最高的,使用最为广泛。

1.3 Accept-Encoding : 客户端声明可接受的编码

  • Accept-Encoding 字段包含用逗号分隔的支持编码的列表,下面是一些例子:
Accept-Encoding: compress, gzip
Accept-Encoding:
Accept-Encoding: *
Accept-Encoding: compress;q=0.5, gzip;q=1.0
Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0
  • 客户端可以给每种编码附带 Q(质量)值参数来说明编码的优先级。Q 值的范围从 0.0 到 1.0,0.0 说明客户端不想接受所说明的编码,1.0 则表明最希望使用的编码。

1.4 Persistent Connection(持久连接)

  • 暂时把 Transfer-Encoding 放一边,我们来看 HTTP 协议中另外一个重要概念:Persistent Connection持久连接,通俗说法长连接)。
  • 我们知道 HTTP 运行在 TCP 连接之上,自然也有着跟 TCP 一样的三次握手慢启动等特性,为了尽可能的提高 HTTP 性能,使用持久连接就显得尤为重要了。为此,HTTP 协议引入了相应的机制。
    • HTTP/1.0持久连接机制是后来才引入的,通过 Connection: keep-alive 这个头部来实现,服务端客户端都可以使用它告诉对方在发送完数据之后不需要断开 TCP 连接,以备后用
    • HTTP/1.1 则规定所有连接都必须是持久的,除非显式地在头部加上 Connection: close
      • 所以,实际上,HTTP/1.1Connection 这个头部字段已经没有 keep-alive 这个取值了,但由于历史原因,很多 Web Server 和浏览器,还是保留着给 HTTP/1.1 长连接发送 Connection: keep-alive 的习惯。

1.6 Content-Length : 告诉浏览器(编码后)响应实体的长度

没有 Content-Length 时 : pending

  • 浏览器重用已经打开的空闲持久连接,可以避开缓慢的三次握手,还可以避免遇上 TCP 慢启动的拥塞适应阶段,听起来十分美妙。为了深入研究持久连接的特性,我决定用 Node 写一个最简单的 Web Server 用于测试,Node 提供了 http 模块用于快速创建 HTTP Web Server,但我需要更多的控制,所以用 net 模块创建了一个 TCP Server:
require('net').createServer(function(sock) { sock.on('data', function(data) { sock.write('HTTP/1.1 200 OK\r\n'); sock.write('\r\n'); sock.write('hello world!'); sock.destroy(); }); }).listen(9090, '127.0.0.1');

启动服务后,在浏览器里访问 127.0.0.1:9090,正确输出了指定内容,一切正常。去掉 sock.destroy() 这一行,让它变成持久连接,重启服务后再访问一下。这次的结果就有点奇怪了:迟迟看不到输出,通过 Network 查看请求状态,一直是 pending

这是因为,对于非持久连接,浏览器可以通过连接是否关闭来界定请求或响应实体的边界;而对于持久连接,这种方法显然不奏效。上例中,尽管我已经发送完所有数据,但浏览器并不知道这一点,它无法得知这个打开的连接上是否还会有新数据进来,只能傻傻地等了。

引入 Content-Length 后 :如实给浏览器反馈响应内容实体的长度

要解决上面这个问题,最容易想到的办法就是计算内容实体长度,并通过头部告诉对方。这就要用到 Content-Length 了,改造一下上面的例子:

require('net').createServer(function(sock) { sock.on('data', function(data) { sock.write('HTTP/1.1 200 OK\r\n'); sock.write('Content-Length: 12\r\n');sock.write('\r\n'); sock.write('hello world!'); sock.destroy(); }); }).listen(9090, '127.0.0.1');

可以看到,这次发送完数据、并没有关闭 TCP 连接,但浏览器能正常输出内容、并结束请求,因为浏览器可以通过 Content-Length 的长度信息,判断出响应实体已结束。那如果 Content-Length 和实体实际长度不一致会怎样?有兴趣的同学可以自己试试,通常如果 Content-Length 比实际长度短,会造成内容被截断;如果比实体内容长,会造成 pending

由于 Content-Length 字段必须真实反映实体长度,但实际应用中,有些时候实体长度并没那么好获得,例如实体来自于网络文件,或者由动态程序生成。这时候要想准确获取长度,只能开一个足够大的 buffer,等内容全部生成好再计算。但这样做一方面需要更大的内存开销,另一方面也可能会让客户端等更久。

1.5 Transfer-Encoding: chunked(传输编码 = 分块传输) :不依赖头部的长度信息,也能知道实体的边界

TTFB (Time To First Byte)

  • 我们在做 WEB 性能优化时,有一个重要的指标叫 TTFBTime To First Byte),它代表的是从客户端发出请求到收到响应的第一个字节所花费的时间
  • 大部分浏览器自带的 Network 面板都可以看到这个指标(如 Chrome - Network - a request - Timing - Waiting for server response),越短的 TTFB 意味着用户可以越早看到页面内容,体验越好。可想而知,服务端为了计算响应实体长度而缓存所有内容,跟更短的 TTFB 理念背道而驰。
  • 但在 HTTP 报文中,实体一定要在头部之后,顺序不能颠倒,为此我们需要一个新的机制:不依赖头部的长度信息,也能知道实体的边界

Transfer-Encoding : 不依赖头部的长度信息,也能知道实体的边界

  • 本文主角终于再次出现了,Transfer-Encoding 正是用来解决上面这个问题的。历史上 Transfer-Encoding 可以有多种取值,为此还引入了一个名为 TE 的头部用来协商采用何种传输编码。但是最新的 HTTP 规范里,只定义了一种传输编码:分块编码(chunked)。

  • 分块编码相当简单,在头部加入 Transfer-Encoding: chunked 之后,就代表这个报文采用了分块编码。这时,报文中的响应实体需要改为用一系列分块来传输。每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的 CRLF(\r\n),也不包括分块数据结尾的 CRLF。最后一个分块长度值必须为 0,对应的分块数据没有内容,表示实体结束

  • 按照这个格式改造下之前的代码:

require('net').createServer(function(sock) {sock.on('data', function(data) {sock.write('HTTP/1.1 200 OK\r\n');sock.write('Transfer-Encoding: chunked\r\n');sock.write('\r\n');sock.write('b\r\n');sock.write('01234567890\r\n');sock.write('5\r\n');sock.write('12345\r\n');sock.write('0\r\n');sock.write('\r\n');});
}).listen(9090, '127.0.0.1');

上面这个例子中,我在响应头中表明接下来的实体会采用分块编码,然后输出了 11 字节的分块,接着又输出了 5 字节的分块,最后用一个 0 长度的分块表明数据已经传完了。用浏览器访问这个服务,可以得到正确结果。可以看到,通过这种简单的分块策略,很好的解决了前面提出的问题。

前面说过 Content-EncodingTransfer-Encoding 二者经常会结合来用,其实就是针对 Transfer-Encoding 的分块再进行 Content-Encoding

下面是用 telnet 请求测试页面得到的响应,就对分块内容进行了 gzip 编码:

> telnet 106.187.88.156 80GET /test.php HTTP/1.1
Host: qgy18.qgy18.com
Accept-Encoding: gzipHTTP/1.1 200 OK
Server: nginx
Date: Sun, 03 May 2015 17:25:23 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Content-Encoding: gzip1f
�H���W(�/�I�J0

用 HTTP 抓包神器 Fiddler 也可以看到类似结果,有兴趣的同学可以自己试一下。

Transfer-Encoding: chunked 与 Content-Length

  • Transfer-Encoding: chunkedContent-Length 同为头部字段,它们不会同时出现在头部中
  • 当使用分块传输时,头部将出现 Transfer-Encoding: chunked,而不再包含Content-Length字段,即使强行设定该字段,也会被忽略。

在HTTP中,我们通常依赖 HttpCode/HttpStatus 来判断一个 HTTP 请求是否成功,如:

HTTP: Status 200 – 成功,服务器成功返回网页
HTTP: Status 304 – 成功,网页未修改
HTTP: Status 404 – 失败,请求的网页不存在
HTTP: Status 503 – 失败,服务不可用

… …

延伸:开发人员的程序发起HTTP请求时,判断 HTTP 请求是否成功场景 (可选读章节)

但开发人员有时候也会有令人意外的想象力。我们的一部分开发人员决定使用 Content-Length 来判断 HTTP 请求是否成功,当 Content-Length 的值小于等于0或者为 162 时,认为请求失败。

Content-Length 的值小于等于0时认为http请求失败还好理解,因为开发人员错误地以为 HTTP 响应头中一定会包含 Content-Length 字段。

为什么当 Content-Length 的值为162时,也认为请求失败呢。这是因为公司服务器的404页面的长度恰好是162。惊不惊喜,意不意外!

2 Web Server 配置篇

  • 启用压缩等配置

Nginx

gzip on;
gzip_min_length 1k;gzip_buffers 4 16k;#gzip_http_version 1.0;gzip_comp_level 2;gzip_types text/xml text/plain text/css text/js application/javascript application/json;gzip_vary on;gzip_disable "MSIE [1-6]\.";
  • 当在 Nginx 的配置文件 nginx.conflocation等位置配置以上内容时,Nginx 服务器将对指定的文件类型开启压缩 (gzip)以优化传输,减少传输量。

  • 分块传输可以将压缩对象分为多个部分,在这种情况下,资源整个进行压缩,压缩的输出分块传输。在压缩的情形中,分块传输有利于一边进行压缩一边发送数据而不是先完成压缩过程,得知压缩后数据的大小之后再进行传输,从而使得用户能够更快地接受到数据,TTFB 指标更好。

  • 对于开启了 gzip 的传输,报文的头部将增加 Content-Encoding: gzip 来标记传输内容的编码方式。同时,Nginx 服务器默认就会对压缩内容进行分块传输,而无须显式开启chunked_transfer_encoding

  • Nginx 中如何关闭分块传输呢,在 Nginx 配置文件 location 段中加一行“chunked_transfer_encoding off;”即可。

location / {chunked_transfer_encoding       off;
}

SpringBoot(Embed Tomcat)

  • SpringBoot 默认是不开启 gzip 压缩的,需要我们手动开启,在配置文件中添加两行
server: compression: enabled: true mime-types: application/json,application/xml,text/html,text/plain,text/css,application/x-javascript
  • 注意:上面配置中的mime-types,在 spring2.0+的版本中,默认值如下,所以一般我们不需要特意添加这个配置
// org.springframework.boot.web.server.Compression#mimeTypes
/*** Comma-separated list of MIME types that should be compressed.*/
private String[] mimeTypes = new String[] { "text/html", "text/xml", "text/plain","text/css", "text/javascript", "application/javascript", "application/json","application/xml" };
  • 虽然加上了上面的配置,开启了 gzip 压缩,但是需要注意并不是说所有的接口都会使用 gzip 压缩,默认情况下,仅会压缩 2048 字节以上的内容
  • 如果我们需要修改这个值,通过修改配置即可
server:compression:min-response-size: 1024

X 参考文献

  • HTTP 协议中的 Transfer-Encoding - imququ.com 【推荐】
  • HTTP 协议中的 Transfer-Encoding - 博客园
  • 浏览器控制台Network面板介绍 - CSDN 【推荐】
    • Chrome-开发者工具-Network-(a request)-Size(响应头大小 + 响应数据)
  • HTTP Transfer-Encoding介绍 - CSDN
  • SpringBoot系列教程Web篇之开启GZIP数据压缩 - 博客园 【推荐】
  • Content-Encoding:内容编码 - CSDN
  • Content-Encoding:br 是一种什么编码格式? - 51CTO 【推荐】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/773976.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Zabbix 5.0 LTS 配置企业微信(Webhook)自动发送告警信息

依据前面文章《Zabbix 5.0 LTS URL 健康监测》环境,实现企业微信(Webhook)自动发送告警信息。 一、创建企业微信机器人先在自己的企业微信群里创建一个机器人,并获取其 WebHook 地址。右击群聊天卡片,添加群机器人。 获得一个类似下图的 WebHook 地址。注意,这个 WebHook 地…

【逆运动学2】damped least squares method阻尼最小二乘法

逆运动学逆运动学,就是从操作空间的end effector position and orientation,求关节空间的joint position的问题。在之前的文章,我们简单提到求逆运动学解的解析解法和优化解法,详细讲解了用逆瞬时(或说微分)运动学即雅可比矩阵法迭代求解逆运动学的方法。这篇文章我们继续…

CTFshow web入门vip 文件上传

CTFshow web入门vip 文件上传web151 题目提示前端校验不可靠,看源码可以看到是传到upload.php这个文件去接受文件上传,文件类型为图片,后缀限制为png 然后把前端验证修改一下,把文件后缀限制改成php 写个一句话木马传进去 1.php <?php eval($_POST[x]);?>url中需要…

我出一道面试题,看看你能拿 3k 还是 30k!

我会出一道经典的后端面试题,你只需要进行 4 个简单的选择,就能判断出来你的水平是新手(3k)、初级(10k)、中级(15k)还是高级(30k)!大家好,我是程序员鱼皮。欢迎屏幕前的各位来到今天的模拟面试现场,接下来我会出一道经典的后端面试题,你只需要进行 4 个简单的选择…

运维管理平台化:运维体系为什么要基于平台化建设

本文来自腾讯蓝鲸智云社区用户:CanWay摘要:笔者根据自身的技术和行业理解,分享嘉为蓝鲸数据与智能运维场景实践。 涉及关键字:一体化运维、平台化运维、数智化运维、AIOps、运维PaaS、运维工具系统、蓝鲸等。 本文作者:嘉为蓝鲸运维产品及解决方案负责人 张敏、嘉为蓝鲸平…

flask定制登入过程 显示中文登录信息

默认情况下,当未登录的用户尝试访问一个 login_required 装饰的视图,Flask-Login 会闪现一条消息并且重定向到登录视图。(如果未设置登录视图,它将会以 401 错误退出。) 登录视图的名称可以设置成 LoginManager.login_view。例如:login_manager.login_view = "users.lo…

BUUCTF GWCTF2019 re3

挺有意思的一题,记录一下 主要思路为smc解密 + AES算法 扔进ida后找到主函数:发现函数地址异或,推测为smc,先按d转化为数据,然后直接使用python脚本解密: from ida_bytes import *ea = 0x402219 for i in range(0,224):patch_bytes(ea + i, bytes(ea + i) ^ 0x99)选中按p…

DedeCMS图片水印设置

入口: 系统 > 系统设置 > 图片水印设置扫码添加技术【解决问题】专注中小企业网站建设、网站安全12年。熟悉各种CMS,精通PHP+MYSQL、HTML5、CSS3、Javascript等。承接:企业仿站、网站修改、网站改版、BUG修复、问题处理、二次开发、PSD转HTML、网站被黑、网站漏洞修复…

帝国CMS网站Fatal error: Call to undefined function sys_ShowTextPage…

Fatal error: Call to undefined function sys_ShowTextPage…解答:1、请确认你的程序是从官方下载2、提示这个错误是因为你的包少了内容,重新从官方下载e/class/t_functions.php到本地扫码添加技术【解决问题】专注中小企业网站建设、网站安全12年。熟悉各种CMS,精通…

帝国CMS网站刷新所有信息内容页面时提示“Table *.phome_ecms_news_data_ doesnt exist”

刷新所有信息内容页面时提示“Table *.phome_ecms_news_data_ doesnt exist”答:原因是数据本身有问题,信息索引表phome_ecms_表名_index有不存在ID的记录。解决办法:进数据库删除索引表中不存在的ID记录即可解决。扫码添加技术【解决问题】专注中小企业网站建设、网站安全1…

adobe acrobat DC如何彻底解决自动更新的问题

有客户反应已经按照adobe acrobat DC下载安装界面的教程删除了升级文件,但是还是会时不时的跳出更新,只能重新安装,有没有其他的更好的方法呢? 解决方法如下: 一、首先删除/Library/Application Support/Adobe/ARMDC/Application路径下的update文件,具体方法如下: 1.点击…

我用Awesome-Graphs看论文:解读PowerGraph

这次向大家分享发表在OSDI 2012上的一篇经典图计算框架论文PowerGraph,旨在通过点切分解决图数据幂律分布导致的计算倾斜问题,并提出了区别于Pregels VC(以点为中心)的GAS(以边为中心)计算框架。PowerGraph论文:《PowerGraph: Distributed Graph-Parallel Computation o…