Nginx与Lua: 使用Nginx的upload模块打造文件上传服务
客户端提出搞一个日志上传的接口,方便向服务端提交日志,研究了一下,决定用Nginx嵌入Lua的方式搞。
Lua是一个可以嵌入到Nginx配置文件中的动态脚本语言,从而可以在Nginx请求处理的任何阶段执行各种Lua代码。业余时间使用过,感觉小巧轻快,代码风格和Python一样简洁优雅。在Nginx中内嵌Lua,需要你重新编译安装Nginx,加上ngx_lua模块,同时需要安装LuaJIT,可以看看这篇文章”Nginx与Lua“。
安装好相关的模块后,Nginx配置如下:
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();