移动开发中,为了减小包体积,很多文件都会通过云端下发的方式服务用户。文件下载中,经常会把Content-Length作为下载进度的重要参数,但是不同的服务器对待文件请求的方式不一样,可能存在Content-Length为-1或不准确的问题,导致下载进度不准确,影响用户体验。
1.什么是Content-Length
在HTTP协议中,Content-Length用于描述HTTP消息实体的传输长度the transfer-length of the message-body。在HTTP协议中,消息实体长度和消息实体的传输长度是有区别,比如说gzip压缩下,消息实体长度是压缩前的长度,消息实体的传输长度是gzip压缩后的长度。
2.Content-Length为什么不靠谱
下面我们来分析几种Content-Length的几种异常情况:
2.1.gzip压缩问题
引用官方文档的描述:
By default this implementation of HttpURLConnection requests that servers use gzip compression. Since getContentLength() returns the number of bytes transmitted, you cannot use that method to predict how many bytes can be read from getInputStream(). Instead, read that stream until it is exhausted: when read() returns -1. Gzip compression can be disabled by setting the acceptable encodings in the request header。
在默认情况下HttpURLConnection 使用 gzip方式获取,文件 getContentLength() 这个方法,每次read完成后可以获得,当前已经传送了多少数据,而不能用这个方法获取需要传送多少字节的内容,当read() 返回 -1时,读取完成。
因此要取得正确的文件长度,要求http请求不要gzip压缩。
conn .setRequestProperty("Accept-Encoding", "identity")
2.2.请求头的问题
一般可能是请求头的问题 导致被服务器拒绝访问了
conn.setRequestProperty("User-Agent", " Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36")
2.3.服务器端无Content-Length
如果服务端没有设置Content-Length, 那么客户端获取Content-Length时就是-1
3.http协议之Content-Length
对于http的请求返回结果要进行内容的长度校验主要有两种方式,二者互斥使用:
1.客户端在http头(head)加Connection:keep-alive时,服务器的response是Transfer-Encoding:chunked的形式,通知页面数据是否接收完毕,例如长连接或者程序运行中可以动态的输出内容,例如一些运算比较复杂且需要用户及时的得到最新结果,那就采用chunked编码将内容分块输出。
2.除了如1所述之外的情况一般都是可以获取到Content-Length的。
在具体的HTTP交互中,客户端是如何获取消息长度的呢,主要基于以下几个规则:
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可有可无
4.如何正确下载文件
引用官方文档的话:
read that stream until it is exhausted: when read() returns -1
numRead = bis.read(buffer, offset,BUFFER_SIZE - offset);
if (numRead == -1) {
// 已经没有数据了,退出循环
if (offset > 0) {
// buffer未填充满,但已经没数据了,则写入文件
fileOutputStream.write(buffer, 0, offset);
}
break;
}
网友评论