基于 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;
        }
    }
}

相关内容

    暂无相关文章