跳转至

upload: 基于 nginx-module-lua cosocket 的 HTTP 文件上传流式读取器和解析器

安装

如果您尚未设置 RPM 仓库订阅,请 注册。然后您可以继续以下步骤。

CentOS/RHEL 7 或 Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-upload

CentOS/RHEL 8+、Fedora Linux、Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-upload

要在 NGINX 中使用此 Lua 库,请确保已安装 nginx-module-lua

本文档描述了 lua-resty-upload v0.11,该版本于 2023 年 1 月 19 日发布。


此 Lua 库是 ngx_lua nginx 模块的流式文件上传 API:

http://wiki.nginx.org/HttpLuaModule

支持 multipart/form-data MIME 类型。

该库的 API 逐个返回令牌。用户只需重复调用 read 方法,直到返回 nil 令牌类型。对于从 read 方法返回的每个令牌,只需检查当前令牌类型的第一个返回值。令牌类型可以是 headerbodypart end。解析的每个 multipart/form-data 表单字段由几个 header 令牌组成,这些令牌持有每个字段的头部,几个 body 令牌持有每个主体数据块,以及一个 part end 标志指示字段结束。

这就是流式读取的工作原理。即使是千兆字节的文件数据输入,只要用户不自己累积输入数据块,Lua 端使用的内存也可以保持小且恒定。

此 Lua 库利用了 ngx_lua 的 cosocket API,确保 100% 非阻塞行为。

请注意,至少需要 ngx_lua 0.7.9OpenResty 1.2.4.14

概述

    server {
        location /test {
            content_by_lua '
                local upload = require "resty.upload"
                local cjson = require "cjson"

                local chunk_size = 5 -- 应设置为 4096 或 8192
                                     -- 以适应实际环境

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

                form:set_timeout(1000) -- 1 秒

                while true do
                    local typ, res, err = form:read()
                    if not typ then
                        ngx.say("failed to read: ", err)
                        return
                    end

                    ngx.say("read: ", cjson.encode({typ, res}))

                    if typ == "eof" then
                        break
                    end
                end

                local typ, res, err = form:read()
                ngx.say("read: ", cjson.encode({typ, res}))
            ';
        }
    }

上面定义的 /test 位置的典型输出是:

read: ["header",["Content-Disposition","form-data; name=\"file1\"; filename=\"a.txt\"","Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\""]]
read: ["header",["Content-Type","text\/plain","Content-Type: text\/plain"]]
read: ["body","Hello"]
read: ["body",", wor"]
read: ["body","ld"]
read: ["part_end"]
read: ["header",["Content-Disposition","form-data; name=\"test\"","Content-Disposition: form-data; name=\"test\""]]
read: ["body","value"]
read: ["body","\r\n"]
read: ["part_end"]
read: ["eof"]
read: ["eof"]

您可以使用 lua-resty-string 库逐步计算文件数据的 SHA-1 和 MD5 摘要。以下是一个示例:

    local resty_sha1 = require "resty.sha1"
    local upload = require "resty.upload"

    local chunk_size = 4096
    local form = upload:new(chunk_size)
    local sha1 = resty_sha1:new()
    local file
    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
            local file_name = my_get_file_name(res)
            if file_name then
                file = io.open(file_name, "w+")
                if not file then
                    ngx.say("failed to open file ", file_name)
                    return
                end
            end

         elseif typ == "body" then
            if file then
                file:write(res)
                sha1:update(res)
            end

        elseif typ == "part_end" then
            file:close()
            file = nil
            local sha1_sum = sha1:final()
            sha1:reset()
            my_save_sha1_sum(sha1_sum)

        elseif typ == "eof" then
            break

        else
            -- do nothing
        end
    end

如果您想计算上传文件的 MD5 和,您只需使用 lua-resty-string 库中提供的 resty.md5 模块。它的 API 与 resty.sha1 类似。

对于大文件上传,重要的是不要将所有数据缓冲在内存中。也就是说,您绝不能在一个巨大的 Lua 字符串或一个巨大的 Lua 表中累积数据块。您必须尽快将数据块写入文件,并立即丢弃数据块(以便让 Lua GC 释放它)。

如果您不想在本地文件系统上保存数据,可以将数据块写入上游 cosocket 连接,而不是将数据块写入文件(如上面的示例所示)。

用法

local upload = require "resty.upload"
local form, err = upload:new(self, chunk_size, max_line_size, preserve_body)
chunk_size 默认为 4096。这是用于从套接字读取数据的大小。

max_line_size 默认为 512。这是读取分块主体头的大小限制。

默认情况下,lua-resty-upload 将消耗请求主体。对于代理模式,这意味着上游将看不到主体。当 preserve_body 设置为 true 时,请求主体将被保留。请注意,此选项并不是免费的。启用时,它将使 resty.upload 的内存使用量加倍。

另见

GitHub

您可以在 nginx-module-upload 的 GitHub 仓库 中找到此模块的其他配置提示和文档。