Saltar a contenido

upload: Lector y analizador de streaming para la carga de archivos http basado en nginx-module-lua cosocket

Instalación

Si no has configurado la suscripción al repositorio RPM, regístrate. Luego, puedes proceder con los siguientes pasos.

CentOS/RHEL 7 o 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

Para usar esta biblioteca Lua con NGINX, asegúrate de que nginx-module-lua esté instalado.

Este documento describe lua-resty-upload v0.11 lanzado el 19 de enero de 2023.


Esta biblioteca Lua es una API de carga de archivos por streaming para el módulo ngx_lua de nginx:

http://wiki.nginx.org/HttpLuaModule

El tipo MIME multipart/form-data es compatible.

La API de esta biblioteca simplemente devuelve tokens uno por uno. El usuario solo necesita llamar al método read repetidamente hasta que se devuelva un tipo de token nil. Para cada token devuelto del método read, solo verifica el primer valor de retorno para el tipo de token actual. El tipo de token puede ser header, body y part end. Cada campo de formulario multipart/form-data analizado consiste en varios tokens header que contienen cada encabezado de campo, varios tokens body que contienen cada fragmento de datos del cuerpo y una bandera part end que indica el final del campo.

Así es como funciona la lectura por streaming. Incluso para gigabytes de datos de archivo de entrada, la memoria utilizada en el espacio Lua puede ser pequeña y constante, siempre que el usuario no acumule los fragmentos de datos de entrada por sí misma.

Esta biblioteca Lua aprovecha la API de cosocket de ngx_lua, que garantiza un comportamiento 100% no bloqueante.

Ten en cuenta que se requiere al menos ngx_lua 0.7.9 o OpenResty 1.2.4.14.

Sinopsis

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

                local chunk_size = 5 -- debe establecerse en 4096 o 8192
                                     -- para configuraciones del mundo real

                local form, err = upload:new(chunk_size)
                if not form then
                    ngx.log(ngx.ERR, "falló al crear la carga: ", err)
                    ngx.exit(500)
                end

                form:set_timeout(1000) -- 1 seg

                while true do
                    local typ, res, err = form:read()
                    if not typ then
                        ngx.say("falló al leer: ", err)
                        return
                    end

                    ngx.say("leído: ", cjson.encode({typ, res}))

                    if typ == "eof" then
                        break
                    end
                end

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

Una salida típica de la ubicación /test definida arriba es:

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

Puedes usar la biblioteca lua-resty-string para calcular el resumen SHA-1 y MD5 de los datos del archivo de manera incremental. Aquí hay un ejemplo:

    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("falló al leer: ", 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("falló al abrir el archivo ", 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
            -- no hacer nada
        end
    end

Si deseas calcular sumas MD5 para los archivos subidos, solo usa el módulo resty.md5 proporcionado por la biblioteca lua-resty-string. Tiene una API similar a resty.sha1.

Para la carga de archivos grandes, es importante no almacenar todos los datos en memoria. Es decir, nunca debes acumular fragmentos de datos ni en una gran cadena Lua ni en una gran tabla Lua. Debes escribir el fragmento de datos en archivos lo antes posible y desechar el fragmento de datos inmediatamente (para permitir que el GC de Lua lo libere).

En lugar de escribir el fragmento de datos en archivos (como se muestra en el ejemplo anterior), también puedes escribir los fragmentos de datos en conexiones de cosocket ascendentes si no deseas guardar los datos en sistemas de archivos locales.

Uso

local upload = require "resty.upload"
local form, err = upload:new(self, chunk_size, max_line_size, preserve_body)
chunk_size tiene un valor predeterminado de 4096. Es el tamaño utilizado para leer datos del socket.

max_line_size tiene un valor predeterminado de 512. Es el límite de tamaño para leer el encabezado del cuerpo fragmentado.

Por defecto, lua-resty-upload consumirá el cuerpo de la solicitud. Para el modo proxy, esto significa que el upstream no verá el cuerpo. Cuando preserve_body se establece en true, el cuerpo de la solicitud se conservará. Ten en cuenta que esta opción no es gratuita. Cuando se habilita, duplicará el uso de memoria de resty.upload.

Véase también

GitHub

Puedes encontrar consejos de configuración adicionales y documentación para este módulo en el repositorio de GitHub para nginx-module-upload.