利用Nginx第三方模块,实现附件打包下载
实现附件的打包下载,需要将一批逻辑上一起的文件,让用户通过一个下载按钮打包下载。首先想到的方案是服务端调用什么zip之类的类库,将文件打包好后返回客户端。但是这样做有一个很明显的问题:文件很多很大的情况下,打包可能会占用大量的内存和cpu,就算在磁盘上构建临时的打包文件,也会增加服务器的磁盘IO负担,而且这些临时的文件无故占用大量的磁盘空间,删除还是个问题。
mod_zip介绍
mod_zip能够动态的构建zip包,这种动态体现在当Nginx作为反向代理服务器的时候,该模块能够根据上游服务器返回的文件列表来打包文件。mod_zip实际上是利用Nginx的subrequest功能,将zip流发送到客户端的,而且它实际上只打包不压缩,所以借助Nginx本身作为文件服务器的能力,该模块的内存占用十分少,对于上G的大文件也没有问题。zip文件本身是结构化的,可以自定义目录结构,所以对于mod_zip而言,要做的只是添加zip的头部尾部和zip内部的目录结构元数据而已,文件数据本身依靠Nginx自身的机制发送。
除此之外,还有如下两点:
基本使用
安装
官方网址: https://github.com/evanmiller/mod_zip
下载源码:
$ git clone https://github.com/evanmiller/mod_zip.git
在nginx的源码目录,重新编译Nginx,不要make install:
$ ./configure --add-module=/src/mod_zip
$ make
将生成的二进制文件覆盖现有的二进制文件。通常编译出来的二进制文件位于源码目录的objs/nginx。更多关于如何添加第三方模块看如何安装nginx第三方模块
$ sudo cp objs/nginx /usr/sbin/nginx
使用方法
该模块不需要在nginx.conf中配置任何东西,一切的行为取决于上游服务器的响应内容。mod_zip规定当响应头中包含X-Archive-Files的时候,将启用mod_zip的功能:
X-Archive-Files: zip
同时,响应的body中需要包含一个欲打包的文件的列表,如:
1034ab38 428 /foo.txt My Document1.txt
83e8110b 100339 /bar.txt My Other Document1.txt
每一行表示一个文件描述,行与行之间有一个换行符(最后也有个换行)。每行从左向右以空格分隔,依次是文件的crc-32校验,文件大小(Byte),文件的uri,文件名。其中crc-32可以忽略,并用-代替,文件名可以包含目录,会体现在最后的压缩包中的目录结构中。
重点是文件的uri怎么理解。这里的/foo.txt和/bar.txt并非指向文件系统的路径,而是一个子请求的地址。比如上面的/foo.txt实际上会产生一个Nginx自身的请求:http://host/foo.txt,至于这个请求得到什么又要根据nginx.conf中的配置决定了。
这样的设计十分灵活,例如下面的配置:
location ~ "^/(?
proxy_pass http://$srv.domain.com/$file;
}
于是,可以这样使用文件uri:
1034ab38 428 /server1/foo.txt My Document1.txt
83e8110b 100339 /server2/bar.txt My Other Document1.txt
这样两个文件分别会向远程服务器请求文件:
http://server1.domain.com/foo.txt
http://server2.domain.com/bar.txt
上游服务器可以通过在头部注入Content-Disposition来控制zip文件的输出文件名
Content-Disposition: attachment; filename=foobar.zip
完整配置示例:
假如nginx的配置的端口为9080
server {
listen 9080;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
location /down/ {
proxy_pass http://down1.example.com/;
}
}
在/usr/share/nginx/html放置两个pdf文件1.pdf,2.pdf
假如文件大小如下
-rw-r--r-- 1 root root 87652 Dec 3 20:54 1.pdf
-rw-r--r-- 1 root root 88195 Dec 3 20:54 2.pdf
配置上游apache服务器
ServerName down1.example.com
DocumentRoot /data/htdocs/example.com
下面是个测试用的上游apache的php代码例子(其他可以执行php代码的服务器也可以)
header('X-Accel-Chareset: utf-8');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=test.zip');
header('X-Archive-Files: zip');
$crc32 = "-";
printf("%s %d %s %s\n", $crc32, 87652, '/1.pdf', 'test-1.pdf');
printf("%s %d %s %s\n", $crc32, 88195, '/2.pdf', '2.pdf');
?>
假如上面的代码,保存为1.php,通过访问nginx服务器的url为http://localhost:9080/down/1.php ,nginx转发请求到 apache, 下的1.php 1.php输出要打包的pdf文件信息,nginx读取文件信息,返回给客户端。