利用 nginx-upload-module 实现文件上传和重命名
2022-03-23 09:00:00

近来因为毕设需求,需要有一个简单的文件上传服务,结合 Nginx,最终通过 nginx-upload-module 实现。但是该模块开发者考虑到同时间同名文件上传碰撞,将文件名统一设置为一串经过计算得到的数值,交由后端服务对文件做改动。

本来不是啥大问题,只是网上教程都使用其他语言实现文件重命名,我实在不想因为这么一个简单的需求在毕设中引入其他后端语言,于是决定自写一个模块实现文件重命名功能,源码保存在 nginx-upload-rename-module 中。

毕设内容是基于 Nginx 实现流媒体点播服务器,打算自写一个支持 HLS 协议的模块,实现从 .mp4 到 .m3u8 的转码切分。正好写这个重命名模块,学习 Nginx 模块开发。

此次实验在本地机上进行,在服务器上部署基本一致。

❗ 请注意文末的提示!

思路

nginx-upload-module 的工作流程是将上传文件从 HTTP 报文中剥除,组合成文件,计算特定数值后保存到指定目录;其他文件信息则经过整合写入 HTTP 报文的请求体,交由后端处理。

我本打算直接调用它定义的变量,获取诸如文件名、文件类型等信息,直接完成文件重命名,但是可能是它变量定义时对标志位的设置,无法获取索引也没有记录入散列表,无法获得其值,就此作罢。

输出其 HTTP 报文的过程中,偶然明白了 nginx-upload-module 的 HTTP 报文传递到后端时的具体内容,于是决定对 HTTP 报文做处理,获取其特定字段,保存后用以处理文件。

编译

首先下载 Nginx 源码和两个模块:

1
2
3
wget https://nginx.org/download/nginx-1.20.2.tar.gz
git clone https://github.com/fdintino/nginx-upload-module.git
git clone https://github.com/xQmQ/nginx-upload-rename-module.git

解压缩 Nginx 源码后,有以下三个目录:

1
2
3
4
.
├── nginx-1.20.2
├── nginx-upload-module
└── nginx-upload-rename-module

进入nginx-1.20.2,准备编译。这里我直接将安装路径放在了当前目录:

自行安装 Nginx 源码编译时需要的依赖

1
./configure --prefix=$(pwd)/nginx --add-module=../nginx-upload-rename-module --add-module=../nginx-upload-module

经过检查后得到以下信息:

image-20220323181439714

然后执行编译:

1
make

image-20220323181532032

编译成功后安装:

1
make install

这样就得到了配置文件和二进制文件:

image-20220323181624796

配置

首先将nginx-upload-rename-module/UploadSuccess目录复制入nginx/html中。这个目录里有上传成功后返回给前端的页面。

image-20220323181904689

然后修改 Nginx 的配置文件conf/nginx.conf,在http{}中设置以下信息,监听的端口自定,我这里就直接监听 1080 端口。

注意配置upload_store时,指定的目录要存在,且有相应的权限,目录可以自定;client_max_body_size决定上传文件的大小;其他配置项可以看看 ngin-upload-module 的 README

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
server {
listen 80;

client_max_body_size 100m;

# Upload form should be submitted to this location
location /upload {
# Pass altered request body to this location
upload_pass /uploadSuccess;

# Store files to this directory
# The directory is hashed, subdirectories 0 1 2 3 4 5 6 7 8 9 should exist
upload_store /tmp;

# Allow uploaded files to be read only by user
upload_store_access user:rw group:rw all:rw;

# Set specified fields in request body
upload_set_form_field $upload_field_name.name "$upload_file_name";
upload_set_form_field $upload_field_name.content_type "$upload_content_type";
upload_set_form_field $upload_field_name.path "$upload_tmp_path";

# Inform backend about hash and size of a file
upload_aggregate_form_field $upload_field_name.md5 "$upload_file_md5";
upload_aggregate_form_field $upload_field_name.size "$upload_file_size";

upload_pass_form_field "^submit$|^description$";

upload_cleanup 400 404 499 500-505;
}

# Pass altered request body to a backend
location /uploadSuccess {
upload_files_rename on;
}
}

启动

设置好后,进入sbin/,准备启动:

2022-03-23_18-27-08

我没有写前端上传的页面,打算利用 Apifox 直接调用接口。

POST 请求,并插入待上传的文件,访问localhost:1080/upload

我这里在本地机做实验。

image-20220323183258019

可以看到返回了成功界面。

实际上进行上传并调用upload_pass后即会返回此页面,不代表文件重命名成功,但是一般情况下都是成功的。

也可以看到指定的目录下有了对应的文件:

image-20220323183512247

而如果没有启动 nginx-upload-rename-module,一般来说文件名如下,一串数字所示:

image-20220323183627041

提示

因为个人能力有限,模块可能会存在 bug,已经测试过多个不同名文件同时上传,没有什么问题。

但是正如 nginx-upload-module 开发者所说,存在文件碰撞的可能,所以他们决定让用户在后端程序(如 PHP)中移动和重命名文件。我没有条件测试同一上传时间的同名文件碰撞。

但是对于同文件名但不同内容的文件 A 和文件 B,先上传文件 A,再次上传文件 B 后,会删除 文件 A 保留文件 B,即没有设置同名文件碰撞处理,请知悉。

其次,由于模块开发过程中挂载方式的选择,upload_pass指定的location /uploadSuccess可能无法启动其他模块的功能,只能启动本模块(暂未测试)。

所以开发的模块只适用于简单的文件上传服务,请小心使用。