Http协议之Content-Length-阿里云开发者社区

开发者社区> 科技小先锋> 正文

Http协议之Content-Length

简介:
+关注继续查看

前言

http协议是互联网中最重要的协议之一,虽然看上去很简单,但是实际中经常遇到问题,我们就已经遇到好几次了。有长连接相关的,有报文解析相关的。对http协议不能一知半解,必须透彻理解才行。所以就写了这个系列分享http协议的问题与经验。


问题

我们的手机App在做更新时会从服务器上下载的一些资源,一般都是一些小文件,更新的代码差不多是下面这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void update() throws IOException {
    URL url = new URL("http://172.16.59.129:8000/update/test.so");
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    if(conn.getResponseCode() == 200) {
        int totalLength = conn.getContentLength();
    BufferedInputStream in = new BufferedInputStream(conn.getInputStream());
    byte[] buffer = new byte[512];
    int readLength = 0;
    int length = 0;
    while((length=in.read(buffer)) != -1) {
        readLength += length;
        //进度条
        System.out.println(((float)readLength) /((float)(totalLength)));
    }
    }
}


比如上面的代码更新一个so文件,先通过content-length获取文件的总大小,然后读Stream,每读一段,就计算出当前读的总大小,除以content-length,用来显示进度条。


结果weblogic从10升级到12后,content-length一直返回-1,这样就不能显示进度条了,但是文件流还能正常读。把weblogic重启了,一开始还能返回content-length,一会又是-1了。



原因分析


Http协议的请求报文和回复报文都有header和body,body就是你要获取的资源,例如一个html页面,一个jpeg图片,而header是用来做某些约定的。例如客户端与服务端商定一些传输格式,客户端先获取头部,得知一些格式信息,然后才开始读取body。



客户端: Accept-Encoding:gzip (给我压缩一下,我用的是流量,先下载下来我再慢慢解压吧)

服务端1:Content-Encoding:null(没有Content-Encoding头。 我不给压缩,CPU没空,你爱要不要)

服务端2:Content-Encoding:gzip (给你节省流量,压缩一下)




客户端:Connection: keep-alive (大哥,咱好不容易建了个TCP连接,下次接着用)

服务端1: Connection: keep-alive (都不容易,接着用)

服务端2: Connection: close (谁跟你接着用,我们这个TCP是一次性的,下次再找我还得重新连)




http协议没有三次握手,一般客户端向服务端请求资源时,以服务端为准。还有一些header并没有协商的过程,而是服务端直接告诉客户端按什么来。例如上述的Content-Length,是服务端告诉客户端body的大小有多大。但是!服务端并不一定能准确的提前告诉你body有多大。服务端要先写header,再写body,如果要在header里把body大小写进去,就得提前知道body大小。如果这个body是动态生成的,服务端先生成完,再开始写header,这样需要很多额外的开销,所以header里不一定有content-length。


那客户端怎么知道body的大小呢?服务器有三种方式告诉你。


1. 服务器已经知道资源大小,通过content-length这个header告诉你。


Content-Length:1076(body的大小是1076B,你读取1076B就可以完成任务了)

Transfer-Encoding: null


2. 服务器没法提前知道资源的大小,或者不愿意花费资源提前计算资源大小,就会把http回复报文中加一个header叫Transfer-Encoding:chunked,就是分块传输的意思。每一块都使用固定的格式,前边是块的大小,后面是数据,然后最后一块大小是0。这样客户端解析的时候就需要注意去掉一些无用的字段。


Content-Length:null

Transfer-Encoding:chunked (接下来的body我要一块一块的传,每一块开始是这一块的大小,等我传到大小为0的块时,就没了)


3. 服务器不知道资源的大小,同时也不支持chunked的传输模式,那么就既没有content-length头,也没有transfer-encoding头,这种情况下必须使用短连接,以连接结束来标示数据传输结束,传输结束就能知道大小了。这时候服务器返回的header里Connection一定是close。


Content-Length:null

Transfer-Encoding:null

Connection:close(我不知道大小,我也用不了chunked,啥时候我关了tcp连接,就说明传输结束了)



实验


我通过nginx在虚拟机里做实验,默认nginx是支持chunked模式的,可以关掉。

使用的代码如下,可能会调整参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void update() throws IOException {
    URL url = new URL("http://172.16.59.129:8000/update/test.so");
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    //conn.setRequestProperty("Accept-Encoding", "gzip");
    //conn.setRequestProperty("Connection", "keep-alive");
    conn.connect();
    if(conn.getResponseCode() == 200) {
        System.out.println(conn.getHeaderFields().keySet());
        System.out.println(conn.getHeaderField("transfer-encoding"));
        System.out.println(conn.getHeaderField("Content-Length"));
        System.out.println(conn.getHeaderField("Content-Encoding"));
        System.out.println(conn.getHeaderField("Connection"));
    }
}


1. nginx在开启chunked_transfer_encoding的时候


(1) 在reqeust header里不使用gzip,也就是不加accept-encoding:gzip


test.so文件大小

结果

100B

能正常返回content-length,没有transfer-encoding头

69M

能正常返回content-length,没有transfer-encoding头

3072M

能正常返回content-length,没有transfer-encoding头



可以发现nginx不管资源多大,如果客户端不接受gzip的压缩格式,就不会使用chunked模式,而且跟是否使用短连接没关系。


(2)在request header里加入gzip,accepting-encoding:gzip


test.so文件大小

结果

100B

没有content-length,transfer-encoding=trunked

69M

没有content-length,transfer-encoding=trunked

3072M

没有content-length,transfer-encoding=trunked


可以看到nginx在开启chunked_transfer_encoding,并且客户端接受gzip的时候,会使用chunked模式,nginx开启gzip后不会计算资源的大小,直接用chunked模式。



2.nginx关闭chunked_transfer_encoding


(1) 在reqeust header里不使用gzip,也就是不加accept-encoding:gzip

test.so文件大小

结果

100B

能正常返回content-length,没有transfer-encoding头

69M

能正常返回content-length,没有transfer-encoding头

3072M

能正常返回content-length,没有transfer-encoding头


因为能很容易的知道文件大小,所以nginx还是能返回content-length。



(2)在request header里加入gzip,accepting-encoding:gzip

test.so文件大小

结果

100B

没有content-length和transfer-encoding头,不论客户端connection为keep-alive还是close,服务端返回的connection头都是close

69M

没有content-length和transfer-encoding头,不论客户端connection为keep-alive还是close,服务端返回的connection头都是close

3072M

没有content-length和transfer-encoding头,不论客户端connection为keep-alive还是close,服务端返回的connection头都是close


这就是上面说的第三种情况,不知道大小,也不支持trunked,那就必须使用短连接来标示结束。



问题解决方案


咨询了中间件组的同事,以前也遇到类似的问题,因为升级了Weblogic导致客户端解析XML出错,因为使用了chunked模式,中间有一些格式化的字符,而客户端解析的代码并没有考虑chunked模式的解析,导致解析出错。

因为我们客户端必须用content-length展示进度,因此不能用chunked模式,Weblogic可以把chunked模式关闭。用下面的方法:

1
2
3
4
5
6
7
8
9
#!java weblogic.WLST 
connect('username’,'password', 't3://localhost:7001')
edit()
startEdit()
cd("Servers/AdminServer/WebServer/AdminServer")
cmo.setChunkedTransferDisabled(true)
save()
activate()
exit()


改了之后,确实不返回chunked了,但是也没有content-length,因为Weblogic就是不提前获取文件大小,而是强制加了connection:close,也就是前边说的第三种,通过连接结束标识数据结束。由于生产上我们用了Apache,测试环境为了方便就直接用的Weblogic,所以只能在测试环境再加个Apache了。


总结

一个好的http客户端,必须充分实现协议,不然就可能出问题,浏览器对于服务端可能产生的各种情况都很好的做了处理,但是自己实现http协议的解析时一定得注意考虑多种情况。


本文转自nxlhero 51CTO博客,原文链接:http://blog.51cto.com/nxlhero/1868483,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
9492 0
chrome插件API协议(Extensions Context Menu API Proposal)
Extensions Context Menu API Proposal NOTE: This document is now out of date. The API evolved some while behind the experimental flag, and is becoming fully supported in chrome 6.
946 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13176 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
11504 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
21902 0
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
4009 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
6888 0
6967
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载