基于 lua-resty-upload 实现简单的文件上传服务,
基于 lua-resty-upload 实现简单的文件上传服务,
文章引用:
1 http://www.codexiu.cn/nginx/blog/11024/
前言:
参照 lua-resty-upload 模块结合Nginx实现对外提供一个url,利用post方式上传文件的lua脚本。测试方式可以利用postman直接调用对外url,以post方式上传文件。
在这里不对Nginx的配置做多余的说明,关于Nginx的配置上传模块可以参见百度。
lua-resty-upload 在 github 上的项目地址为:https://github.com/openresty/lua-resty-upload
一丶接收上传文件的lua脚本
--从环境变量LUA_PATH中搜索lua文件
package.path = './lualib/resty/?.lua;'
--从LUA_CPATH中搜索C文件
package.cpath = './lualib/?.so;'
--==========================================
-- 获取上传文件名称
--==========================================
function get_filename(res)
local filename = ngx.re.match(res,'(.+)filename="(.+)"(.*)')
if filename then
return filename[2]
end
end
--==========================================
-- 获取上传文件路径
--==========================================
function get_fileUploadPath()
local obj = io.popen("cd")
local path = obj:read("*all"):sub(1,-2)
--记录当前lua脚本所在的绝对路径
--local cjson = require("cjson.safe")
--local logs = {lua_script_absolutely_path = path}
--local json = cjson.encode(logs)
ngx.log(ngx.ERR, "lua_script_absolutely_path is: " .. path)
path = path.sub(path, 1, string.len(path) - 16) .. "data/package_upload"
--记录当前上传文件存储的绝对路径
--logs = {upload_file_absolutely_path = path}
--json = cjson.encode(logs)
ngx.log(ngx.ERR, "upload_file_absolutely_path is: " .. path)
return path
end
--==========================================
-- 文件上传
--==========================================
function upload()
local upload = require("upload")
local chunk_size = 4096
local form, err = upload:new(nil,chunk_size,chunk_size)
if not form then
ngx.log(ngx.ERR, "failed to new upload: ", err)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local file
local filelen=0
form:set_timeout(0) -- 1 sec
local filename
local osfilepath = get_fileUploadPath()
local i=0
while true do
local typ, res, err = form:read()
if not typ then
ngx.say("failed to read: ", err)
return
end
if typ == "header" then
if res[1] ~= "Content-Type" then
filename = get_filename(res[2])
if filename then
i=i+1
filepath = osfilepath .. filename
file = io.open(filepath,"w+")
if not file then
ngx.say("failed to open file ")
return
end
else
end
end
elseif typ == "body" then
if file then
filelen= filelen + tonumber(string.len(res))
file:write(res)
else
end
elseif typ == "part_end" then
if file then
file:close()
file = nil
--调用agent的bat脚本,并用agent的返回值代替下面say的内容
local result = io.popen('test.bat')
local returnValue = result:read("*all")
ngx.say(returnValue)
end
elseif typ == "eof" then
break
else
end
end
if i==0 then
ngx.say("please upload at least one file!")
return
end
end
--开始调用上传文件脚本
ngx.log(ngx.ERR, "\n")
ngx.log(ngx.ERR, "------------------------------------------------------------------")
ngx.log(ngx.ERR, "-------------start execute upload_package lua script--------------")
ngx.log(ngx.ERR, "------------------------------------------------------------------")
local request_method = ngx.var.request_method
if "POST" == request_method then
get_fileUploadPath()
upload()
end
ngx.log(ngx.ERR, "------------------------------------------------------------------")
ngx.log(ngx.ERR, "-------------end execute upload_package lua script--------------")
ngx.log(ngx.ERR, "------------------------------------------------------------------")
ngx.log(ngx.ERR, "\n")
二丶 lualib/resty/upload.lua 源码
通过阅读 lualib/resty/upload.lua 源码,该模块在解析文件上传请求的过程中,主要采用了简单的类似有限状态机的算法来实现的,在不同的状态由相应的 handler 进行处理,支持的状态包括如下状态:
1 STATE_BEGIN(1)
初始状态,是在 upload:new 实例化的时候初始化的,如下源码(只保留了主干):
function _M.new(self, chunk_size, max_line_size)
local boundary = get_boundary()
local sock, err = req_socket()
local read2boundary, err = sock:receiveuntil("--" .. boundary)
local read_line, err = sock:receiveuntil("\r\n")
return setmetatable({
sock = sock,
size = chunk_size or CHUNK_SIZE,
line_size = max_line_size or MAX_LINE_SIZE,
read2boundary = read2boundary,
read_line = read_line,
boundary = boundary,
state = STATE_BEGIN
}, mt)
end
2 STATE_READING_HEADER(2)
开始解析 HTTP 头部消息,一般在这个阶段主要用于解析出其中的文件名, boundary 等信息;相应的 handler 为 read_header;
3 STATE_READING_BODY(3)
开始解析 HTTP 包体,这个阶段就是读取文件内容;
4 STATE_EOF(4)
如果文件全部都解析和读取完后,则进入该状态,一般这个阶段表示文件都已经读取完;
这 4 个状态分别的 handler 为:
state_handlers = {
read_preamble,
read_header,
read_body_part,
eof
}
这里要注意的是不同阶段/状态下 read 返回的结构不同,如在 STATE_READING_HEADER 下返回的结构是 “header”,{ key, value, line}
上传的文件会被保存在本地的路径 /home/steven/openresty/nginx/upload/ 下
三丶配置nginx.conf
添加 location /upfile 用于接收文件上传的 action,并通过 myupload.lua 来解析文件上传内容后保存至本地文件系统,如下:
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 19080;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
location /upfile {
content_by_lua_file lua/myupload.lua;
}
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
评论暂时关闭