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

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

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

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

header标头说明 

 

断点续传下载需要重视2个请求头Range与If-Range

一.断点续传的原理

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

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

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

1.1不使用断点续传

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

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

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

 

2.使用断点续传

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

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

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

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

Range: bytes=2000070-

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

Range的完整格式是 

  1.  
    Range: bytes=startOffset-targetOffset/sum  [表示从startOffset读取,一直读取到targetOffset位置,读取总数为sum直接]
  2.  
     
  3.  
    Range: bytes=startOffset-targetOffset  [字节总数也可以去掉]

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

  1.  
    HTTP/1.1 206 Partial Content
  2.  
    content-length=106786028
  3.  
    content-range=bytes 2000070-106786027/106786028
  4.  
    date=mon, 30 apr 2001 12:55:20 gmt
  5.  
    etag=w/"02ca57e173c11:95b"
  6.  
    content-type=application/octet-stream
  7.  
    server=microsoft-iis/5.0
  8.  
    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端代码如下

  1.  
    try {
  2.  
    URL url = new URL("http://img5.duitang.com/uploads/item/201203/16/20120316164401_tyAVV.thumb.700_0.jpeg");
  3.  
    File targetFile = new File("test.jpeg"); 
  4.  
    HttpURLConnection openConnection = (HttpURLConnection) url.openConnection();
  5.  
    openConnection.setRequestMethod("POST");
  6.  
    if(targetFile.exists())
  7.  
    {
  8.  
    openConnection.addRequestProperty("Range", "bytes="+targetFile.length()+"-");
  9.  
    }else{
  10.  
    openConnection.addRequestProperty("Range", "bytes=0-");
  11.  
    }
  12.  
    openConnection.connect();
  13.  
    int responseCode = openConnection.getResponseCode();
  14.  
    Map<String, List<String>> headerFields = openConnection.getHeaderFields();
  15.  
    System.out.println(headerFields);
  16.  
    if(responseCode==200 || responseCode==206)
  17.  
    {
  18.  
    InputStream is = openConnection.getInputStream();
  19.  
    FileOutputStream fos = new FileOutputStream(targetFile);
  20.  
     
  21.  
    int len = -1;
  22.  
    byte[] buf = new byte[1024];
  23.  
    while((len=is.read(buf,0,1024))>0)
  24.  
    {
  25.  
     
  26.  
    fos.write(buf, 0, len); 
  27.  
    break;//为了便于测试,每次只读取一次
  28.  
    }
  29.  
     
  30.  
    fos.close();
  31.  
    is.close();
  32.  
    }
  33.  
     
  34.  
    catch (IOException e) {
  35.  
    e.printStackTrace();
  36.  
    }

 

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

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

1.在PHP文件下载所需要的头信息

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

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

2.用代码控制断点续传

  1.  
    <?php
  2.  
    function smartReadFile( $filepath, $mimeType='application/octet-stream')
  3.  
    {
  4.  
     
  5.  
      date_default_timezone_set('GMT');  //注意时区必须是GMT,否则可能产生错误缓存
  6.  
     
  7.  
      $filepath=iconv("utf-8","gb2312",$filepath);
  8.  
     if(!file_exists($filepath))
  9.  
      { 
  10.  
         header ("HTTP/1.0 404 Not Found");
  11.  
        return;
  12.  
      }
  13.  
      $size=filesize($filepath);
  14.  
     
  15.  
      $time=date('D, j M Y H:i:s e',filemtime($filepath)); //转为格林尼治时间,同时注意php中文件时间写入的函数是  touch
  16.  
       
  17.  
      $fm=@fopen($filepath,'rb'); //测试能否打开文
  18.  
      if(!$fm)
  19.  
      { 
  20.  
         header ("HTTP/1.0 505 Internal server error");
  21.  
         return;
  22.  
      }
  23.  
     
  24.  
      $stat = stat($filepath);
  25.  
      $md5str = md5_file($filepath); //使用md5校验,更加精确
  26.  
      $etag =  $md5str.'-'.sprintf('%x-%x-%x', $stat['ino'], $stat['size'], $stat['mtime'] * 1000000);
  27.  
      
  28.  
      if(isset($_SERVER['HTTP_IF_RANGE']) && (($_SERVER['HTTP_IF_RANGE'] == $etag) || (strtotime($_SERVER['HTTP_IF_RANGE']) >= $stat['mtime'])))
  29.  
      {
  30.  
     
  31.  
           header('Etag: "' . $etag . '"');
  32.  
           header('Last-Modified: ' . date('D, j M Y H:i:s e', $stat['mtime']));
  33.  
           header('HTTP/1.0 304 Not Modified');
  34.  
           return ;
  35.  
      }    
  36.  
     
  37.  
       if(isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag)
  38.  
        {
  39.  
            header('Etag: "' . $etag . '"');
  40.  
            header('HTTP/1.0 304 Not Modified');
  41.  
            return ;
  42.  
        } elseif(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $stat['mtime']) {
  43.  
            header('Last-Modified: ' . date('D, j M Y H:i:s e', $stat['mtime']));
  44.  
            header('HTTP/1.0 304 Not Modified');
  45.  
            return;
  46.  
        }
  47.  
       
  48.  
      $begin=0;
  49.  
      $end=$size;
  50.  
       
  51.  
      if(isset($_SERVER['HTTP_RANGE']))
  52.  
      { if(preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches))
  53.  
        { $begin=intval($matches[0]);
  54.  
          if(!empty($matches[1]))
  55.  
            $end=intval($matches[1]);
  56.  
        }
  57.  
      }
  58.  
       
  59.  
      if($begin>0||$end<$size)
  60.  
        header('HTTP/1.0 206 Partial Content');
  61.  
      else
  62.  
        header('HTTP/1.0 200 OK');  
  63.  
       
  64.  
      header("Content-Type: $mimeType"); //指定文件minetype,//注意,部分浏览器mineType需要明确指定(如image/png),否则不能下载
  65.  
      header('Cache-Control: public, must-revalidate, max-age=0'); //控制client缓存,要求不缓存
  66.  
      header('Pragma: no-cache');  
  67.  
      header('Accept-Ranges: bytes'); //表示浏览器接受bytes的断点续传
  68.  
      header('Content-Length:'.($end-$begin)); //如果未指定长度,这以chunked编码传输文件到客户端
  69.  
      header("Content-Range: bytes $begin-$end/$size");
  70.  
      header("Content-Disposition: attachment; filename=".basename($filepath)."");  //文件下载
  71.  
      header('Content-Description: File Transfer');//非标准头信息,可以不要
  72.  
      header("Content-Transfer-Encoding: binary\n"); //非标准头信息,可以不要
  73.  
      header("Last-Modified: $time"); //用于校验
  74.  
      header('Etag: "' . $etag . '"');
  75.  
      header('Connection: close');  
  76.  
       
  77.  
      $cur=$begin;
  78.  
      fseek($fm,$begin,0); //将指针定位到要读取的位置
  79.  
     
  80.  
      while(!feof($fm)&&$cur<$end&&(connection_status()==0))
  81.  
      { 
  82.  
        echo fread($fm,min(1024*16,$end-$cur));
  83.  
        $cur+=1024*16;
  84.  
      }
  85.  
       
  86.  
      fclose($fm);
  87.  
      
  88.  
    }
  89.  
     
  90.  
     
  91.  
     
  92.  
    $file = './test.png';
  93.  
    $exts = get_loaded_extensions();
  94.  
    $mimeType = 'application/octet-stream';
  95.  
    if(array_search('fileinfo', $exts)===FALSE)
  96.  
    {
  97.  
      $sizeInfo = getimagesize($file);
  98.  
      $mimeType = $sizeInfo['mime'];
  99.  
    }else{
  100.  
      $mimeType = mime_content_type($file);
  101.  
    }
  102.  
     
  103.  
    smartReadFile($file,$mimeType);
  104.  
    ?>

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

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

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

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

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

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

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

 

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

不设置If-Range的时候

设置If-Range的时候

3.3使用If-Modified-Since & If-None-Match

If-Modified-Since传递时间

If-None-Match传递etag,不会出现http 412问题

 

简单来说,if-Range是上述两者的综合体,因此,在实际项目中,请根据需要使用哪一种请求头。

 

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

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

Content-Disposition:inline; filename="c501b_01_h264_sd_960_540.mp4"
posted @ 2018-10-18 16:00  xiezhenzhong  阅读(1125)  评论(0编辑  收藏  举报