Lua чтение фрагментированного тела запроса, если указан заголовок - PullRequest
0 голосов
/ 29 мая 2020

мы используем Nginx + Lua и хотим поддерживать частичную загрузку согласно этому обходному пути, который обычно работает. Мой вопрос в том, как я могу обрабатывать запрос на загрузку как обычно - работать с заголовками, телом, eof:

                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 sec

                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

и только когда я разбил заголовок загрузки -H "Transfer-Encoding: chunked", используйте этот chunk скрипт.

Извините, если это что-то очевидное, но после пары дней поиска в Google я не вижу ни одного примера. Но я предлагаю:

# read headers
ngx.req.get_headers()

#read body:
ngx.req.get_body_data()

, и тогда мне не нужно form:read() и перебирать массив форм до eof. Оцените любые ссылки, примеры.

пример curl:

curl -X PUT localhost:8080/test -F file=@./myfile -H "Transfer-Encoding: chunked"

Ответы [ 2 ]

1 голос
/ 01 июня 2020

К сожалению, ngx.req.socket (https://github.com/openresty/lua-nginx-module#ngxreqsocket), который используется lua-resty-upload под капотом, в настоящий момент не обрабатывает кодировки тела. То есть, когда вы читаете из объекта сокета, вы получите тело запроса как есть , поэтому вам нужно декодировать его самостоятельно. lua-resty-upload этого не делает, он ожидает тело данных в простой форме без дополнительной кодировки. См. https://github.com/openresty/lua-resty-upload/issues/32#issuecomment -266301684 для дальнейшего объяснения.

Как уже упоминалось по ссылке выше, вы можете использовать ngx.req.read_body / ngx.re.get_body_data, которые поддерживаются «nginx» встроенный читатель тела запроса с поддержкой кодирования фрагментов ». Метод ngx.re.get_body_data возвращает уже декодированное тело. Вы можете передать тело некоторому синтаксическому анализатору данных формы, который принимает тело как байтовую строку, а не читает его из сокета (как это делает lua-resty-upload). Например, можно использовать lua-resty-multipart-parser: https://github.com/agentzh/lua-resty-multipart-parser

Есть существенный недостаток - тело запроса нужно сразу прочитать в строку Lua, то есть все тело запроса хранится в памяти как Lua строковый объект.

Теоретически это можно исправить. Мы можем изменить lua-resty-upload, чтобы принимать объект, подобный сокету, вместо жестко запрограммированного (https://github.com/openresty/lua-resty-upload/blob/v0.10/lib/resty/upload.lua#L60) и написать какой-то буфер, который лениво считывает байты из итератора и предоставляет интерфейс, подобный сокету. Может, потом попробую.


Вот пример использования обеих библиотек. Он делает именно то, что вы просили (но помните, что он читает все тело в строку, если тело запроса chunked - закодировано).

# nginx.conf
http {
    server {
        listen 8888;
        location = /upload {
            content_by_lua_block {
                require('upload').handler()
            }
        }
    }
}
-- upload.lua
local upload = require('resty.upload')
local multipart_parser = require('resty.multipart.parser')

local get_header = function(headers, name)
    local header = headers[name]
    if not header then
        return nil
    end
    if type(header) == 'table' then
        return header[1]
    end
    return header
end

local handler = function()
    -- return 405 if HTTP verb is not POST
    if ngx.req.get_method() ~= 'POST' then
        return ngx.exit(ngx.HTTP_NOT_ALLOWED)
    end
    local headers = ngx.req.get_headers()
    local content_type = get_header(headers, 'content-type')
    -- return 400 if the body is not a formdata
    if not content_type or not string.find(content_type, '^multipart/form%-data') then
        return ngx.exit(ngx.HTTP_BAD_REQUEST)
    end
    local transfer_encoding = get_header(headers, 'transfer-encoding')
    if transfer_encoding == 'chunked' then
        -- parse form using `lua-resty-multipart-parser`
        ngx.say('*** chunked')
        -- read the body, chunked encoding will be decoded by nginx
        ngx.req.read_body()
        local body = ngx.req.get_body_data()
        if not body then
            local filename = ngx.req.get_body_file()
            if not filename then
                return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
            end
            -- WARNING
            -- don't use this code in production, file I/O is blocking,
            -- you are going to block nginx event loop at this point!
            local fd = io.open(filename, 'rb')
            if not fd then
                return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
            end
            body = fd:read('*a')
        end
        local parser = multipart_parser.new(body, content_type)
        while true do
            local part = parser:parse_part()
            if not part then
                break
            end
            ngx.say('>>> ', part)
        end
    else
        -- parse form using `lua-resty-upload` (in a streaming fashion)
        ngx.say('*** not chunked')
        local chunk_size = 8 -- for demo purposes only, use 4096 or 8192
        local form = upload:new(chunk_size)
        while true do
            local typ, res = form:read()
            if typ == 'eof' then
                break
            elseif typ == 'body' then
                ngx.say('>>> ', res)
            end
        end
    end
end

return {
    handler = handler
}
$ curl -X POST localhost:8888/upload -F file='binary file content'                             

*** not chunked
>>> binary f
>>> ile cont
>>> ent

Как видите, Тело читается и обрабатывается чанк за чанком.

$ curl -X POST localhost:8888/upload -F file='binary file content' -H transfer-encoding:chunked

*** chunked
>>> binary file content

Здесь, наоборот, тело обрабатывается сразу.

0 голосов
/ 11 июня 2020
• 1000 от итератора и предоставляет интерфейс, подобный сокету.

Готово. Я создал новую библиотеку под названием lua -buffet . Его можно использовать для создания объектов, которые действуют как обычные объекты ngx_lua cosocket. Еще не все методы сокета реализованы, но прямо сейчас у него есть все методы, необходимые для lua-resty-upload. Он еще не выпущен, но я собираюсь выпустить первую версию в ближайшее время.

Я также форкнул и модифицировал lua-resty-upload, чтобы добавить параметр сокета. Позже я создам PR для вышестоящего репозитория.

Вот пример того, как обрабатывать данные в вашем случае: https://github.com/un-def/lua-buffet/tree/master/examples/resty-chunked-formdata

...