Исходник Гайд Асинхронные HTTP запросы в MoonLoader

Статус
В этой теме нельзя размещать новые ответы.

FYP

Известный
Автор темы
Администратор
1,764
5,923
Асинхронные HTTP-запросы, основанные на неблокирующих сокетах, реализующихся библиотекой copas. Запросы выполняются в том же потоке, благодаря чему не подвержены никаким проблемам многопоточности и проще в использовании.

Для следующей реализации требуются только библиотеки 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 из модуля socket.http (и ssl.https), кроме дополнительного последнего параметра.
Параметры функции
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
Вызов точно такой же, как и в первом примере.

Стоит сразу добавить, что обе реализации достаточно примитивны, в них обрабатываются не все случаи возникновения ошибок и есть что улучшить. Пользоваться можно, но по большей части они созданы послужить основой для более продвинутых реализаций и помочь в понимании устройства многопоточности в луа.
 
Последнее редактирование модератором:

AnWu

Известный
Всефорумный модератор
4,777
5,400
Обработка ошибок не интерисует. Будет ли ощущаться разница между downloadUrlToFile при загрузке Json файла и чтением первым способом? Просто у меня есть проект который и так блять использует 10+ библиотек, и еще это пихать затратно выходит.
 
  • Нравится
Реакции: obito...

FYP

Известный
Автор темы
Администратор
1,764
5,923
Обработка ошибок не интерисует. Будет ли ощущаться разница между downloadUrlToFile при загрузке Json файла и чтением первым способом? Просто у меня есть проект который и так блять использует 10+ библиотек, и еще это пихать затратно выходит.
если скачиваются только один-два файла, то лучше использовать downloadUrlToFile. тащить кучу либ ради проверки версии или скачивания обновления - идея так себе.
 
  • Нравится
Реакции: AnWu

AnWu

Известный
Всефорумный модератор
4,777
5,400
если скачиваются только один-два файла, то лучше использовать downloadUrlToFile. тащить кучу либ ради проверки версии или скачивания обновления - идея так себе.
Лады, значит ничего менять не буду. Но это полезно для все серверных чатов и тп.
 

Garrus

Известный
159
20
Как выполнить пост-запрос? Что-то не получается передать в него данные)
 

FYP

Известный
Автор темы
Администратор
1,764
5,923

Garrus

Известный
159
20
Как можно загрузить файл с помощью lua-requests? (Почему-то не у всех работает функция downloadUrlToFile, когда как get/post запросы либы luasocket у всех корректно работают. Решил переписать функцию обновления под это дело).
UPD. Разобрался.
Lua:
local f = assert(io.open('moonloader\\file', 'wb'))
f:write(response.text)
f:close()
 
Последнее редактирование:

Garrus

Известный
159
20
На новом муне (026), функция async_http_request некорректно работает, если ее вызвать с нового потока, созданного с помощью
lua_thread.create.
 
  • Нравится
Реакции: applethecandy

FYP

Известный
Автор темы
Администратор
1,764
5,923
На новом муне (026), функция async_http_request некорректно работает, если ее вызвать с нового потока, созданного с помощью
lua_thread.create.
да, в новой версии есть проблема с потоками, завтра будет фикс
 

Garrus

Известный
159
20
На новом муне (026), функция async_http_request некорректно работает, если ее вызвать с нового потока, созданного с помощью
lua_thread.create.
Проблема по-прежнему актуальна на 26.1
UPD. Fixed in 026.2.
 
Последнее редактирование:
  • Нравится
Реакции: applethecandy

Garrus

Известный
159
20
Вызывать функцию async_http_request внутри нее же нельзя? На прошлой версии муна вроде работало, сейчас падает скрипт/игра.
 

FYP

Известный
Автор темы
Администратор
1,764
5,923
Вызывать функцию async_http_request внутри нее же нельзя? На прошлой версии муна вроде работало, сейчас падает скрипт/игра.
ну вообще должно работать. способ не идеальный, как уже говорилось, нужно дорабатывать. сомневаюсь, что проблема в муне. lanes и requests собраны под luajit 2.0, может быть из-за этого проблемы с новым муном.
дай код для воспроизведения проблемы
 

Garrus

Известный
159
20
ну вообще должно работать. способ не идеальный, как уже говорилось, нужно дорабатывать. сомневаюсь, что проблема в муне. lanes и requests собраны под luajit 2.0, может быть из-за этого проблемы с новым муном.
дай код для воспроизведения проблемы
Lua:
async_http_request('GET', 'https://www.google.com/robots.txt', nil,
  function(newVersions)
print("Доступно обновление скрипта.")

---------------    changelog
async_http_request('GET', 'https://www.google.com/robots.txt', nil,
  function(changelog)
    print(changelog.text)
  
--------------- downloading new version
async_http_request('GET', 'https://www.google.com/robots.txt', nil,
  function(response)
-- загружаем новую версию скрипта и перезагружаем его
 
  end,
  function(err) 
    print("Ошибка при загрузке обновления:")
    print(err)
  end)
  
  end,
  function(err)
    print("Ошибка при загрузке обновления:")
    print(err)
  end)  
  
    end,
  function(err)
    print("Ошибка при загрузке инофрмации о последней версии:")
    print(err)
  end)

Сама функция выполняется, но проходит около минуты и скрипт падает с ошибкой "cannot resume non-suspended coroutine".
 
  • Нравится
Реакции: applethecandy

FYP

Известный
Автор темы
Администратор
1,764
5,923
Lua:
async_http_request('GET', 'https://www.google.com/robots.txt', nil,
  function(newVersions)
print("Доступно обновление скрипта.")

---------------    changelog
async_http_request('GET', 'https://www.google.com/robots.txt', nil,
  function(changelog)
    print(changelog.text)
 
--------------- downloading new version
async_http_request('GET', 'https://www.google.com/robots.txt', nil,
  function(response)
-- загружаем новую версию скрипта и перезагружаем его
 
  end,
  function(err)
    print("Ошибка при загрузке обновления:")
    print(err)
  end)
 
  end,
  function(err)
    print("Ошибка при загрузке обновления:")
    print(err)
  end) 
 
    end,
  function(err)
    print("Ошибка при загрузке инофрмации о последней версии:")
    print(err)
  end)

Сама функция выполняется, но проходит около минуты и скрипт падает с ошибкой "cannot resume non-suspended coroutine".
не воспроизводится, даже если запускать несколько раз. проверял с lanes для luajit 2.0 и 2.1.
попробуй с этой либой
 

Вложения

  • httptest.lua
    2.2 KB · Просмотры: 148
  • lanes_luajit210.zip
    39.4 KB · Просмотры: 85

Garrus

Известный
159
20
не воспроизводится, даже если запускать несколько раз. проверял с lanes для luajit 2.0 и 2.1.
попробуй с этой либой
Удалось воспроизвести проблему, поместив функцию в скрипт clickwarp - скрипт падает при вызове курсора. В моем скрипте игра зависает при вызове менюшки imgui.
 

Вложения

  • clickwarp.lua
    10.6 KB · Просмотры: 29
Последнее редактирование:
Статус
В этой теме нельзя размещать новые ответы.