普通网站在实现文件上传功能的时候,一般是使用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