文档章节

Http 206 文件断点续传下载原理

IamOkay
 IamOkay
发布于 2015/01/25 08:26
字数 2091
阅读 12592
收藏 220
点赞 9
评论 13

引文

HTTP 304/200(from cache) 静态资源缓存原理

HTTP 204/205状态响应&HEAD请求

header标头说明 

 

断点续传下载需要重视2对头信息Accept-Ranges/Range与If-Range/tag

一.断点续传的原理

 其实断点续传的原理很简单,就是在http的请求上和一般的下载有所不同而已。

 打个比方,浏览器请求服务器上的一个文时,所发出的请求如下:

 假设服务器域名为www.ksTest.com,文件名为down.zip。

 

1.1、不使用断点续传的场景

get /down.zip http/1.1
accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-
excel, application/msword, application/vnd.ms-powerpoint, */*
accept-language: zh-cn
accept-encoding: gzip, deflate
user-agent: mozilla/4.0 (compatible; msie 5.01; windows nt 5.0)
connection: keep-alive

    服务器收到请求后,按要求寻找请求的文件,提取文件的信息,然后返回给浏览器,返回信息如下:

HTTP/1.1 200 Ok
content-length=106786028
accept-ranges=bytes
date=mon, 30 apr 2001 12:56:11 gmt
etag=w/"02ca57e173c11:95b"
content-type=application/octet-stream
server=microsoft-iis/5.0
last-modified=mon, 30 apr 2001 12:56:11 gmt

 

1.2、判断服务器是否支持断点续传

当请求资源时,如果服务器返回如下响应头

accept-ranges=bytes 

表明服务器支持bytes类型得数据断点续传。如果为空或者none,可能不支持断点续传

1.3、断点续传案例

所谓断点续传,也就是要从文件已经下载的地方开始继续下载。所以在客户端浏览器传给web服务器的时候要多加一条信息--从哪里开始。

下面是用自己编的一个“浏览器”来传递请求信息给web服务器,要求从2000070字节开始。

get /down.zip http/1.0
User-Agent: netfox
Range: bytes=2000070-
accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2

  仔细看一下就会发现多了一行

Range: bytes=2000070-

  这一行的意思就是告诉服务器down.zip这个文件从2000070字节开始传,前面的字节不用传了。

Range的完整格式是 

Range: bytes=startOffset-targetOffset/sum  [表示从startOffset读取,一直读取到targetOffset位置,读取总数为sum直接]

Range: bytes=startOffset-targetOffset  [字节总数也可以去掉]

服务器收到这个请求以后,返回的信息如下:

HTTP/1.1 206 Partial Content
content-length=106786028
content-range=bytes 2000070-106786027/106786028
date=mon, 30 apr 2001 12:55:20 gmt
etag=w/"02ca57e173c11:95b"
content-type=application/octet-stream
server=microsoft-iis/5.0
last-modified=mon, 30 apr 2001 12:55:20 gmt

 

 和前面服务器返回的信息比较一下,就会发现增加了一行:

Content-Range=bytes 2000070-106786027/106786028

返回的代码也改为206了,而不再是200了。

HTTP/1.1 206 Partial Content

以上信息不需要后台程序返回,而是服务器直接读取信息返回给client

 知道了以上原理,就可以进行断点续传的编程了。

Client端代码如下

try {
	URL url = new URL("http://img5.duitang.com/uploads/item/201203/16/20120316164401_tyAVV.thumb.700_0.jpeg");
	File targetFile = new File("test.jpeg"); 
	HttpURLConnection openConnection = (HttpURLConnection) url.openConnection();
	openConnection.setRequestMethod("POST");
	if(targetFile.exists())
	{
		openConnection.addRequestProperty("Range", "bytes="+targetFile.length()+"-");
	}else{
		openConnection.addRequestProperty("Range", "bytes=0-");
	}
	openConnection.connect();
	int responseCode = openConnection.getResponseCode();
	Map<String, List<String>> headerFields = openConnection.getHeaderFields();
	System.out.println(headerFields);
	if(responseCode==200 || responseCode==206)
	{
		InputStream is = openConnection.getInputStream();
		FileOutputStream fos = new FileOutputStream(targetFile);
				
		int len = -1;
		byte[] buf = new byte[1024];
		while((len=is.read(buf,0,1024))>0)
		{
					
			fos.write(buf, 0, len); 
			break;//为了便于测试,每次只读取一次
		}
				
		fos.close();
		is.close();
	}
			
		} catch (IOException e) {
			e.printStackTrace();
		}

 

二.使用代码控制断点续传

文件下载原理主要控制来自于服务器端响应,浏览器或者httpClient自行读取IO流

2.1、在PHP文件下载所需要的头信息

Accept-Ranges:bytes  #接受类型
Access-Control-Allow-Origin:* #允许任何主机均可跨域访问,ajax同样可以
Access-Control-Max-Age:2592000
Cache-Control:public, max-age=31536000
Connection:keep-alive
Content-Disposition:attachment; filename="c501b_01_h264_sd_960_540.mp4"
Content-Length:14470485
Content-Transfer-Encoding:binary #传输类型,字节类型
Content-Type:video/mp4  #响应类型
Date:Sun, 25 Jan 2015 00:17:14 GM  #文件日期--注意,对于浏览器读取缓存而不重新请求服务器十分有用,用来检测静态文件有没有被修改
ETag:"lraEcGPNv-73F2tLNOKhuA8a6pFa" #

下面是一个简单的PHP下载文件的示例

2.2、用代码控制断点续传

<?php
function smartReadFile( $filepath, $mimeType='application/octet-stream')
{
 
  date_default_timezone_set('GMT');  //注意时区必须是GMT,否则可能产生错误缓存

  $filepath=iconv("utf-8","gb2312",$filepath);
 if(!file_exists($filepath))
  { 
     header ("HTTP/1.0 404 Not Found");
    return;
  }
  $size=filesize($filepath);

  $time=date('D, j M Y H:i:s e',filemtime($filepath)); //转为格林尼治时间,同时注意php中文件时间写入的函数是  touch
   
  $fm=@fopen($filepath,'rb'); //测试能否打开文
  if(!$fm)
  { 
     header ("HTTP/1.0 505 Internal server error");
     return;
  }

  $stat = stat($filepath);
  $md5str = md5_file($filepath); //使用md5校验,更加精确
  $etag =  $md5str.'-'.sprintf('%x-%x-%x', $stat['ino'], $stat['size'], $stat['mtime'] * 1000000);
  
  if(isset($_SERVER['HTTP_IF_RANGE']) && (($_SERVER['HTTP_IF_RANGE'] == $etag) || (strtotime($_SERVER['HTTP_IF_RANGE']) >= $stat['mtime'])))
  {

       header('Etag: "' . $etag . '"');
       header('Last-Modified: ' . date('D, j M Y H:i:s e', $stat['mtime']));
       header('HTTP/1.0 304 Not Modified');
       return ;
  }    

   if(isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag)
    {
        header('Etag: "' . $etag . '"');
        header('HTTP/1.0 304 Not Modified');
        return ;
    } elseif(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $stat['mtime']) {
        header('Last-Modified: ' . date('D, j M Y H:i:s e', $stat['mtime']));
        header('HTTP/1.0 304 Not Modified');
        return;
    }
   
  $begin=0;
  $end=$size;
   
  if(isset($_SERVER['HTTP_RANGE']))
  { if(preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches))
    { $begin=intval($matches[0]);
      if(!empty($matches[1]))
        $end=intval($matches[1]);
    }
  }
   
  if($begin>0||$end<$size)
    header('HTTP/1.0 206 Partial Content');
  else
    header('HTTP/1.0 200 OK');  
   
  header("Content-Type: $mimeType"); //指定文件minetype,//注意,部分浏览器mineType需要明确指定(如image/png),否则不能下载
  header('Cache-Control: public, must-revalidate, max-age=0'); //控制client缓存,要求不缓存
  header('Pragma: no-cache');  
  header('Accept-Ranges: bytes'); //表示浏览器接受bytes的断点续传
  header('Content-Length:'.($end-$begin)); //如果未指定长度,这以chunked编码传输文件到客户端
  header("Content-Range: bytes $begin-$end/$size");
  header("Content-Disposition: attachment; filename=".basename($filepath)."");  //文件下载
  header('Content-Description: File Transfer');//非标准头信息,可以不要
  header("Content-Transfer-Encoding: binary\n"); //非标准头信息,可以不要
  header("Last-Modified: $time"); //用于校验
  header('Etag: "' . $etag . '"');
  header('Connection: close');  
   
  $cur=$begin;
  fseek($fm,$begin,0); //将指针定位到要读取的位置
 
  while(!feof($fm)&&$cur<$end&&(connection_status()==0))
  { 
    echo fread($fm,min(1024*16,$end-$cur));
    $cur+=1024*16;
  }
   
  fclose($fm);
  
}



$file = './test.png';
$exts = get_loaded_extensions();
$mimeType = 'application/octet-stream';
if(array_search('fileinfo', $exts)===FALSE)
{
  $sizeInfo = getimagesize($file);
  $mimeType = $sizeInfo['mime'];
}else{
  $mimeType = mime_content_type($file);
}

smartReadFile($file,$mimeType);
?>

三、服务器断点续传文件增强验证(If-Range,If-Match)

3.1、使用if-Range进行增强校验

部分服务器支持断点续传,但是前提是必须保证如下格式请求头才行,否则无法断点续传,只能是http 200正常下载

If-Range: "40e04a44a997d11:0" //第一次获取到的Etag的值
//If-Range: "Sat, 16 Apr 2016 06:29:02 GMT"//或者是Last-Modified的值
#对于IIS服务器
1.我们下载中断的时候一定要把得到的Last-Modified和Etag写入文件meta信息中,但是很多情况下ETag无法写入文件meta信息,因此,我们要确保last-Modifield被保存
2.注意,使用时间必须是格林尼治时间

3.2使用if-Match进行增强校验与Http 412问题

当然使用if-Match也是一种方式,但是,如果服务器端的资源被修改了,那么,http请求时http 412,因此,我们建议使用iF-Range,这样,即时文件被修改,也会以http 200返回全部资源。

If-Match: "40e04a44a997d11:0" //第一次获取到的Etag的值

 

3.3关于If-Range增强断点续传验证测试

不设置If-Range的时候

设置If-Range的时候

3.3、 使用If-Modified-Since & If-None-Match时304冲突

If-Modified-Since/Last-Modified 传递时间

If-None-Match/Etag 消息摘要,不会出现http 412问题

这里If-Modified-Since/Last-Modified的值值示服务器上的资源更新时间,服务器对待If-Modified-Since的优先级低于If-Range:Etag、If-Range:Last-ModifiedIf-None-Match:Etag

 

在断点续传时需要注意优先级是If-None-MatchIf-Range > If-Modified-Since所以如果资源需要断点续传,那么最好不要设置,否则有可能返回304,表示资源未更新。

 

简单来说,Accept-Ranges对应Range来指示服务器使用断点续传,而if-Range对应Etag或者Last-Modified用来增强资源得一致性。If-Modified-Since对应Last-Modified来支持校验资源是否过期,而If-None-Match/Etag用来增强这种作用。

 

问题:if-Range和If-None-Match 的值都为同一个etag,为什么会有不同的响应?

if-Range主要是验证断点续传传输时,资源没有被更改,而If-None-Match是用来校验本地缓存的有效性。

四.关于在浏览器中显示文件内容

浏览器默认会显示一些 text/*,image/*,PDF类型的文件,但默认会变成自动下载,这是我们需要修改响应头为

Content-Disposition:inline; filename="c501b_01_h264_sd_960_540.mp4"

 

© 著作权归作者所有

共有 人打赏支持
IamOkay
粉丝 187
博文 450
码字总数 368039
作品 0
海淀
程序员
加载中

评论(13)

z
zhishaofei3
浏览器默认会显示一些 text/*,image/*,PDF类型的文件,但默认会变成自动下载
这句话真没读懂 到底哪个是默认。。?
loyal
loyal
代码不够严谨,
韦不良
韦不良
IamOkay
IamOkay

引用来自“KeepMoving”的评论

例子也错了,文件
没错,只是有些没例子,我这里主要讲的是原理而已
IamOkay
IamOkay

引用来自“jorneyr”的评论

PHP代码里好像没有看到续传的内容,要下载的是,但还出现了一个$file_size=filesize("a.jpg");//判断文件大小,不知是有什么秘诀在里面。
就是为了客户端能够提前或者io流的大小啊,客户端得到尺寸之后,就能设置进度条一类的东西
公孙二狗
公孙二狗
PHP代码里好像没有看到续传的内容,要下载的是,但还出现了一个$file_size=filesize("a.jpg");//判断文件大小,不知是有什么秘诀在里面。
林泳坛
林泳坛
这里只是说了下载的部分,上传的部分要怎么实现呢?
KeepMoving
KeepMoving
例子也错了,文件
IamOkay
IamOkay

引用来自“西夏一品堂”的评论

http 206是什么
响应码,http status
IamOkay
IamOkay

引用来自“路小磊”的评论

题目一个错别字。断点传续~~
额,凑合看吧。
用Java实现HTTP断点续传(1)

(一)断点续传的原理 其实断点续传的原理很简单,就是在Http的请求上和一般的下载有所不同而已。打个比方,浏览器请求服务器上的一个文时,所发出的请求如下: 假设服务器域名为wwww.sjtu.edu...

millzhang ⋅ 2011/05/04 ⋅ 0

用 Java 实现断点续传 (HTTP)

断点续传的原理 其实断点续传的原理很简单,就是在 Http 的请求上和一般的下载有所不同而已。 打个比方,浏览器请求服务器上的一个文时,所发出的请求如下: 假设服务器域名为 wwww.sjtu.ed...

罪恶的花生 ⋅ 2012/06/22 ⋅ 0

java断点续传

断点续传的原理 其实断点续传的原理很简单,就是在 Http 的请求上和一般的下载有所不同而已。 打个比方,浏览器请求服务器上的一个文时,所发出的请求如下: 假设服务器域名为 wwww.sjtu.ed...

刘万祥 ⋅ 2016/04/13 ⋅ 0

PHP断点续传-HTTP学习笔记

HTTP断点续传原理是这样的: http://www.it.com.cn/f/edu/058/17/159759.htm 1. 客户端需要告诉服务器端从哪里开始。 2.服务端收到请求,返回206状态。并标识续传的起始点及结束点 如下实例 ...

邪恶的小Y ⋅ 2011/07/27 ⋅ 0

php 支持断点续传的文件下载类

php 支持断点续传,主要依靠HTTP协议中 header HTTPRANGE实现。 HTTP断点续传原理 Http头 Range、Content-Range() HTTP头中一般断点下载时才用到Range和Content-Range实体头, Range用户请求...

蜗牛奔跑 ⋅ 2016/03/04 ⋅ 0

NSURLSessionDownloadTask的后台下载与断点续传

对于后台下载与断点续传相信大家肯定不会陌生,那么如果要同时实现这两种需求该怎么办呢? 使用NSURLSessionDataTask可以很轻松实现断点续传,可是有个致命的缺点就是无法进行后台下载,一点应用...

xiaobai1315 ⋅ 2016/09/21 ⋅ 0

解读断点续传的基本原理

断点续传的理解可以分为两部分:一部分是断点,一部分是续传。断点的由来是在下载过程中,将一个下载文件分成了多个部分,同时进行多个部分一起 的下载,当某个时间点,任务被暂停了,此时下...

bengozhong ⋅ 2016/08/31 ⋅ 0

Delphi用ICS控件进行断点续传

Delphi用ICS控件进行断点续传 原帖地址:http://www.lonetear.net/bbs/read.asp?id=36143 原帖地址2(傻猫):http://www.samool.com/archives/41252/ 用HTTPCLI和NMHTTP都可以实现断点续传,原理...

vga ⋅ 2014/06/07 ⋅ 0

HTTP 204/205状态响应&HEAD请求

HTTP 304/200(from cache) 静态资源缓存原理 Http 206 文件断点续传下载原理 一.http 204/205状态响应 HTTP 204(no content)表示响应执行成功,但没有数据返回,浏览器不用刷新,不用导向新页...

IamOkay ⋅ 2016/04/22 ⋅ 0

一个关于android下开发下载断点续传的问题与研究

Android下应用程序要实现断点续传,可以把系统的下载服务搬过来改一下即可(本身已经有一个pause状态应对网络中断,利用它就可以实现暂停跟恢复) 但是………… 我发现很多android站自身建站...

hawkyoung ⋅ 2011/12/28 ⋅ 1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Springboot2 之 Spring Data Redis 实现消息队列——发布/订阅模式

一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式,这里利用redis消息“发布/订阅”来简单实现订阅者模式。 实现之前先过过 redis 发布订阅的一些基础概念和操...

Simonton ⋅ 22分钟前 ⋅ 0

error:Could not find gradle

一.更新Android Studio后打开Project,报如下错误: Error: Could not find com.android.tools.build:gradle:2.2.1. Searched in the following locations: file:/D:/software/android/andro......

Yao--靠自己 ⋅ 昨天 ⋅ 0

Spring boot 项目打包及引入本地jar包

Spring Boot 项目打包以及引入本地Jar包 [TOC] 上篇文章提到 Maven 项目添加本地jar包的三种方式 ,本篇文章记录下在实际项目中的应用。 spring boot 打包方式 我们知道,传统应用可以将程序...

Os_yxguang ⋅ 昨天 ⋅ 0

常见数据结构(二)-树(二叉树,红黑树,B树)

本文介绍数据结构中几种常见的树:二分查找树,2-3树,红黑树,B树 写在前面 本文所有图片均截图自coursera上普林斯顿的课程《Algorithms, Part I》中的Slides 相关命题的证明可参考《算法(第...

浮躁的码农 ⋅ 昨天 ⋅ 0

android -------- 混淆打包报错 (warning - InnerClass ...)

最近做Android混淆打包遇到一些问题,Android Sdutio 3.1 版本打包的 错误如下: Android studio warning - InnerClass annotations are missing corresponding EnclosingMember annotation......

切切歆语 ⋅ 昨天 ⋅ 0

eclipse酷炫大法之设置主题、皮肤

eclipse酷炫大法 目前两款不错的eclipse 1.系统设置 Window->Preferences->General->Appearance 2.Eclipse Marketplace下载【推荐】 Help->Eclipse Marketplace->搜索‘theme’进行安装 比如......

anlve ⋅ 昨天 ⋅ 0

vim编辑模式、vim命令模式、vim实践

vim编辑模式 编辑模式用来输入或修改文本内容,编辑模式除了Esc外其他键几乎都是输入 如何进入编辑模式 一般模式输入以下按键,均可进入编辑模式,左下角提示 insert(中文为插入) 字样 i ...

蛋黄Yolks ⋅ 昨天 ⋅ 0

大数据入门基础:SSH介绍

什么是ssh 简单说,SSH是一种网络协议,用于计算机之间的加密登录。 如果一个用户从本地计算机,使用SSH协议登录另一台远程计算机,我们就可以认为,这种登录是安全的,即使被中途截获,密码...

董黎明 ⋅ 昨天 ⋅ 0

web3j教程

web3j是一个轻量级、高度模块化、响应式、类型安全的Java和Android类库提供丰富API,用于处理以太坊智能合约及与以太坊网络上的客户端(节点)进行集成。 汇智网最新发布的web3j教程,详细讲解...

汇智网教程 ⋅ 昨天 ⋅ 0

谷歌:安全问题机制并不如你想象中安全

腾讯科技讯 5月25日,如今的你或许已经对许多网站所使用的“安全问题机制”习以为常了,但你真的认为包括“你第一个宠物的名字是什么?”这些问题能够保障你的帐户安全吗? 根据谷歌(微博)安...

问题终结者 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部