基于 lua-resty-upload 实现简单的文件上传服务

来源:互联网 发布:英敏特信息咨询 知乎 编辑:程序博客网 时间:2024/06/06 01:13

文章引用:

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 pathend--==========================================-- 文件上传--==========================================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_methodif "POST" == request_method then     get_fileUploadPath()    upload()endngx.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;        }    }}
原创粉丝点击