普通网站在实现文件上传功能的时候,一般是使用Python,Java等后端程序实现,比较麻烦。Nginx有一个Upload模块,可以非常简单的实现文件上传功能。此模块的原理是先把用户上传的文件保存到临时文件,然后在交由后台页面处理,并且把文件的原名,上传后的名称,文件类型,文件大小set到页面。下面和大家具体介绍一下。
一、编译安装Nginx
为了使用Nginx Upload Module,需要编译安装Nginx,将upload module编译进去。upload module的代码可以去Github上下载: Upload Module
之后的编译安装Nginx这里就不介绍,不了解的可以参考: Ubuntu 14.10下源码编译安装Nginx 1.8.0
二、Nginx配置
模块通过Nginx服务器来接受用户上传的文件,在Nginx接受完文件以后再转给后端的程序做处理。
它自动分析客户端的上传请求,将上传的文件保存到 upload_store 所指向的目录位置. 然后这些文件信息将被从原始的请求中剔除,重新组装好上传参数后转到后端由 upload_pass 指定的位置去处理,这样就可以任意处理上传的文件。
每一个上传的 file 字段值将可以由upload_set_form_field 指定的值替换. 文件的内容可以由$upload_tmp_path 变量读到或简单的移到其他位置. 将文件删除由 upload_cleanup 指定控制。
upload_set_form_field可以使用的几个变量
- $upload_field_name 原始的文件字段
- $upload_content_type 上传文件的类型
- $upload_file_name 客户端上传的原始文件名称
- $upload_tmp_path 上传的文件保存在服务端的位置
upload_aggregate_form_field 可以多使用的几个变量,文件接收完毕后生成的
- $upload_file_md5 文件的MD5校验值
- $upload_file_md5_uc 大写字母表示的MD5校验值
- $upload_file_sha1 文件的SHA1校验值
- $upload_file_sha1_uc 大写字母表示的SHA1校验值
- $upload_file_crc32 16进制表示的文件CRC32值
- $upload_file_size 文件大小
Nginx upload module的简单配置如下:
server {
listen *:80 default_server;
server_name 192.168.1.251;
client_max_body_size 20m;
client_body_buffer_size 512k;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE_ADD $remote_addr;
location /upload {
# 转到后台处理URL,表示Nginx接收完上传的文件后,然后交给后端处理的地址
upload_pass @python;
# 临时保存路径, 可以使用散列
# 上传模块接收到的文件临时存放的路径, 1 表示方式,该方式是需要在/tmp/nginx_upload下创建以0到9为目录名称的目录,上传时候会进行一个散列处理。
upload_store /tmp/nginx_upload;
# 上传文件的权限,rw表示读写 r只读
upload_store_access user:rw group:rw all:rw;
set $upload_field_name "file";
# upload_resumable on;
# 这里写入http报头,pass到后台页面后能获取这里set的报头字段
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;
# Upload模块自动生成的一些信息,如文件大小与文件md5值
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 "^.*$";
# upload_pass_form_field "^submit$|^description$";
# 每秒字节速度控制,0表示不受控制,默认0, 128K
upload_limit_rate 0;
# 如果pass页面是以下状态码,就删除此次上传的临时文件
upload_cleanup 400 404 499 500-505;
# 打开开关,意思就是把前端脚本请求的参数会传给后端的脚本语言,比如:http://192.168.1.251:9000/upload/?k=23,后台可以通过POST['k']来访问。
upload_pass_args on;
}
location @python {
proxy_pass http://localhost:9999;
# return 200; # 如果不需要后端程序处理,直接返回200即可
}
}
三、后端处理程序
这里我们使用Django作为后端处理程序,比较简单,具体如下:
首先创建Django项目:
django-admin.py startproject uploadmodule
然后,创建views.py文件,代码如下:
# -*- coding: utf-8 -*-
import os
import json
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
UPLOAD_FILE_PATH = '/tmp/nginx_upload/'
@csrf_exempt
def upload(request):
request_params = request.POST
file_name = request_params['file_name']
file_content_type = request_params['file_content_type']
file_md5 = request_params['file_md5']
file_path = request_params['file_path']
file_size = request_params['file_size']
ip_address = request.META.get('HTTP_X_REAL_IP') or request.META.get('HTTP_REMOTE_ADD')
# save file to tmp
new_file_name = '%s_%s' % (file_md5, ip_address)
new_file_path = ''.join([UPLOAD_FILE_PATH, new_file_name, os.path.splitext(file_name)[-1]])
with open(new_file_path, 'a') as new_file:
with open(file_path, 'rb') as f:
new_file.write(f.read())
content = json.dumps({
'name': file_name,
'content_type': file_content_type,
'md5': file_md5,
'path': file_path,
'size': file_size,
'ip': ip_address,
})
response = HttpResponse(content, content_type='application/json; charset=utf-8')
return response
四、示例
上面的代码完成之后,我们通过下面的命令启动Django后端程序:
cd uploadmodule/
python manage.py runserver 0.0.0.0:9999
然后,模拟POST请求:http://192.168.1.251/upload/,上传一个jpg文件,返回结果如下:
{
"name": "6125444419718417450.jpg",
"ip": "192.168.1.121",
"content_type": "image/jpeg",
"path": "/tmp/nginx_upload/0000000002",
"md5": "c3b1bd2e72694a8d5fc4548b9ecd9e18",
"size": "37980"
}
五、其他实例
Lua的方式处理:
location ~ /upload { # 调用的路由
# 转到后台处理URL
upload_pass /uploadHandle;
# 临时保存路径
upload_store /opt/upload/tmp;
#上传文件的权限,rw表示读写,r只读
upload_store_access user:rw group:rw all:rw;
# 是否允许nginx上传参数到后台
upload_pass_args on;
# 这里写入http报头,pass到后台页面后能获取这里set的报头字段
upload_set_form_field "file_name" $upload_file_name;
upload_set_form_field "file_content_type" $upload_content_type;
upload_set_form_field "file_tmp_path" $upload_tmp_path;
# Upload模块自动生成的一些信息,如文件大小与文件md5值
upload_aggregate_form_field "file_md5" $upload_file_md5;
upload_aggregate_form_field "file_size" $upload_file_size;
# 允许的字段,允许全部可以 "^.*$"
upload_pass_form_field "^submit$|^description$";
# 每秒字节速度控制,0表示不受控制,默认0
upload_limit_rate 0;
# 如果pass页面是以下状态码,就删除此次上传的临时文件
upload_cleanup 400 404 499 500-505;
}
# 后台处理路由,使用Lua
location ~ /uploadHandle {
lua_need_request_body on;
content_by_lua_file /opt/nginx/luas/onupload.lua; # Lua脚本的位置
return 302 'http://***********'; # 处理完重定向到展示页面
}
客户端直接post提交过来就行,下面是用于处理提交过来的文件的Lua脚本:
function onupload()
ngx.req.read_body();
local post_args=ngx.req.get_post_args();
local table_params=getFormParams(post_args);
ret_val=processFile(table_params);
if (ret_val==0) then
ngx.say("Boy, upload success!")
else
ngx.say("Something wrong with nginx!!")
end
end
function processFile(params)
local root_dir="/opt/upload/"; -- 文件目录
local filename=params["file_name"];
local mv_command="mv "..trim(params["file_tmp_path"]).." "..root_dir..filename;
if (os.execute(mv_command)~=0)then
ngx.exec("/50x.html");
return 1;
else
return 0;
end
end
function trim(str)
if (str~=nil)then
return string.gsub(str, "%s+", "");
else
return nil;
end
end
-- [[提交过来的表单数据是一个超长的字符串,
需要从这个字符串中解析出字典结构的数据,
这样可以利用key来访问字典中对应的值
]]
function getFormParams(post_args)
local table_params={};
for key, val in pairs(post_args) do
str_params = key ..val
end
local str_start = " name";
local str_start_len = string.len(str_start);
local str_end = "%-%-";
local str_sign = "\"";
local idx,idx_end = string.find(str_params,str_start);
local i = 0;
-- 如果字符串内仍有开始标记,则说明还有内容需要分离。继续分离到没内容为止。
while idx do
str_params = string.sub(str_params,idx_end); -- 截取开始标记后所有字符待用
i = string.find(str_params,str_sign); -- 查找字段名开始处的引号索引
str_params = string.sub(str_params,i+1); -- 去掉开始处的引号
i = string.find(str_params,str_sign); -- 查找字段名结束位置的索引
f_name = string.sub(str_params,0,i-1); -- 截取到字段名称
str_params = string.sub(str_params,i+1); -- 去掉名称字段以及结束时的引号
i,i2 = string.find(str_params,str_end); -- 查找字段值结尾标识的索引
f_value = string.sub(str_params,1,i-1); -- 截取到字段值
real_value=trim(f_value)
table_params[f_name] = real_value;
idx = string.find(str_params,str_start,0); -- 查找判断下一个字段是否存在的
end
local root_dir="/opt/upload/";
table_params["file_path"]=root_dir..table_params["file_name"]
return table_params
end
onupload();
demo.conf
upload_progress proxied 1m;
upload_progress_json_output;
server {
listen 8101;
access_log /var/log/nginx/access-uploads.log main;
error_log /var/log/nginx/error-uploads.log;
client_max_body_size 2000m;
client_body_buffer_size 512k;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE_ADD $remote_addr;
location /upload {
auth_request /auth;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_request_buffering off;
proxy_pass http://127.0.0.1:8101/upload_internal;
proxy_redirect off;
#track_uploads proxied 30s;
}
location /upload_internal {
add_header 'Access-Control-Allow-Origin' '*';
upload_resumable on;
# 转到后台处理URL,表示Nginx接收完上传的文件后,然后交给后端处理的地址
upload_pass @php;
# 临时保存路径, 可以使用散列
# 上传模块接收到的文件临时存放的路径, 1 表示方式,该方式是需要在/tmp/nginx_upload下创建以0到9为目录名称的目录,上传时候会进行一个散列处理。
upload_store /home/nginx_upload;
# 上传文件的权限,rw表示读写 r只读
upload_store_access user:rw group:rw all:rw;
# 这里写入http报头,pass到后台页面后能获取这里set的报头字段
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;
# Upload模块自动生成的一些信息,如文件大小与文件md5值
upload_aggregate_form_field "$upload_field_name.md5" $upload_file_md5;
upload_aggregate_form_field "$upload_field_name.sha1" "$upload_file_sha1";
upload_aggregate_form_field "${upload_field_name}_size" $upload_file_size;
# 允许的字段,允许全部可以 "^.*$"
upload_pass_form_field "^.*$";
# upload_pass_form_field "^submit$|^description$";
# 每秒字节速度控制,0表示不受控制,默认0, 128K
upload_limit_rate 0;
# 如果pass页面是以下状态码,就删除此次上传的临时文件
upload_cleanup 400 403 404 499 500-505;
# 打开开关,意思就是把前端脚本请求的参数会传给后端的脚本语言,比如:http://192.168.1.251:9000/upload/?k=23,后台可以通过POST['k']来访问。
upload_pass_args on;
track_uploads proxied 30s;
}
location = /auth {
internal;
#rewrite ^ /auth.php$arg_test break;
set $query '';
if ($request_uri ~* "[^\?]+\?(.*)$") {
set $query $1;
}
proxy_pass http://127.0.0.1:8100/auth.php?$query;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
location ^~ /progress {
add_header 'Access-Control-Allow-Origin' '*';
report_uploads proxied;
}
location @php {
rewrite ^ /index.php$1 break;
proxy_pass http://127.0.0.1:8100;
#return 403; # 如果不需要后端程序处理,直接返回200即可
}
}
1.用户访问 /upload 进行 auth 验证,验证成功 运行 upload_internal 调用
参考:
Ubuntu 14.10下源码编译安装Nginx 1.8.0
HttpUploadModule - Nginx Community
vkholodkov/nginx-upload-module at 2.2
Uploading to nginx using the nginx upload module with php_handler