upload: Leitor e parser de streaming para upload de arquivos http baseado no nginx-module-lua cosocket
Instalação
Se você ainda não configurou a assinatura do repositório RPM, inscreva-se. Depois, você pode prosseguir com os seguintes passos.
CentOS/RHEL 7 ou 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 com o NGINX, certifique-se de que o nginx-module-lua está instalado.
Este documento descreve o lua-resty-upload v0.11 lançado em 19 de janeiro de 2023.
Esta biblioteca Lua é uma API de upload de arquivos em streaming para o módulo ngx_lua do nginx:
http://wiki.nginx.org/HttpLuaModule
O tipo MIME multipart/form-data é suportado.
A API desta biblioteca retorna tokens um por um. O usuário só precisa chamar o método read repetidamente até que um tipo de token nil seja retornado. Para cada token retornado do método read, basta verificar o primeiro valor de retorno para o tipo de token atual. O tipo de token pode ser header, body e part end. Cada campo de formulário multipart/form-data analisado consiste em vários tokens header contendo cada cabeçalho de campo, vários tokens body contendo cada pedaço de dados do corpo e uma flag part end indicando o fim do campo.
É assim que a leitura em streaming funciona. Mesmo para gigabytes de dados de arquivo de entrada, a memória usada no ambiente Lua pode ser pequena e constante, desde que o usuário não acumule os pedaços de dados de entrada por conta própria.
Esta biblioteca Lua aproveita a API de cosocket do ngx_lua, que garante um comportamento 100% não bloqueante.
Note que pelo menos ngx_lua 0.7.9 ou OpenResty 1.2.4.14 é necessário.
Sinopse
server {
location /test {
content_by_lua '
local upload = require "resty.upload"
local cjson = require "cjson"
local chunk_size = 5 -- deve ser definido como 4096 ou 8192
-- para configurações do mundo real
local form, err = upload:new(chunk_size)
if not form then
ngx.log(ngx.ERR, "falha ao criar upload: ", 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("falha ao ler: ", err)
return
end
ngx.say("lido: ", cjson.encode({typ, res}))
if typ == "eof" then
break
end
end
local typ, res, err = form:read()
ngx.say("lido: ", cjson.encode({typ, res}))
';
}
}
Uma saída típica da localização /test definida acima é:
lido: ["header",["Content-Disposition","form-data; name=\"file1\"; filename=\"a.txt\"","Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\""]]
lido: ["header",["Content-Type","text\/plain","Content-Type: text\/plain"]]
lido: ["body","Hello"]
lido: ["body",", wor"]
lido: ["body","ld"]
lido: ["part_end"]
lido: ["header",["Content-Disposition","form-data; name=\"test\"","Content-Disposition: form-data; name=\"test\""]]
lido: ["body","value"]
lido: ["body","\r\n"]
lido: ["part_end"]
lido: ["eof"]
lido: ["eof"]
Você pode usar a biblioteca lua-resty-string para calcular o digest SHA-1 e MD5 dos dados do arquivo de forma incremental. Aqui está um exemplo:
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("falha ao ler: ", 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("falha ao abrir arquivo ", 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
-- não faz nada
end
end
Se você quiser calcular somas MD5 para os arquivos enviados, basta usar o módulo resty.md5 fornecido pela biblioteca lua-resty-string. Ele possui uma API semelhante à do resty.sha1.
Para upload de arquivos grandes, é importante não armazenar todos os dados na memória. Ou seja, você nunca deve acumular pedaços de dados nem em uma grande string Lua nem em uma grande tabela Lua. Você deve escrever o pedaço de dados em arquivos o mais rápido possível e descartar o pedaço de dados imediatamente (para permitir que o GC do Lua o libere).
Em vez de escrever o pedaço de dados em arquivos (como mostrado no exemplo acima), você também pode escrever os pedaços de dados em conexões de cosocket upstream se não quiser salvar os dados em sistemas de arquivos locais.
Uso
local upload = require "resty.upload"
local form, err = upload:new(self, chunk_size, max_line_size, preserve_body)
chunk_size padrão é 4096. É o tamanho usado para ler dados do socket.
max_line_size padrão é 512. É o limite de tamanho para ler o cabeçalho do corpo em pedaços.
Por padrão, lua-resty-upload consumirá o corpo da requisição. Para o modo proxy, isso significa que o upstream não verá o corpo. Quando preserve_body é definido como true, o corpo da requisição será preservado. Note que esta opção não é gratuita. Quando ativada, dobrará o uso de memória do resty.upload.
Veja Também
- o módulo ngx_lua
- a biblioteca lua-resty-string
- a biblioteca lua-resty-memcached
- a biblioteca lua-resty-redis
- a biblioteca lua-resty-mysql
GitHub
Você pode encontrar dicas adicionais de configuração e documentação para este módulo no repositório GitHub para nginx-module-upload.