首页 > 系统相关 >nginx+lua实现文件上传功能

nginx+lua实现文件上传功能

时间:2024-10-18 11:14:08浏览次数:1  
标签:end nil err nginx 上传 self lua return local

需要用到nginx实现文件上传,刚好手里面的版本支持lua,下面是完整实现:

首先是nginx的配置如下:注意$home_path设置的是上传文件的保存目录

location /uploadFile 
{
    set $home_path "/root/up2";
     content_by_lua_file conf/upfile.lua;
}

接着在web根目录放入Html文件,命名成myupload.html

<html>
<head>
<title>File Upload to nginx</title>
</head>

<body>
<form action="uploadFile" method="post" enctype="multipart/form-data">
<label for="testFileName">select file: </label>
<input type="file" name="testFileName" />
<input type="submit" name="upload" value="Upload" />
</form>
</body>
</html>

接着在conf目录放入下面的两个文件:

 upload.lua

-- Copyright (C) Yichun Zhang (agentzh)


-- local sub = string.sub
local req_socket = ngx.req.socket
local match = string.match
local setmetatable = setmetatable
local type = type
local ngx_var = ngx.var
local ngx_init_body = ngx.req.init_body
local ngx_finish_body = ngx.req.finish_body
local ngx_append_body = ngx.req.append_body
-- local print = print


local _M = { _VERSION = '0.11' }


local CHUNK_SIZE = 4096
local MAX_LINE_SIZE = 512

local STATE_BEGIN = 1
local STATE_READING_HEADER = 2
local STATE_READING_BODY = 3
local STATE_EOF = 4

local mt = { __index = _M }

local state_handlers

local function wrapped_receiveuntil(self, until_str)
    local iter, err_outer = self:old_receiveuntil(until_str)
    if iter == nil then
        ngx_finish_body()
    end

    local function wrapped(size)
        local ret, err = iter(size)
        if ret then
            ngx_append_body(ret)
        end

        -- non-nil ret for call with no size or successful size call and nil ret
        if (not size and ret) or (size and not ret and not err) then
            ngx_append_body(until_str)
        end
        return ret, err
    end

    return wrapped, err_outer
end


local function wrapped_receive(self, arg)
    local ret, err, partial = self:old_receive(arg)
    if ret then
        ngx_append_body(ret)

    elseif partial then
        ngx_append_body(partial)
    end

    if ret == nil then
        ngx_finish_body()
    end

    return ret, err
end


local function req_socket_body_collector(sock)
    sock.old_receiveuntil = sock.receiveuntil
    sock.old_receive = sock.receive
    sock.receiveuntil = wrapped_receiveuntil
    sock.receive = wrapped_receive
end


local function get_boundary()
    local header = ngx_var.content_type
    if not header then
        return nil
    end

    if type(header) == "table" then
        header = header[1]
    end

    local m = match(header, ";%s*boundary=\"([^\"]+)\"")
    if m then
        return m
    end

    return match(header, ";%s*boundary=([^\",;]+)")
end


function _M.new(self, chunk_size, max_line_size, preserve_body)
    local boundary = get_boundary()

    -- print("boundary: ", boundary)

    if not boundary then
        return nil, "no boundary defined in Content-Type"
    end

    -- print('boundary: "', boundary, '"')

    local sock, err = req_socket()
    if not sock then
        return nil, err
    end

    if preserve_body then
        ngx_init_body(chunk_size)
        req_socket_body_collector(sock)
    end

    local read2boundary, err = sock:receiveuntil("--" .. boundary)
    if not read2boundary then
        return nil, err
    end

    local read_line, err = sock:receiveuntil("\r\n")
    if not read_line then
        return nil, err
    end

    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,
        preserve_body = preserve_body
    }, mt)
end


function _M.set_timeout(self, timeout)
    local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end

    return sock:settimeout(timeout)
end


local function discard_line(self)
    local read_line = self.read_line

    local line, err = read_line(self.line_size)
    if not line then
        return nil, err
    end

    local dummy, err = read_line(1)
    if dummy then
        if self.preserve_body then
            ngx_finish_body()
        end

        return nil, "line too long: " .. line .. dummy .. "..."
    end

    if err then
        return nil, err
    end

    return 1
end


local function discard_rest(self)
    local sock = self.sock
    local size = self.size

    while true do
        local dummy, err = sock:receive(size)
        if err and err ~= 'closed' then
            return nil, err
        end

        if not dummy then
            return 1
        end
    end
end


local function read_body_part(self)
    local read2boundary = self.read2boundary

    local chunk, err = read2boundary(self.size)
    if err then
        return nil, nil, err
    end

    if not chunk then
        local sock = self.sock

        local data = sock:receive(2)
        if data == "--" then
            local ok, err = discard_rest(self)
            if not ok then
                return nil, nil, err
            end

            self.state = STATE_EOF
            return "part_end"
        end

        if data ~= "\r\n" then
            local ok, err = discard_line(self)
            if not ok then
                return nil, nil, err
            end
        end

        self.state = STATE_READING_HEADER
        return "part_end"
    end

    return "body", chunk
end


local function read_header(self)
    local read_line = self.read_line

    local line, err = read_line(self.line_size)
    if err then
        return nil, nil, err
    end

    local dummy, err = read_line(1)
    if dummy then
        if self.preserve_body then
            ngx_finish_body()
        end
 
        return nil, nil, "line too long: " .. line .. dummy .. "..."
    end

    if err then
        return nil, nil, err
    end

    -- print("read line: ", line)

    if line == "" then
        -- after the last header
        self.state = STATE_READING_BODY
        return read_body_part(self)
    end

    local key, value = match(line, "([^: \t]+)%s*:%s*(.+)")
    if not key then
        return 'header', line
    end

    return 'header', {key, value, line}
end


local function eof()
    return "eof", nil
end


function _M.read(self)
    -- local size = self.size

    local handler = state_handlers[self.state]
    if handler then
        return handler(self)
    end

    return nil, nil, "bad state: " .. self.state
end


local function read_preamble(self)
    local sock = self.sock
    if not sock then
        return nil, nil, "not initialized"
    end

    local size = self.size
    local read2boundary = self.read2boundary

    while true do
        local preamble = read2boundary(size)
        if not preamble then
            break
        end

        -- discard the preamble data chunk
        -- print("read preamble: ", preamble)
    end

    local ok, err = discard_line(self)
    if not ok then
        return nil, nil, err
    end

    local read2boundary, err = sock:receiveuntil("\r\n--" .. self.boundary)
    if not read2boundary then
        return nil, nil, err
    end

    self.read2boundary = read2boundary

    self.state = STATE_READING_HEADER
    return read_header(self)
end


state_handlers = {
    read_preamble,
    read_header,
    read_body_part,
    eof
}


return _M

接着upfile.lua

local upload = require("conf.upload")

local chunk_size = 4096
local form, err = upload:new(chunk_size)
if not form then
    ngx.log(ngx.ERR, "failed to new upload: ", err)
    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

form:set_timeout(1000)
string.split = function(s, p)
    local rt = {}
    string.gsub(s, "[^" .. p .. "]+", function(w)
        table.insert(rt, w)
    end)
    return rt
end

string.trim = function(s)
    return (s:gsub("^%s*(.-)%s*$", "%1"))
end

function ensure_dir_exists(dirname)  
    local file = io.open(dirname, "r")  
    
    if file then  
        file:close()  
        return true   
    else  
        local ok, err = os.execute("mkdir " .. dirname)  
        if not ok then  
            ngx.log(ngx.ERR, "Failed to create directory: " .. err)  
            return false
        end  
        return true
    end  
end

-- FILE LOCATION
local saveRootPath = "/tmp/"
local fileToSave
local ret_save = false
if ngx.var.home_path~=nil then saveRootPath=ngx.var.home_path.."/" end
ensure_dir_exists(saveRootPath)

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
        -- read fileName from header
        local key = res[1]
        local value = res[2]
        if key == "Content-Disposition" then
            -- form-data; name="testFileName"; filename="testfile.txt"
            local kvlist = string.split(value, ";")
            for _, kv in ipairs(kvlist) do
                local seg = string.trim(kv)
                if seg:find("filename") then
                    local kvfile = string.split(seg, "=")
                    local filename = string.sub(kvfile[2], 2, -2)
                    if filename then
                        fileToSave = io.open(saveRootPath .. filename, "w+")
                        if not fileToSave then
                            ngx.say("failed to open file ", filename)
                            return
                        end
                        break
                    end
                end
            end
        end
    elseif typ == "body" then
        if fileToSave then
            fileToSave:write(res)
        end
    elseif typ == "part_end" then
        if fileToSave then
            fileToSave:close()
            fileToSave = nil
        end
        ret_save = true
    elseif typ == "eof" then
        break
    else
        ngx.log(ngx.INFO, "do other things")
    end
end

if ret_save then
    ngx.say("save file ok")
end

现在就可以用了 

可以用网页上传,也可以用curl命令上传

curl -X POST -F "[email protected]" http://127.0.0.1/uploadFile

 

标签:end,nil,err,nginx,上传,self,lua,return,local
From: https://www.cnblogs.com/yuandaozhe/p/18473861

相关文章

  • nginx+keepalived实现高可用
    在之前的文章已经提到怎么实现keepalived的双机热备,现在我们就结合nginx来实现高可用1、nginx的部署1.1依赖安装在线安装依赖yum-yinstallgccgcc-c++pcrepcre-develzlibzlib-developensslopenssl-devel1.2nginx的安装https://nginx.org/en/download.html下载......
  • Lnmp(mysql分离)(nginx 1.13.6+mysql5.5+php5.3)环境一键搭建
    Lnmp(mysql分离)(nginx 1.13.6+mysql5.5+php5.3)环境一键搭建如果对运维课程感兴趣,可以在b站上、csdn或微信视频号上搜索我的账号:运维实战课程,可以关注我,学习更多免费的运维实战技术视频在192.168.37.128服务器上:(mysql只到makeinstall即可)nginx默认站点html1.上传lnmp的安装......
  • Nginx UI:全新的 Nginx 在线管理平台
    https://www.cnblogs.com/Can-daydayup/p/18468541 思维导航前言工具介绍主要功能支持语言在线演示开源地址程序员常用的工具软件前言Nginx在程序部署中扮演着至关重要的角色,其高性能、高安全性、易于配置和管理的特点,使得它成为现代Web应用部署中不可或缺的一部......
  • NGINX 的 Event Loop
    NGINX的EventLoop讲解NGINX的事件驱动架构基于EventLoop,它使得NGINX能够高效地处理大量并发连接,而不必为每个请求启动一个独立的线程或进程。通过EventLoop,NGINX采用非阻塞I/O模型,这样即使处理大量连接,也能保持较低的资源消耗。为什么NGINX使用EventLoo......
  • DAY53WEB 攻防-XSS 跨站&SVG&PDF&Flash&MXSS&UXSS&配合上传&文件添加脚本
    知识点:1、XSS跨站-MXSS&UXSS2、XSS跨站-SVG制作&配合上传3、XSS跨站-PDF制作&配合上传4、XSS跨站-SWF制作&反编译&上传XSS分类:https://www.fooying.com/the-art-of-xss-1-introduction/(失效了)MXSS现在基本上见不到UXSS:UniversalCross-SiteScripting针对浏览器的漏......
  • CentOS 7 下 yum 安装和配置 Nginx
    CentOS7下yum安装和配置Nginx 前言Nginx(enginex)是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。。本例演示CentOS7下安装和配置Nginx的基本步骤。环境说明CentOS7(MinimalInstall)$cat/etc/redhat-releaseCentOSLinuxrelea......
  • 强大灵活的文件上传库:FilePond 详解hD
    文件上传是Web开发中常见的功能,尤其是对于图片、视频、文档等大文件的处理,如何既保证用户体验,又兼顾安全和性能,是每位开发者关心的问题。在这样的背景下,FilePond作为一款灵活强大的文件上传库,逐渐在前端开发者中脱颖而出。FilePond提供了优雅的用户界面和全面的功能,同时兼容多......
  • Lua脚本的原子性
    Lua脚本之所以被认为是原子性的,主要源于Redis的内部实现机制和Lua脚本的执行方式。以下是对Lua脚本原子性的详细解释:一、Redis的单线程模型Redis是一个基于内存、可基于Key-Value等多种数据结构的存储系统,它使用单线程模型来处理客户端的请求。这意味着在任何给定的时间点......
  • 关于 Ant Design Vue框架中 <a-upload> beforeUpload 上传文件校验之后,返回false 还能上
    现在在(jinsai)外包的时候,使用的是jeecg-boot项目,后端上传使用的是自带的JImageUpload,里面上传是a-upload组件,就是AntDesignVue框架,说实话,挺难用的。在JImageUpload组件中:直接上代码:点击查看代码//上传前beforeUpload:function(file){this.uploadGo......
  • Nginx 配置文件
    Nginx配置文件配置文件参考'''confupstreamtapi{server127.0.0.1:8000;}server{listen443ssl;server_nametapi.theemogen.com;ssl_certificate/etc/letsencrypt/live/hostname.com/fullchain.pem;ssl_certificate_key/etc/letsencrypt/live/......