Перейти к содержанию

upload: Потоковый читатель и парсер для http загрузки файлов на основе nginx-module-lua cosocket

Установка

Если вы еще не настроили подписку на репозиторий 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

Чтобы использовать эту библиотеку Lua с NGINX, убедитесь, что установлен nginx-module-lua.

Этот документ описывает lua-resty-upload v0.11, выпущенную 19 января 2023 года.


Эта библиотека Lua является API для потоковой загрузки файлов для модуля ngx_lua nginx:

http://wiki.nginx.org/HttpLuaModule

Поддерживается MIME-тип multipart/form-data.

API этой библиотеки просто возвращает токены один за другим. Пользователю нужно просто повторно вызывать метод read, пока не будет возвращен токен типа nil. Для каждого токена, возвращенного из метода read, просто проверьте первое возвращаемое значение для текущего типа токена. Тип токена может быть header, body и part end. Каждое поле формы multipart/form-data, которое разбирается, состоит из нескольких токенов header, содержащих заголовок каждого поля, нескольких токенов body, содержащих каждую часть данных тела, и флага part end, указывающего на конец поля.

Вот как работает потоковое чтение. Даже для гигабайтов входных данных файла, память, используемая в Lua, может быть небольшой и постоянной, если пользователь не накапливает части входных данных самостоятельно.

Эта библиотека Lua использует API cosocket ngx_lua, который обеспечивает 100% неблокирующее поведение.

Обратите внимание, что требуется как минимум ngx_lua 0.7.9 или OpenResty 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, "не удалось создать загрузку: ", 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("не удалось прочитать: ", err)
                        return
                    end

                    ngx.say("прочитано: ", cjson.encode({typ, res}))

                    if typ == "eof" then
                        break
                    end
                end

                local typ, res, err = form:read()
                ngx.say("прочитано: ", cjson.encode({typ, res}))
            ';
        }
    }

Типичный вывод местоположения /test, определенного выше:

прочитано: ["header",["Content-Disposition","form-data; name=\"file1\"; filename=\"a.txt\"","Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\""]]
прочитано: ["header",["Content-Type","text\/plain","Content-Type: text\/plain"]]
прочитано: ["body","Hello"]
прочитано: ["body",", wor"]
прочитано: ["body","ld"]
прочитано: ["part_end"]
прочитано: ["header",["Content-Disposition","form-data; name=\"test\"","Content-Disposition: form-data; name=\"test\""]]
прочитано: ["body","value"]
прочитано: ["body","\r\n"]
прочитано: ["part_end"]
прочитано: ["eof"]
прочитано: ["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("не удалось прочитать: ", 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("не удалось открыть файл ", 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
            -- ничего не делать
        end
    end

Если вы хотите вычислить MD5 суммы для загруженных файлов, просто используйте модуль resty.md5, поставляемый с библиотекой lua-resty-string. У него аналогичный API, как у resty.sha1.

Для загрузки больших файлов важно не буферизовать все данные в памяти. То есть, вы никогда не должны накапливать части данных либо в огромной строке Lua, либо в огромной таблице Lua. Вы должны записывать части данных в файлы как можно скорее и немедленно выбрасывать часть данных (чтобы позволить сборщику мусора Lua освободить их).

Вместо записи части данных в файлы (как показано в примере выше), вы также можете записывать части данных в соединения cosocket upstream, если не хотите сохранять данные на локальных файловых системах.

Использование

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 будет потреблять тело запроса. Для режима прокси это означает, что upstream не увидит тело. Когда preserve_body установлен в true, тело запроса будет сохранено. Обратите внимание, что эта опция не бесплатна. При включении она удваивает использование памяти resty.upload.

См. также

GitHub

Вы можете найти дополнительные советы по конфигурации и документацию для этого модуля в репозитории GitHub для nginx-module-upload.