Асинхронные HTTP-запросы, основанные на неблокирующих сокетах, реализующихся библиотекой copas. Запросы выполняются в том же потоке, благодаря чему не подвержены никаким проблемам многопоточности и проще в использовании.
Для следующей реализации требуются только библиотеки copas, LuaSocket и опционально LuaSec для HTTPS запросов.
Аргументы у функции такие же, как у функции request из модуля socket.http (и ssl.https), кроме дополнительного последнего параметра.
Параметры функции
request - URL или параметры запроса
body - тело запроса для метода POST
handler - функция-обработчик запроса. Если этот параметр задан, то запрос выполнится параллельно, не блокируя луа-поток мунлоадера. Если не задан, то луа-поток будет приостановлен до получения ответа, значения которого будут возвращены вызовом функции - такой запрос можно использовать только изнутри луа-потока. Системный поток не блокируется в обоих случаях
Параметры функции handler
первый - содержимое ответа либо единица, в зависимости от типа первого параметра функции
второй - код ответа
третий - заголовок ответа
четвертый - текст статуса ответа
* параметры при ошибке
первый - nil
второй - текст ошибки
Возвращаемые значения
если handler указан, то функция возвращает сопрограмму, созданную вызовом copas.addthread, если не указан, то те же параметры, которые передаются в handler
Та же реализация, только с применением библиотеки lua-requests. Она удобнее, но тащит за собой много других библиотек. Параметры функции те же, что у функции request из модуля requests (кроме последнего).
Пример:
Другие решения
Решение с системной многопоточностью, применяются библиотеки lua-requests и effil:
https://blast.hk/threads/20532/post-256096
Неактуальное решение с системной многопоточностью, применяются библиотеки lua-requests и Lanes:
Для следующей реализации требуются только библиотеки copas, LuaSocket и опционально LuaSec для HTTPS запросов.
Lua:
local copas = require 'copas'
local http = require 'copas.http'
function httpRequest(request, body, handler) -- copas.http
-- start polling task
if not copas.running then
copas.running = true
lua_thread.create(function()
wait(0)
while not copas.finished() do
local ok, err = copas.step(0)
if ok == nil then error(err) end
wait(0)
end
copas.running = false
end)
end
-- do request
if handler then
return copas.addthread(function(r, b, h)
copas.setErrorHandler(function(err) h(nil, err) end)
h(http.request(r, b))
end, request, body, handler)
else
local results
local thread = copas.addthread(function(r, b)
copas.setErrorHandler(function(err) results = {nil, err} end)
results = table.pack(http.request(r, b))
end, request, body)
while coroutine.status(thread) ~= 'dead' do wait(0) end
return table.unpack(results)
end
end
Параметры функции
request - URL или параметры запроса
body - тело запроса для метода POST
handler - функция-обработчик запроса. Если этот параметр задан, то запрос выполнится параллельно, не блокируя луа-поток мунлоадера. Если не задан, то луа-поток будет приостановлен до получения ответа, значения которого будут возвращены вызовом функции - такой запрос можно использовать только изнутри луа-потока. Системный поток не блокируется в обоих случаях
Параметры функции handler
первый - содержимое ответа либо единица, в зависимости от типа первого параметра функции
второй - код ответа
третий - заголовок ответа
четвертый - текст статуса ответа
* параметры при ошибке
первый - nil
второй - текст ошибки
Возвращаемые значения
если handler указан, то функция возвращает сопрограмму, созданную вызовом copas.addthread, если не указан, то те же параметры, которые передаются в handler
Lua:
local list = {
"http://www.google.com",
"http://www.microsoft.com",
"http://www.apple.com",
"https://www.facebook.com",
"https://www.yahoo.com",
}
-- параллельные запросы, обработаются одновременно
print('parallel')
for i, url in ipairs(list) do
print('request', url)
httpRequest(url, nil, function(response, code, headers, status)
if response then
print(url, 'OK', status)
else
print(url, 'Error', code)
end
end)
end
-- последовательные запросы, обработаются друг за другом
print('sequential')
for i, url in ipairs(list) do
print('request', url)
local response, code, headers, status = httpRequest(url)
if response then
print(url, 'OK', status)
else
print(url, 'Error', code)
end
end
Та же реализация, только с применением библиотеки lua-requests. Она удобнее, но тащит за собой много других библиотек. Параметры функции те же, что у функции request из модуля requests (кроме последнего).
Lua:
local copas = require 'copas'
local http = require 'copas.http'
local requests = require 'requests'
requests.http_socket, requests.https_socket = http, http
function httpRequest(method, request, args, handler) -- lua-requests
-- start polling task
if not copas.running then
copas.running = true
lua_thread.create(function()
wait(0)
while not copas.finished() do
local ok, err = copas.step(0)
if ok == nil then error(err) end
wait(0)
end
copas.running = false
end)
end
-- do request
if handler then
return copas.addthread(function(m, r, a, h)
copas.setErrorHandler(function(err) h(nil, err) end)
h(requests.request(m, r, a))
end, method, request, args, handler)
else
local results
local thread = copas.addthread(function(m, r, a)
copas.setErrorHandler(function(err) results = {nil, err} end)
results = table.pack(requests.request(m, r, a))
end, method, request, args)
while coroutine.status(thread) ~= 'dead' do wait(0) end
return table.unpack(results)
end
end
Lua:
local response, err = httpRequest('POST', {'http://httpbin.org/post', data='random data'})
if err then error(err) end
local json_data = response.json()
print(json_data.data)
Другие решения
Решение с системной многопоточностью, применяются библиотеки lua-requests и effil:
https://blast.hk/threads/20532/post-256096
Неактуальное решение с системной многопоточностью, применяются библиотеки lua-requests и Lanes:
Применение с LuaJIT 2.1-beta (и MoonLoader .025+) противопоказано! (подробности)
Простой вариант, годится для редких или параллельных запросов, запускает отдельный поток при каждом вызове:
Пример вызова:
Более сложный, но походит для частых и последовательных запросов, запускает один поток при первом запросе, который остаётся активен до завершения работы скрипта:
Вызов точно такой же, как и в первом примере.
Стоит сразу добавить, что обе реализации достаточно примитивны, в них обрабатываются не все случаи возникновения ошибок и есть что улучшить. Пользоваться можно, но по большей части они созданы послужить основой для более продвинутых реализаций и помочь в понимании устройства многопоточности в луа.
Простой вариант, годится для редких или параллельных запросов, запускает отдельный поток при каждом вызове:
Lua:
local lanes = require('lanes').configure() -- в начало скрипта, конечно же
function async_http_request(method, url, args, resolve, reject)
local request_lane = lanes.gen('*', {package = {path = package.path, cpath = package.cpath}}, function()
local requests = require 'requests'
local ok, result = pcall(requests.request, method, url, args)
if ok then
result.json, result.xml = nil, nil -- cannot be passed through a lane
return true, result
else
return false, result -- return error
end
end)
if not reject then reject = function() end end
lua_thread.create(function()
local lh = request_lane()
while true do
local status = lh.status
if status == 'done' then
local ok, result = lh[1], lh[2]
if ok then resolve(result) else reject(result) end
return
elseif status == 'error' then
return reject(lh[1])
elseif status == 'killed' or status == 'cancelled' then
return reject(status)
end
wait(0)
end
end)
end
Lua:
async_http_request('GET', 'https://www.google.com/robots.txt', nil --[[параметры запроса]],
function(response) -- вызовется при успешном выполнении и получении ответа
print(response.text) -- response.text - текст ответа. ещё есть response.status_code и response.headers
end,
function(err) -- вызовется при ошибке, err - текст ошибки. эту функцию можно не указывать
print(err)
end)
Более сложный, но походит для частых и последовательных запросов, запускает один поток при первом запросе, который остаётся активен до завершения работы скрипта:
Lua:
local lanes = require('lanes').configure()
function sequent_async_http_request(method, url, args, resolve, reject)
if not _G['lanes.async_http'] then
local linda = lanes.linda()
local lane_gen = lanes.gen('*', {package = {path = package.path, cpath = package.cpath}}, function()
local requests = require 'requests'
while true do
local key, val = linda:receive(50 / 1000, 'request')
if key == 'request' then
local ok, result = pcall(requests.request, val.method, val.url, val.args)
if ok then
result.json, result.xml = nil, nil
linda:send('response', result)
else
linda:send('error', result)
end
end
end
end)
_G['lanes.async_http'] = {lane = lane_gen(), linda = linda}
end
local lanes_http = _G['lanes.async_http']
lanes_http.linda:send('request', {method = method, url = url, args = args})
lua_thread.create(function(linda)
while true do
local key, val = linda:receive(0, 'response', 'error')
if key == 'response' then
return resolve(val)
elseif key == 'error' then
return reject(val)
end
wait(0)
end
end, lanes_http.linda)
end
Стоит сразу добавить, что обе реализации достаточно примитивны, в них обрабатываются не все случаи возникновения ошибок и есть что улучшить. Пользоваться можно, но по большей части они созданы послужить основой для более продвинутых реализаций и помочь в понимании устройства многопоточности в луа.
Последнее редактирование модератором: