如何下载文件?

方法一、直接通过nginx下载静态文件

如果文件是保存在服务器上面的,可以直接用nginx下载文件

比如说可以供用户下载pdf文件,那么我的nginx配置可以是这样子的:

location ~ /document/(.*)\.pdf$ {
        root /home/nemo/myfile;
        try_files /$uri 404;
} 

按照上面的配置,当我请求http://fbd.intelleeegooo.cc/document/test.pdf的时候,我服务器上的位于/home/nemo/myfile/document/test.pdf的这个文件就被下载了。当找不到相应的文件的时候,就会返回404


方法二、通过php读取文件并下载

但上面这种方式是所有人都可以下载pdf文件的,假如说下载文件这个动作是与账号有关的,比如说某用户只能下载某些文件,那么就需要在php里面对用户账户进行处理并且下载相关文件。

看我在index.php里面这段示例代码,这段代码的功能下载test.txt文件

<?php


$filePath = '/home/nemo/fun/testdownloadfile/test.txt';
$fileName = 'test.txt';

readfile($filePath);

比如说我开了一个8764端口,nginx配置如下:

server {
	listen 8764;
	server_name xx.xx.xx;


	……
	……
	……

	location / {
		root            /home/nemo/fun/testdownloadfile;
        fastcgi_pass    127.0.0.1:xxxx;
        fastcgi_index   index.php;
        include         fastcgi.conf;
	}
}

配置文件里面的fastcgi_pass后面可以是ip+端口,也可以是unix_socket的路径。具体根据你安装的php的里面的php-fpm.conflisten来决定。

我们用command + option + i快捷键打开浏览器的调试模式,当我在浏览器里面请求http://xx.xx.xx:8764/的时候,结果是浏览器直接把txt文件的内容显示在了页面上。

看一下调试模式里面的这个请求,它的response header如下:

response header

可以看到它里面的Content-Typetext/html,表示是一个html文件,所以浏览器就直接展示在页面上了。【关于常用的一些Content-Type,可以见本文最后】

那么我改一下代码,在里面设置一下header,示例代码如下:

<?php


$filePath = '/home/nemo/fun/testdownloadfile/test.txt';
$fileName = 'test.txt';

header('Content-Disposition: attachment; filename=' . $fileName);

readfile($filePath);

我在chrome里面新建一个tab页输入urlhttp://fbd.intelleeegooo.cc/document/test.pdf的时候,成功下载了这个文件,如下图所示:

download in chrome

但是我在safari里面的时候,下载下来的文件多了一个html后缀,如下图所示

download in safari

我再改下代码,设置Content-Type,看如下示例代码:

<?php


$filePath = '/home/nemo/fun/testdownloadfile/test.txt';
$fileName = 'test.txt';

header('Content-Type: application/octet-stream;charset=utf-8');
header('Content-Disposition: attachment; filename=' . $fileName);

readfile($filePath);

这样改过之后,在safari里面下载的文件就是正常的了,不带html后缀的。


2.2 在php里面读取并输出文件的几种方法

在设置完header信息之后,下面几种方法都可以用来输出文件

    1. file_get_contents(),这个方法是把文件的内容以字符串的形式全部读取到内存里面。当文件比较大的时候,会超过内存限制
$content = file_get_contents($filePath);
echo $content;
    1. file(),将文件以行的形式全部读取到数组中。当文件比较大的时候,会超过内存限制
$f = file($filePath);
while(list($line, $content) = each($f)) { // $line是int类型表示是第几行(从0开始), $content是字符串类型表示这一行的内容
   echo $content;
}
    1. readfile(),读取文件并且写入到输出缓冲区。这种方式可以输出大文件,读取单个文件不会超出内存限制。
ob_end_clean();
readfile($filePath);

但是看官方手册上面的这段话

官方手册上关于readfile的提示

readfile自身不会导致任何内存问题。如果出现内存不足的问题,使用ob_get_level()确保输出缓存已经关闭。

readfile()方法还是可以会引起内存耗尽,鸟哥的这篇笔记里面写到:

readfile实际上还是需要采用MMAP(如果支持), 或者是一个固定的buffer去循环读取文件, 直接输出。

    1. fopen(),这就类似于C语言里面的读取文件。fopen每次可以指定读取某个块大小的内容,可以读入大文件。不会超过内存限制
$file = @fopen($filePath,"rb");
while(!feof($file)) {
    print(@fread($file, 1024*8));
    ob_flush();
    flush();
}

2.3 内存限制

在php的配置文件php.ini里面,有一个memory_limit这个设置项,设置的是每个脚本可以分配的内存。

如下图所示,我自己放宽了一点变成了256M,默认是128M

memory limit in php.ini

正如上面所说,读取大文件的时候,可能会内存耗尽。

php里面有ini_set()方法可以在脚本运行时保持新的值,在脚本结束时恢复。

并不是php.ini里面的所有设置项都可以被修改,所有可以被ini_set()修改的选项可以从官方手册里面的这个清单知晓

有一种方法可以在执行的时候动态的修改脚本可以使用的内存大小,而不一定非要修改php.ini文件,毕竟php.ini是针对全局的。

在脚本里面动态的修改一些设置,只对该脚本有效,实际上并不真正地修改php.ini文件。

2.5 时间限制

一般情况下,使用php下载文件的时候,会加上一行set_time_limit(0);,表示不限制这个php脚本执行的时间

<?php


$filePath = '/home/nemo/fun/testdownloadfile/test.txt';
$fileName = 'test.txt';

set_time_limit(0);
header('Content-Type: application/octet-stream;charset=utf-8');
header('Content-Disposition: attachment; filename=' . $fileName);

readfile($filePath);

看下官方手册上的解释

限制脚本运行的最大时间


Content-Disposition相关解释

在常规的HTTP应答中,Content-Disposition消息头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地

Content-Disposition消息头最初是在MIME标准中定义的,HTTP表单及POST 请求只用到了其所有参数的一个子集。只有form-data以及可选的name和filename三个参数可以应用在HTTP场景中

inline

  • inline展示txt文件

看如下示例代码,设置inline内联,将上面的test.txt文件在浏览器里面展示

<?php

$filePath = '/home/nemo/fun/testdownloadfile/test.txt';
$fileName = 'test.txt';

header('Content-Disposition: inline; filename=' . $fileName);

readfile($filePath);

常用的几种Content-Type类型

下面列一下常用的几种Content-Type

  • text/html,内容是html格式

  • text/plain,内容是纯文本格式

  • image/gif, gif图片格式

  • image/jpeg, jpg图片格式

  • image/png, png图片格式

  • multipart/form-data,常见的 POST 数据提交的方式。当需要上传文件时,会用到这种类型

  • application/json,消息主体是序列化后的 JSON 字符串

  • application/octet-stream,二进制流数据。一般在下载文件的时候比较常见

  • application/x-www-form-urlencoded, 浏览器的原生form表单,提交的数据按照 key1=val1&key2=val2 的方式进行编码,key和val都进行了URL转码