文件下载就是把文件从服务器上面下下来,比如一个文件是http://www.xxx.com/1.rar(真实存在这个文件),直接在浏览器上面输入,是可以弹出下载对话框的,但是如果是一个图片地址,很可能就是不是下载了,而是用浏览器直接打开.还有就是为了不让客户端看到下载地址,最好也是不直接提供下载文件地址的.还有就是比如只有登录用户才能下载。。 等等 .所以还是要单独写个下载程序
有几个响应头比较重要,记录下
- Content-type
- Content-Disposition
- Content-Length
- Pragma
- Cache-control
Content-type 告诉浏览器文件的MIME 类型,这是非常重要的一个响应头了,MIME种类繁多,真是太多了
Content-Disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。当 Internet Explorer 接收到头时,它会激活文件下载对话框,它的文件名框自动填充了头中指定的文件名。 嗯,就是这个头哟,激活弹出提示下载框
因为MIME的类型非常多,很可能会在程序中漏掉一些MIME类型.可以这样,漏掉了类型,全部以"Content-type: application/octet-stream" "Content-Disposition: attachment; filename=xxx.xx"(xx.xx为文件名) 经过一些浏览器的测试发现下载下来的文件都是正确的
"Content-Length: 123" 就是告诉浏览器这个文件的大小是123字节,其实我发现好像不设置这个头,浏览器也能自己识别
把这2个头都设置成public 告诉浏览器缓存(其实对于缓存头 我有很多都没理解清楚 除了public 它表示全部都缓存,但是至于浏览器缓存不缓存,可能还不一定,我试了很多浏览器,没有一个是从浏览器缓存读文件的,但是从服务器上下的)
在输出文件的时候,我看网上大概有2中方式:一种是一次性输出,一中是分段输出(每次输出一部分),我去csdn问了下,别人说分段输出好,减轻服务器压力,因为每次读文件的时候占的内存都少
一个例子(里面的MIME很少,更多的MIME最好去网上找)
class downLoad{
var $file_name;
var $file_dir;
var $buffer_size = 1024;
var $err = "";
public static $MIME_type = array(
"pdf" =>"application/pdf",
"exe" =>"application/octet-stream",
"zip" =>"application/zip",
"doc" =>"application/msword",
"xls" =>"application/vnd.ms-excel",
"ppt" =>"application/vnd.ms-powerpoint",
"gif" =>"image/gif",
"png" =>"image/png",
"jpeg" =>"jpg",
"mp3" =>"audio/mpeg",
"wav" =>"audio/x-wav",
"mpeg" =>"mpg",
"mpe" =>"video/mpeg",
"mov" =>"video/quicktime",
"avi" =>"video/x-msvideo",
);
public function __construct($file_dir="",$file_name=""){
$this->file_dir = $file_dir;
$this->file_name = $file_name;
$this->path = $file_dir."/".$file_name;
$this->suffix = pathinfo($file_name,PATHINFO_EXTENSION);
}
public function down(){
if(!file_exists($this->path)){
$this->err = "该文件被移除了";
return false;
}
$content_type = $this->getMIME($this->suffix);
$file_size = filesize($this->path);
header("Content-type: ".$content_type);
header('Content-Disposition: attachment; filename="'.$this->file_name.'"');
@header("Cache-control: public");
@header("Pragma: public");
header("Content-Length: ".$file_size);
ob_end_clean();
//readfile($this->path); 一次性读出来
$fp= fopen($this->path,"r");
$buffer_size = $this->buffer_size;
$cur_pos = 0; //记录读了多少了
while(!feof($fp) && $file_size>$buffer_size+$cur_pos){
$buffer = fread($fp,$buffer_size); //每次读1024字节
echo $buffer;
$cur_pos += $buffer_size;
}
//把剩下的读出来 因为文件的带下很有很能不是1024 的整数倍
$buffer = fread($fp,$file_size-$cur_pos);
echo $buffer;
fclose($fp);
return true;
}
public function getMIME($key=""){
if($key == "" || !isset(self::$MIME_type[$key])){
return "application/octet-stream";
}
return self::$MIME_type[$key];
}
}
// $x = new downLoad($file_dir,$file_name); $file_dir路径 比如 all $file_name文件名 比如 a.exe 合起来就是全部的路径了all/a.exe
// $x->down();
这里面的断点续传指的是下载时候的断点续传的断点续传,上传的断点续传完全不明白原理
一些预备的东西
- http 状态码206的意思
- 响应头 "Accept-Ranges: bytes"
- 响应头 "Content-Range:bytes start-end/all"
- fseek
- ob_flush
请求的 header 必须包含一个 Range 字段,表明自己请求第几个字节到第几个字节的内容。如果服务器支持的话,就返回 206 Partial Content,然后使用 header 的 Content-Range 指明范围,并在 body 内提供这个范围内的数据。
这个字段说明Web服务器是否支持Range(是否支持断点续传功能),如果支持,则返回Accept-Ranges: bytes,如果不支持,则返回Accept-Ranges: none.
用于响应头,指定了返回的Web资源的字节范围。这个字段值的格式是: 开始字节位置—结束字节位置/Web资源的总字节数,一个例子:Content-Range:1000-3000/5000
fseek : 该函数把文件指针从当前位置向前或向后移动到新的位置,新位置从文件头开始以字节数度量
ob_flush : 该函数把文件指针从当前位置向前或向后移动到新的位置,新位置从文件头开始以字节数度量
我测试了一下 ie7,8是不支持断点续传的(也许可以 但是我不会),苹果浏览器也不行.哎.......... (还好 firefox chrome Opera支持)
首先客户端发一次请求给php请求下载文件
这时因为是第一次请求 所以请求头里面是没有 Range头的
我用Fiddler抓包的截图
请求头
在php端 $_SERVER['HTTP_RANGE']可以取请求头里面的Rang, 因为第一次不存在这个,所以我们取文件内容就取全部,然后返回的状态码也是200
响应头
当传了一部分内容后,点击暂停,在点击几乎继续,抓包的响应头就有Range了
php端返回的时候也把返回内容的范围返回了 Content-Range: bytes 4194304-37939151/37939152 是返回数据的范围
Accept-Ranges: bytes表示支持断点续传
Content-Length: 33744847 里面的值是根据请求的Range, 也就是 $_SERVER['HTTP_RANGE'], 和文件的总大小,相减得出来的
另外http状态码是206
例子
class downLoad
{
var $file_path;
var $file_name;
var $file_suffix;
var $speed = 1024; //表示下载速度1m
var $err = "";
var $range_start=0;
var $range_end;
var $http_range;
var $file_size;
public static $MIME_type = array(
"pdf" =>"application/pdf",
"exe" =>"application/octet-stream",
"zip" =>"application/zip",
"doc" =>"application/msword",
"xls" =>"application/vnd.ms-excel",
"ppt" =>"application/vnd.ms-powerpoint",
"gif" =>"image/gif",
"png" =>"image/png",
"jpeg" =>"jpg",
"mp3" =>"audio/mpeg",
"wav" =>"audio/x-wav",
"mpeg" =>"mpg",
"mpe" =>"video/mpeg",
"mov" =>"video/quicktime",
"avi" =>"video/x-msvideo",
);
public function __construct($file_path="",$http_ranges="",$speed=""){
$this->file_path = $file_path;
$this->file_name = basename($file_path);
$this->file_suffix = substr(strrchr($this->file_name, '.'),1);
$this->file_size= filesize($file_path);
$this->http_range = (isset($http_ranges) && !empty($http_ranges)!="")?$http_ranges:false;
!empty($speed)&&($this->speed = $speed);
}
public function down(){
if(!file_exists($this->file_path)){
$this->err = "该文件被移除";
return false;
}
$file_path = $this->file_path;
$content_type = $this->getMIME($this->file_suffix);
$http_range = $this->http_range;
if($http_range){
//$http_range 的形式为 bytes=3145728- 差不多就是这个样子的
$http_range = preg_replace('/[\s|,]*/', '', $http_range);
$arr = explode('-',substr($http_range,6));
$this->range_start = ( isset($arr[0]) && !empty($arr[0]) ) ? $arr[0] : 0;
$this->range_end = ( isset($arr[1]) && !empty($arr[1]) && $arr[1]!=0 ) ? $arr[1] : $this->file_size-1;
}
$this->setHeader();
$fh = fopen($file_path, "rb");
fseek($fh, $this->range_start);
$speed = $this->speed;
while(!feof($fh))
{
echo fread($fh, 1024*$speed);
ob_flush();
sleep(1);
}
fclose($fh);
}
public function getMIME($key=""){
if($key=="" || !isset(self::$MIME_type[$key])){
return "application/octet-stream";
}
return self::$MIME_type[$key];
}
public function setHeader(){
header('Cache-control: public');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.$this->file_name);
if($this->http_range){
$range_start = $this->range_start;
$range_end = $this->range_end;
header('HTTP/1.1 206 Partial Content');
header('Accept-Ranges: bytes');
header( sprintf('Content-Length: %u',$range_end - $range_start) );
header( sprintf('Content-Range: bytes %s-%s/%s', $range_start, $range_end, $this->file_size) );
}else{
header("HTTP/1.1 200 OK");
header(sprintf('Content-Length: %s',$this->file_size));
}
}
}
//$http_range = isset($_SERVER['HTTP_RANGE'])?$_SERVER['HTTP_RANGE']:false;
//$x = new downLoad("all/1.exe",$http_range);
//$x->down();