mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 18:41:51 +00:00
Merge pull request #972 from MihailRis/upgrade-http-in-vc
upgrade http requests
This commit is contained in:
commit
85cc55c63b
16 changed files with 461 additions and 338 deletions
4
.github/workflows/appimage.yml
vendored
4
.github/workflows/appimage.yml
vendored
|
|
@ -48,11 +48,11 @@ jobs:
|
|||
- name: Run tests
|
||||
run: ctest --test-dir build
|
||||
- name: Run engine tests
|
||||
timeout-minutes: 1
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
chmod +x build/VoxelEngine
|
||||
chmod +x AppDir/usr/bin/vctest
|
||||
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build
|
||||
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build --memchecker valgrind
|
||||
- name: Build AppImage
|
||||
uses: AppImageCrafters/build-appimage-action@fe2205a4d6056be47051f7b1b3811106e9814910
|
||||
env:
|
||||
|
|
|
|||
8
.github/workflows/macos.yml
vendored
8
.github/workflows/macos.yml
vendored
|
|
@ -23,13 +23,13 @@ jobs:
|
|||
|
||||
- name: Install dependencies from brew
|
||||
run: |
|
||||
brew install glfw3 glew libpng openal-soft luajit libvorbis skypjack/entt/entt googletest glm freetype
|
||||
brew install glfw3 glew libpng openal-soft luajit libvorbis skypjack/entt/entt googletest glm freetype
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
cmake -DOPENAL_INCLUDE_DIR=$(brew --prefix openal-soft)/include \
|
||||
-DOPENAL_LIBRARY=$(brew --prefix openal-soft)/lib/libopenal.dylib \
|
||||
-S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1 -DVC_BUILD_NAME="${{ inputs.build_name }}"
|
||||
-S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1 -DVC_BUILD_NAME="${{ inputs.build_name }}"
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
|
|
@ -45,11 +45,11 @@ jobs:
|
|||
run: ctest --output-on-failure --test-dir build
|
||||
|
||||
- name: Run engine tests
|
||||
timeout-minutes: 1
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
chmod +x build/VoxelEngine
|
||||
chmod +x AppDir/usr/bin/vctest
|
||||
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build --output-always
|
||||
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build --output-always --debugger "lldb -batch -o 'run' -o 'thread backtrace all' -o 'quit' -- "
|
||||
- name: Create DMG
|
||||
run: |
|
||||
mkdir VoxelEngineDmgContent
|
||||
|
|
|
|||
2
.github/workflows/windows.yml
vendored
2
.github/workflows/windows.yml
vendored
|
|
@ -38,7 +38,7 @@ jobs:
|
|||
- name: Run engine tests
|
||||
run: |
|
||||
build/vctest/RelWithDebInfo/vctest.exe -e build/RelWithDebInfo/VoxelEngine.exe -d dev/tests -u build
|
||||
timeout-minutes: 1
|
||||
timeout-minutes: 5
|
||||
- name: Package for Windows
|
||||
run: |
|
||||
mkdir packaged
|
||||
|
|
|
|||
|
|
@ -8,4 +8,4 @@ end, function (code)
|
|||
response_received = true
|
||||
end)
|
||||
|
||||
app.sleep_until(function () return response_received end, nil, 10)
|
||||
app.sleep_until(function () return response_received end, nil, 5)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,39 @@ A library for working with the network.
|
|||
|
||||
## HTTP requests
|
||||
|
||||
There is a configurable function `network.request` that allows performing HTTP requests with various methods (GET, POST, PUT, DELETE, etc.) and configuring headers, request body, timeout, and other parameters.
|
||||
|
||||
> To pass binary data in the request body, use a byte array (Bytearray) or a string. In `on_response`, the `body` string can be converted to a byte array using `Bytearray(response.body)`.
|
||||
|
||||
```lua
|
||||
network.request(
|
||||
url: string,
|
||||
parameters: {
|
||||
-- Request method (GET, POST, PUT, DELETE, etc.)
|
||||
method: string,
|
||||
-- Request body as a string or Bytearray
|
||||
body: table|string,
|
||||
-- List of additional request headers
|
||||
headers: table<string>,
|
||||
-- Timeout in milliseconds
|
||||
timeout: int,
|
||||
-- Whether to verify the SSL certificate
|
||||
verify_ssl: boolean,
|
||||
-- Function called when a response is received
|
||||
on_response: function(response: {
|
||||
-- HTTP response status code
|
||||
status: int,
|
||||
-- Response body as a string
|
||||
body: string,
|
||||
-- List of response headers
|
||||
headers: table<string>
|
||||
}),
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Simplified functions for GET and POST requests
|
||||
|
||||
```lua
|
||||
-- Performs a GET request to the specified URL.
|
||||
network.get(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,39 @@
|
|||
|
||||
## HTTP-Запросы
|
||||
|
||||
Существует настраиваемая функция `network.request`, которая позволяет выполнять HTTP-запросы с различными методами (GET, POST, PUT, DELETE и т.д.) и настраивать заголовки, тело запроса, таймаут и другие параметры.
|
||||
|
||||
> Для передачи двоичных данных в теле запроса, используйте массив байт (Bytearray) или строку. В `on_response` строку `body` можно преобразовать в массив байт с помощью `Bytearray(response.body)`.
|
||||
|
||||
```lua
|
||||
network.request(
|
||||
url: string,
|
||||
parameters: {
|
||||
-- Метод запроса (GET, POST, PUT, DELETE и т.д.)
|
||||
method: string,
|
||||
-- Тело запроса в виде строки или массива байт (Bytearray)
|
||||
body: table|string,
|
||||
-- Список дополнительных заголовков запроса
|
||||
headers: table<string>,
|
||||
-- Таймаут в миллисекундах
|
||||
timeout: int,
|
||||
-- Проверять ли SSL-сертификат
|
||||
verify_ssl: boolean,
|
||||
-- Функция, вызываемая при получении ответа
|
||||
on_response: function(response: {
|
||||
-- HTTP-код ответа
|
||||
status: int,
|
||||
-- Тело ответа в виде строки
|
||||
body: string,
|
||||
-- Список заголовков ответа
|
||||
headers: table<string>
|
||||
}),
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Упрощённые функции для GET и POST запросов
|
||||
|
||||
```lua
|
||||
-- Выполняет GET запрос к указанному URL.
|
||||
network.get(
|
||||
|
|
@ -231,4 +264,4 @@ network.get_total_download() -> int
|
|||
```lua
|
||||
-- Ищет свободный для использования порт.
|
||||
network.find_free_port() -> int | nil
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -98,40 +98,80 @@ local _udp_server_callbacks = {}
|
|||
local _udp_client_datagram_callbacks = {}
|
||||
local _udp_client_open_callbacks = {}
|
||||
local _http_response_callbacks = {}
|
||||
local _http_error_callbacks = {}
|
||||
|
||||
local http_request = network.__request
|
||||
local open_tcp = network.__open_tcp
|
||||
local open_udp = network.__open_udp
|
||||
local connect_tcp = network.__connect_tcp
|
||||
local connect_udp = network.__connect_udp
|
||||
network.__request = nil
|
||||
network.__open_tcp = nil
|
||||
network.__open_udp = nil
|
||||
network.__connect_tcp = nil
|
||||
network.__connect_udp = nil
|
||||
|
||||
local function request(url, params)
|
||||
local id = http_request(url, params)
|
||||
if params.on_response then
|
||||
_http_response_callbacks[id] = params.on_response
|
||||
end
|
||||
end
|
||||
|
||||
network.request = request
|
||||
|
||||
network.get = function(url, callback, errorCallback, headers)
|
||||
local id = network.__get(url, headers)
|
||||
if callback then
|
||||
_http_response_callbacks[id] = callback
|
||||
end
|
||||
if errorCallback then
|
||||
_http_error_callbacks[id] = errorCallback
|
||||
end
|
||||
return request(url, {
|
||||
method = "GET",
|
||||
headers = headers,
|
||||
on_response = function(response)
|
||||
if response.status / 100 == 2 then
|
||||
return callback(response.body)
|
||||
else
|
||||
return errorCallback(response.status, response.body)
|
||||
end
|
||||
end,
|
||||
on_error = function(response)
|
||||
errorCallback(response.status, response.body)
|
||||
end,
|
||||
follow_location = true,
|
||||
})
|
||||
end
|
||||
|
||||
network.get_binary = function(url, callback, errorCallback, headers)
|
||||
local id = network.__get_binary(url, headers)
|
||||
if callback then
|
||||
_http_response_callbacks[id] = callback
|
||||
end
|
||||
if errorCallback then
|
||||
_http_error_callbacks[id] = errorCallback
|
||||
end
|
||||
return request(url, {
|
||||
method = "GET",
|
||||
headers = headers,
|
||||
on_response = callback and (function (response)
|
||||
if response.code / 100 == 2 then
|
||||
return callback(Bytearray(response.body))
|
||||
else
|
||||
return errorCallback(response.status, response.body)
|
||||
end
|
||||
end),
|
||||
follow_location = true,
|
||||
})
|
||||
end
|
||||
|
||||
network.post = function(url, data, callback, errorCallback, headers)
|
||||
local id = network.__post(url, data, headers)
|
||||
if callback then
|
||||
_http_response_callbacks[id] = callback
|
||||
end
|
||||
if errorCallback then
|
||||
_http_error_callbacks[id] = errorCallback
|
||||
end
|
||||
network.post = function(url, body, callback, errorCallback, headers)
|
||||
return request(url, {
|
||||
method = "POST",
|
||||
headers = table.extend({
|
||||
"Content-Type: application/json"
|
||||
}, headers),
|
||||
body = body,
|
||||
on_response = function(response)
|
||||
if response.code / 100 == 2 then
|
||||
return callback(Bytearray(response.body))
|
||||
else
|
||||
return errorCallback(response.status, response.body)
|
||||
end
|
||||
end,
|
||||
follow_location = true,
|
||||
})
|
||||
end
|
||||
|
||||
network.tcp_open = function (port, handler)
|
||||
local socket = setmetatable({id=network.__open_tcp(port)}, ServerSocket)
|
||||
local socket = setmetatable({id=open_tcp(port)}, ServerSocket)
|
||||
|
||||
_tcp_server_callbacks[socket.id] = function(id)
|
||||
handler(setmetatable({id=id}, Socket))
|
||||
|
|
@ -141,7 +181,7 @@ end
|
|||
|
||||
network.tcp_connect = function(address, port, callback, errorCallback)
|
||||
local socket = setmetatable({id=0}, Socket)
|
||||
socket.id = network.__connect_tcp(address, port)
|
||||
socket.id = connect_tcp(address, port)
|
||||
_tcp_client_callbacks[socket.id] = function() callback(socket) end
|
||||
if errorCallback then
|
||||
_tcp_client_error_callbacks[socket.id] = function(message) errorCallback(socket, message) end
|
||||
|
|
@ -154,7 +194,7 @@ network.udp_open = function (port, datagramHandler)
|
|||
error "udp server cannot be opened without datagram handler"
|
||||
end
|
||||
|
||||
local socket = setmetatable({id=network.__open_udp(port)}, DatagramServerSocket)
|
||||
local socket = setmetatable({id=open_udp(port)}, DatagramServerSocket)
|
||||
|
||||
_udp_server_callbacks[socket.id] = function(address, port, data)
|
||||
datagramHandler(address, port, data, socket)
|
||||
|
|
@ -169,7 +209,7 @@ network.udp_connect = function (address, port, datagramHandler, openCallback)
|
|||
end
|
||||
|
||||
local socket = setmetatable({id=0}, WriteableSocket)
|
||||
socket.id = network.__connect_udp(address, port)
|
||||
socket.id = connect_udp(address, port)
|
||||
|
||||
_udp_client_datagram_callbacks[socket.id] = datagramHandler
|
||||
if openCallback then
|
||||
|
|
@ -239,20 +279,10 @@ network.__process_events = function()
|
|||
end
|
||||
end
|
||||
elseif etype == RESPONSE then
|
||||
if event[2] / 100 == 2 then
|
||||
local callback = _http_response_callbacks[event[3]]
|
||||
_http_response_callbacks[event[3]] = nil
|
||||
_http_error_callbacks[event[3]] = nil
|
||||
if callback then
|
||||
callback(event[4])
|
||||
end
|
||||
else
|
||||
local callback = _http_error_callbacks[event[3]]
|
||||
_http_response_callbacks[event[3]] = nil
|
||||
_http_error_callbacks[event[3]] = nil
|
||||
if callback then
|
||||
callback(event[2], event[4])
|
||||
end
|
||||
local callback = _http_response_callbacks[event[3]]
|
||||
_http_response_callbacks[event[3]] = nil
|
||||
if callback then
|
||||
callback(event[4])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "engine/Engine.hpp"
|
||||
#include "network/Network.hpp"
|
||||
#include "devtools/Project.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
|
||||
#include <variant>
|
||||
#include <utility>
|
||||
|
|
@ -27,7 +28,7 @@ struct ResponseEventDto {
|
|||
int status;
|
||||
bool binary;
|
||||
int requestId;
|
||||
std::vector<char> bytes;
|
||||
network::HttpResponse response;
|
||||
};
|
||||
|
||||
enum NetworkDatagramSide {
|
||||
|
|
@ -85,74 +86,55 @@ static std::vector<std::string> read_headers(lua::State* L, int index) {
|
|||
|
||||
static int request_id = 1;
|
||||
|
||||
static int perform_get(lua::State* L, network::Network& network, bool binary) {
|
||||
std::string url(lua::require_lstring(L, 1));
|
||||
auto headers = read_headers(L, 2);
|
||||
static int l_request(lua::State* L, network::Network& network) {
|
||||
network::HttpRequest request {};
|
||||
request.url = lua::require_lstring(L, 1);
|
||||
|
||||
int currentRequestId = request_id++;
|
||||
|
||||
network.get(
|
||||
url,
|
||||
[currentRequestId, binary](std::vector<char> bytes) {
|
||||
push_event(NetworkEvent(
|
||||
RESPONSE,
|
||||
ResponseEventDto {
|
||||
200, binary, currentRequestId, std::move(bytes)}
|
||||
));
|
||||
},
|
||||
[currentRequestId, binary](int code, std::vector<char> bytes) {
|
||||
push_event(NetworkEvent(
|
||||
RESPONSE,
|
||||
ResponseEventDto {
|
||||
code, binary, currentRequestId, std::move(bytes)}
|
||||
));
|
||||
},
|
||||
std::move(headers)
|
||||
);
|
||||
return lua::pushinteger(L, currentRequestId);
|
||||
}
|
||||
|
||||
static int l_get(lua::State* L, network::Network& network) {
|
||||
return perform_get(L, network, false);
|
||||
}
|
||||
|
||||
static int l_get_binary(lua::State* L, network::Network& network) {
|
||||
return perform_get(L, network, true);
|
||||
}
|
||||
|
||||
static int l_post(lua::State* L, network::Network& network) {
|
||||
std::string url(lua::require_lstring(L, 1));
|
||||
auto data = lua::tovalue(L, 2);
|
||||
|
||||
std::string string;
|
||||
if (data.isString()) {
|
||||
string = data.asString();
|
||||
} else {
|
||||
string = json::stringify(data, false);
|
||||
if (!lua::istable(L, 2)) {
|
||||
throw std::runtime_error("table expected as argument #2");
|
||||
}
|
||||
if (lua::getfield(L, "method", 2)) {
|
||||
request.method = lua::require_string(L, -1);
|
||||
lua::pop(L);
|
||||
}
|
||||
if (lua::getfield(L, "headers", 2)) {
|
||||
request.headers = read_headers(L, -1);
|
||||
lua::pop(L);
|
||||
}
|
||||
if (lua::getfield(L, "body", 2)) {
|
||||
if (lua::type(L, -1) == LUA_TCDATA) {
|
||||
request.body = lua::bytearray_as_string(L, -1);
|
||||
} else {
|
||||
request.body = lua::require_lstring(L, -1);
|
||||
}
|
||||
lua::pop(L);
|
||||
}
|
||||
if (lua::getfield(L, "follow_location", 2)) {
|
||||
request.followLocation = lua::toboolean(L, -1);
|
||||
lua::pop(L);
|
||||
}
|
||||
if (lua::getfield(L, "timeout_ms", 2)) {
|
||||
request.timeoutMs = lua::tointeger(L, -1);
|
||||
lua::pop(L);
|
||||
}
|
||||
if (lua::getfield(L, "verify_ssl", 2)) {
|
||||
request.verifySSL = lua::toboolean(L, -1);
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
auto headers = read_headers(L, 3);
|
||||
int currentRequestId = request_id++;
|
||||
request.onResponse = [currentRequestId](network::HttpResponse response) {
|
||||
push_event(NetworkEvent(
|
||||
RESPONSE,
|
||||
ResponseEventDto {
|
||||
response.status,
|
||||
false,
|
||||
currentRequestId,
|
||||
std::move(response)}
|
||||
));
|
||||
};
|
||||
|
||||
network.post(
|
||||
url,
|
||||
string,
|
||||
[currentRequestId](std::vector<char> bytes) {
|
||||
push_event(NetworkEvent(
|
||||
RESPONSE,
|
||||
ResponseEventDto {
|
||||
200, false, currentRequestId, std::move(bytes)}
|
||||
));
|
||||
},
|
||||
[currentRequestId](int code, std::vector<char> bytes) {
|
||||
push_event(NetworkEvent(
|
||||
RESPONSE,
|
||||
ResponseEventDto {
|
||||
code, false, currentRequestId, std::move(bytes)}
|
||||
));
|
||||
},
|
||||
std::move(headers)
|
||||
);
|
||||
network.request(std::move(request));
|
||||
return lua::pushinteger(L, currentRequestId);
|
||||
}
|
||||
|
||||
|
|
@ -519,11 +501,31 @@ static int l_pull_events(lua::State* L) {
|
|||
lua::pushinteger(L, dto.requestId);
|
||||
lua::rawseti(L, 3);
|
||||
|
||||
lua::createtable(L, 0, 2);
|
||||
lua::pushinteger(L, dto.response.status);
|
||||
lua::setfield(L, "status");
|
||||
|
||||
if (dto.binary) {
|
||||
lua::create_bytearray(L, dto.bytes.data(), dto.bytes.size());
|
||||
lua::create_bytearray(
|
||||
L, dto.response.body.data(), dto.response.body.size()
|
||||
);
|
||||
} else {
|
||||
lua::pushlstring(L, std::string_view(dto.bytes.data(), dto.bytes.size()));
|
||||
lua::pushlstring(
|
||||
L,
|
||||
std::string_view(
|
||||
dto.response.body.data(), dto.response.body.size()
|
||||
)
|
||||
);
|
||||
}
|
||||
lua::setfield(L, "body");
|
||||
|
||||
lua::createtable(L, dto.response.headers.size(), 0);
|
||||
for (int i = 0; i < dto.response.headers.size(); i++) {
|
||||
lua::pushlstring(L, dto.response.headers[i]);
|
||||
lua::rawseti(L, i + 1);
|
||||
}
|
||||
lua::setfield(L, "headers");
|
||||
|
||||
lua::rawseti(L, 4);
|
||||
break;
|
||||
}
|
||||
|
|
@ -562,9 +564,7 @@ int wrap(lua_State* L) {
|
|||
}
|
||||
|
||||
const luaL_Reg networklib[] = {
|
||||
{"__get", wrap<l_get>},
|
||||
{"__get_binary", wrap<l_get_binary>},
|
||||
{"__post", wrap<l_post>},
|
||||
{"__request", wrap<l_request>},
|
||||
{"get_total_upload", wrap<l_get_total_upload>},
|
||||
{"get_total_download", wrap<l_get_total_download>},
|
||||
{"find_free_port", wrap<l_find_free_port>},
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
#define NOMINMAX
|
||||
#include <curl/curl.h>
|
||||
#include <queue>
|
||||
|
||||
using namespace network;
|
||||
|
||||
|
|
@ -23,97 +22,96 @@ static size_t write_callback(
|
|||
return size * nmemb;
|
||||
}
|
||||
|
||||
enum class RequestType {
|
||||
GET, POST
|
||||
};
|
||||
static size_t header_callback(
|
||||
char* buffer, size_t size, size_t nitems, void* userdata
|
||||
) {
|
||||
auto* headers = static_cast<std::vector<std::string>*>(userdata);
|
||||
size_t len = size * nitems;
|
||||
std::string header(buffer, len);
|
||||
|
||||
struct Request {
|
||||
RequestType type;
|
||||
std::string url;
|
||||
OnResponse onResponse;
|
||||
OnReject onReject;
|
||||
long maxSize;
|
||||
bool followLocation = false;
|
||||
std::string data;
|
||||
while (!header.empty() &&
|
||||
(header.back() == '\r' || header.back() == '\n')) {
|
||||
header.pop_back();
|
||||
}
|
||||
|
||||
headers->push_back(std::move(header));
|
||||
return len;
|
||||
}
|
||||
|
||||
struct ProcessingRequest {
|
||||
CURLM* multiHandle;
|
||||
CURL* curl;
|
||||
HttpRequest request;
|
||||
std::vector<char> buffer;
|
||||
std::vector<std::string> headers;
|
||||
|
||||
ProcessingRequest(CURLM* multiHandle) : multiHandle(multiHandle) {
|
||||
curl = curl_easy_init();
|
||||
}
|
||||
|
||||
ProcessingRequest(const ProcessingRequest&) = delete;
|
||||
ProcessingRequest& operator=(const ProcessingRequest&) = delete;
|
||||
|
||||
ProcessingRequest(ProcessingRequest&& other) noexcept
|
||||
: multiHandle(other.multiHandle),
|
||||
curl(other.curl),
|
||||
request(std::move(other.request)),
|
||||
buffer(std::move(other.buffer)),
|
||||
headers(std::move(other.headers)) {
|
||||
other.curl = nullptr;
|
||||
other.multiHandle = nullptr;
|
||||
}
|
||||
|
||||
ProcessingRequest& operator=(ProcessingRequest&& other) noexcept {
|
||||
if (this != &other) {
|
||||
if (curl) {
|
||||
curl_multi_remove_handle(multiHandle, curl);
|
||||
curl_easy_cleanup(curl);
|
||||
}
|
||||
multiHandle = other.multiHandle;
|
||||
curl = other.curl;
|
||||
request = std::move(other.request);
|
||||
buffer = std::move(other.buffer);
|
||||
headers = std::move(other.headers);
|
||||
other.curl = nullptr;
|
||||
other.multiHandle = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~ProcessingRequest() {
|
||||
if (curl) {
|
||||
curl_multi_remove_handle(multiHandle, curl);
|
||||
curl_easy_cleanup(curl);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class CurlRequests : public Requests {
|
||||
CURLM* multiHandle;
|
||||
CURL* curl;
|
||||
std::vector<std::unique_ptr<ProcessingRequest>> requests;
|
||||
|
||||
size_t totalUpload = 0;
|
||||
size_t totalDownload = 0;
|
||||
|
||||
OnResponse onResponse;
|
||||
OnReject onReject;
|
||||
std::vector<char> buffer;
|
||||
std::string url;
|
||||
|
||||
std::queue<Request> requests;
|
||||
public:
|
||||
CurlRequests(CURLM* multiHandle, CURL* curl)
|
||||
: multiHandle(multiHandle), curl(curl) {
|
||||
CurlRequests(CURLM* multiHandle) : multiHandle(multiHandle) {
|
||||
}
|
||||
|
||||
virtual ~CurlRequests() {
|
||||
curl_multi_remove_handle(multiHandle, curl);
|
||||
curl_easy_cleanup(curl);
|
||||
requests.clear();
|
||||
curl_multi_cleanup(multiHandle);
|
||||
}
|
||||
void get(
|
||||
const std::string& url,
|
||||
OnResponse onResponse,
|
||||
OnReject onReject,
|
||||
std::vector<std::string> headers,
|
||||
long maxSize
|
||||
) override {
|
||||
Request request {
|
||||
RequestType::GET,
|
||||
url,
|
||||
onResponse,
|
||||
onReject,
|
||||
maxSize,
|
||||
true,
|
||||
"",
|
||||
std::move(headers)};
|
||||
|
||||
void request(HttpRequest request) override {
|
||||
processRequest(std::move(request));
|
||||
}
|
||||
|
||||
void post(
|
||||
const std::string& url,
|
||||
const std::string& data,
|
||||
OnResponse onResponse,
|
||||
OnReject onReject=nullptr,
|
||||
std::vector<std::string> headers = {},
|
||||
long maxSize=0
|
||||
) override {
|
||||
Request request {
|
||||
RequestType::POST,
|
||||
url,
|
||||
onResponse,
|
||||
onReject,
|
||||
maxSize,
|
||||
false,
|
||||
"",
|
||||
std::move(headers)};
|
||||
request.data = data;
|
||||
processRequest(std::move(request));
|
||||
}
|
||||
void processRequest(HttpRequest request) {
|
||||
auto entry = std::make_unique<ProcessingRequest>(multiHandle);
|
||||
auto curl = entry->curl;
|
||||
|
||||
void processRequest(Request request) {
|
||||
if (!url.empty()) {
|
||||
requests.push(request);
|
||||
return;
|
||||
}
|
||||
onResponse = request.onResponse;
|
||||
onReject = request.onReject;
|
||||
url = request.url;
|
||||
|
||||
buffer.clear();
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_POST, request.type == RequestType::POST);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, request.method.c_str());
|
||||
|
||||
curl_slist* hs = nullptr;
|
||||
|
||||
|
|
@ -121,22 +119,25 @@ public:
|
|||
hs = curl_slist_append(hs, header.c_str());
|
||||
}
|
||||
|
||||
switch (request.type) {
|
||||
case RequestType::GET:
|
||||
break;
|
||||
case RequestType::POST:
|
||||
hs = curl_slist_append(hs, "Content-Type: application/json");
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request.data.length());
|
||||
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request.data.c_str());
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("not implemented");
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(request.body.length()));
|
||||
if (!request.body.empty()) {
|
||||
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request.body.data());
|
||||
}
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, static_cast<long>(request.verifySSL));
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, static_cast<long>(request.verifySSL));
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, request.followLocation);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, static_cast<long>(request.followLocation));
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &entry->buffer);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &entry->headers);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.81.0");
|
||||
if (request.timeoutMs > 0) {
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, request.timeoutMs);
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||
#endif
|
||||
if (request.maxSize == 0) {
|
||||
curl_easy_setopt(
|
||||
curl, CURLOPT_MAXFILESIZE, std::numeric_limits<long>::max()
|
||||
|
|
@ -144,69 +145,105 @@ public:
|
|||
} else {
|
||||
curl_easy_setopt(curl, CURLOPT_MAXFILESIZE, request.maxSize);
|
||||
}
|
||||
curl_multi_add_handle(multiHandle, curl);
|
||||
int running;
|
||||
CURLMcode res = curl_multi_perform(multiHandle, &running);
|
||||
if (res != CURLM_OK) {
|
||||
auto message = curl_multi_strerror(res);
|
||||
logger.error() << message << " (" << url << ")";
|
||||
if (onReject) {
|
||||
onReject(HTTP_BAD_GATEWAY, {});
|
||||
{
|
||||
CURLMcode res = curl_multi_add_handle(multiHandle, curl);
|
||||
if (res != CURLM_OK) {
|
||||
auto message = curl_multi_strerror(res);
|
||||
logger.error() << message << " (" << request.url << ")";
|
||||
if (request.onResponse) {
|
||||
request.onResponse({0, {}, {}});
|
||||
}
|
||||
return;
|
||||
}
|
||||
url = "";
|
||||
}
|
||||
{
|
||||
int running;
|
||||
CURLMcode res = curl_multi_perform(multiHandle, &running);
|
||||
if (res != CURLM_OK) {
|
||||
auto message = curl_multi_strerror(res);
|
||||
logger.error() << message << " (" << request.url << ")";
|
||||
if (request.onResponse) {
|
||||
request.onResponse({0, {}, {}});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
entry->request = std::move(request);
|
||||
requests.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
void update() override {
|
||||
int messagesLeft;
|
||||
int running;
|
||||
CURLMsg* msg;
|
||||
CURLMcode res = curl_multi_perform(multiHandle, &running);
|
||||
if (res != CURLM_OK) {
|
||||
if (CURLMcode res = curl_multi_poll(
|
||||
multiHandle,
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
nullptr
|
||||
)) {
|
||||
auto message = curl_multi_strerror(res);
|
||||
logger.error() << message << " (" << url << ")";
|
||||
if (onReject) {
|
||||
onReject(HTTP_BAD_GATEWAY, {});
|
||||
}
|
||||
curl_multi_remove_handle(multiHandle, curl);
|
||||
url = "";
|
||||
logger.error() << message;
|
||||
return;
|
||||
}
|
||||
if ((msg = curl_multi_info_read(multiHandle, &messagesLeft)) != nullptr) {
|
||||
if(msg->msg == CURLMSG_DONE) {
|
||||
curl_multi_remove_handle(multiHandle, curl);
|
||||
}
|
||||
int response;
|
||||
curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response);
|
||||
if (response == HTTP_OK) {
|
||||
long size;
|
||||
if (!curl_easy_getinfo(curl, CURLINFO_REQUEST_SIZE, &size)) {
|
||||
totalUpload += size;
|
||||
}
|
||||
if (!curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &size)) {
|
||||
totalDownload += size;
|
||||
}
|
||||
totalDownload += buffer.size();
|
||||
if (onResponse) {
|
||||
onResponse(std::move(buffer));
|
||||
}
|
||||
} else {
|
||||
logger.error()
|
||||
<< "response code " << response << " (" << url << ")"
|
||||
<< (buffer.empty()
|
||||
? ""
|
||||
: std::to_string(buffer.size()) + " byte(s)");
|
||||
totalDownload += buffer.size();
|
||||
if (onReject) {
|
||||
onReject(response, std::move(buffer));
|
||||
}
|
||||
}
|
||||
url = "";
|
||||
|
||||
int messagesLeft;
|
||||
int running;
|
||||
if (CURLMcode res = curl_multi_perform(multiHandle, &running)) {
|
||||
auto message = curl_multi_strerror(res);
|
||||
logger.error() << message;
|
||||
return;
|
||||
}
|
||||
if (url.empty() && !requests.empty()) {
|
||||
auto request = std::move(requests.front());
|
||||
requests.pop();
|
||||
processRequest(std::move(request));
|
||||
CURLMsg* msg = curl_multi_info_read(multiHandle, &messagesLeft);
|
||||
if (msg == nullptr || msg->msg != CURLMSG_DONE) {
|
||||
return;
|
||||
}
|
||||
auto curl = msg->easy_handle;
|
||||
auto found = std::find_if(
|
||||
requests.begin(),
|
||||
requests.end(),
|
||||
[curl](const std::unique_ptr<ProcessingRequest>& entry) {
|
||||
return entry && entry->curl == curl;
|
||||
}
|
||||
);
|
||||
if (requests.empty() || found == requests.end()) {
|
||||
logger.error() << "could not find request for cURL handle";
|
||||
return;
|
||||
}
|
||||
auto entry = std::move(*found);
|
||||
auto& req = entry->request;
|
||||
requests.erase(found);
|
||||
|
||||
long response = -1;
|
||||
CURLcode result = msg->data.result;
|
||||
curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response);
|
||||
auto headers = std::move(entry->headers);
|
||||
if (response == 0) {
|
||||
auto message = std::string(curl_easy_strerror(result));
|
||||
logger.error() << message << " (" << req.url << ")";
|
||||
if (req.onResponse) {
|
||||
req.onResponse(
|
||||
{static_cast<int>(response),
|
||||
std::move(headers),
|
||||
std::vector<char>(
|
||||
message.data(), message.data() + message.size()
|
||||
)}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
long size;
|
||||
if (!curl_easy_getinfo(curl, CURLINFO_REQUEST_SIZE, &size)) {
|
||||
totalUpload += size;
|
||||
}
|
||||
if (!curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &size)) {
|
||||
totalDownload += size;
|
||||
}
|
||||
totalDownload += entry->buffer.size();
|
||||
if (req.onResponse) {
|
||||
req.onResponse({
|
||||
static_cast<int>(response),
|
||||
std::move(headers),
|
||||
std::move(entry->buffer),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -219,16 +256,11 @@ public:
|
|||
}
|
||||
|
||||
static std::unique_ptr<CurlRequests> create() {
|
||||
auto curl = curl_easy_init();
|
||||
if (curl == nullptr) {
|
||||
throw std::runtime_error("could not initialzie cURL");
|
||||
}
|
||||
auto multiHandle = curl_multi_init();
|
||||
if (multiHandle == nullptr) {
|
||||
curl_easy_cleanup(curl);
|
||||
throw std::runtime_error("could not initialzie cURL-multi");
|
||||
throw std::runtime_error("could not initialize cURL-multi");
|
||||
}
|
||||
return std::make_unique<CurlRequests>(multiHandle, curl);
|
||||
return std::make_unique<CurlRequests>(multiHandle);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -51,27 +51,8 @@ Network::Network(std::unique_ptr<Requests> requests)
|
|||
|
||||
Network::~Network() = default;
|
||||
|
||||
void Network::get(
|
||||
const std::string& url,
|
||||
OnResponse onResponse,
|
||||
OnReject onReject,
|
||||
std::vector<std::string> headers,
|
||||
long maxSize
|
||||
) {
|
||||
requests->get(url, onResponse, onReject, std::move(headers), maxSize);
|
||||
}
|
||||
|
||||
void Network::post(
|
||||
const std::string& url,
|
||||
const std::string& fieldsData,
|
||||
OnResponse onResponse,
|
||||
OnReject onReject,
|
||||
std::vector<std::string> headers,
|
||||
long maxSize
|
||||
) {
|
||||
requests->post(
|
||||
url, fieldsData, onResponse, onReject, std::move(headers), maxSize
|
||||
);
|
||||
void Network::request(HttpRequest request) {
|
||||
requests->request(std::move(request));
|
||||
}
|
||||
|
||||
Connection* Network::getConnection(u64id_t id, bool includePrivate) {
|
||||
|
|
|
|||
|
|
@ -68,22 +68,7 @@ namespace network {
|
|||
Network(std::unique_ptr<Requests> requests);
|
||||
~Network();
|
||||
|
||||
void get(
|
||||
const std::string& url,
|
||||
OnResponse onResponse,
|
||||
OnReject onReject = nullptr,
|
||||
std::vector<std::string> headers = {},
|
||||
long maxSize=0
|
||||
);
|
||||
|
||||
void post(
|
||||
const std::string& url,
|
||||
const std::string& fieldsData,
|
||||
OnResponse onResponse,
|
||||
OnReject onReject = nullptr,
|
||||
std::vector<std::string> headers = {},
|
||||
long maxSize=0
|
||||
);
|
||||
void request(HttpRequest request);
|
||||
|
||||
[[nodiscard]] Connection* getConnection(u64id_t id, bool includePrivate);
|
||||
[[nodiscard]] Server* getServer(u64id_t id, bool includePrivate) const;
|
||||
|
|
|
|||
|
|
@ -10,33 +10,38 @@
|
|||
#include <mutex>
|
||||
|
||||
namespace network {
|
||||
using OnResponse = std::function<void(std::vector<char>)>;
|
||||
using OnReject = std::function<void(int, std::vector<char>)>;
|
||||
struct HttpResponse;
|
||||
|
||||
using OnResponse = std::function<void(HttpResponse)>;
|
||||
using ConnectCallback = std::function<void(u64id_t, u64id_t)>;
|
||||
using ConnectErrorCallback = std::function<void(u64id_t, std::string)>;
|
||||
using ServerDatagramCallback = std::function<void(u64id_t sid, const std::string& addr, int port, const char* buffer, size_t length)>;
|
||||
using ClientDatagramCallback = std::function<void(u64id_t cid, const char* buffer, size_t length)>;
|
||||
|
||||
struct HttpRequest {
|
||||
std::string method;
|
||||
std::string url;
|
||||
std::string body;
|
||||
std::vector<std::string> headers;
|
||||
|
||||
OnResponse onResponse;
|
||||
bool followLocation = false;
|
||||
bool verifySSL = true;
|
||||
long maxSize = -1;
|
||||
long timeoutMs = 0;
|
||||
};
|
||||
|
||||
struct HttpResponse {
|
||||
int status;
|
||||
std::vector<std::string> headers;
|
||||
std::vector<char> body;
|
||||
};
|
||||
|
||||
class Requests {
|
||||
public:
|
||||
virtual ~Requests() {}
|
||||
|
||||
virtual void get(
|
||||
const std::string& url,
|
||||
OnResponse onResponse,
|
||||
OnReject onReject=nullptr,
|
||||
std::vector<std::string> headers = {},
|
||||
long maxSize=0
|
||||
) = 0;
|
||||
|
||||
virtual void post(
|
||||
const std::string& url,
|
||||
const std::string& data,
|
||||
OnResponse onResponse,
|
||||
OnReject onReject=nullptr,
|
||||
std::vector<std::string> headers = {},
|
||||
long maxSize=0
|
||||
) = 0;
|
||||
virtual void request(HttpRequest request) = 0;
|
||||
|
||||
[[nodiscard]] virtual size_t getTotalUpload() const = 0;
|
||||
[[nodiscard]] virtual size_t getTotalDownload() const = 0;
|
||||
|
|
|
|||
|
|
@ -543,6 +543,14 @@ static std::basic_string<CharT> upper_case(const std::basic_string<CharT>& str)
|
|||
return result;
|
||||
}
|
||||
|
||||
std::string util::lower_case(const std::string& str) {
|
||||
return ::lower_case(str);
|
||||
}
|
||||
|
||||
std::string util::upper_case(const std::string& str) {
|
||||
return ::upper_case(str);
|
||||
}
|
||||
|
||||
std::wstring util::lower_case(const std::wstring& str) {
|
||||
return ::lower_case(str);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@ namespace util {
|
|||
|
||||
std::string capitalized(const std::string& str);
|
||||
|
||||
std::string lower_case(const std::string& str);
|
||||
std::string upper_case(const std::string& str);
|
||||
std::wstring lower_case(const std::wstring& str);
|
||||
std::wstring upper_case(const std::wstring& str);
|
||||
std::wstring capitalized(const std::wstring& str);
|
||||
|
|
|
|||
|
|
@ -6,18 +6,21 @@
|
|||
TEST(curltest, curltest) {
|
||||
NetworkSettings settings {};
|
||||
auto network = network::Network::create(settings);
|
||||
network->get(
|
||||
"https://raw.githubusercontent.com/MihailRis/VoxelEngine-Cpp/refs/"
|
||||
"heads/curl/res/content/base/blocks/lamp.json",
|
||||
[](std::vector<char> data) {
|
||||
if (data.empty()) {
|
||||
return;
|
||||
}
|
||||
auto view = std::string_view(data.data(), data.size());
|
||||
auto value = json::parse(view);
|
||||
std::cout << value << std::endl;
|
||||
}, [](auto, auto){}
|
||||
);
|
||||
|
||||
network::HttpRequest request {};
|
||||
request.url = "https://raw.githubusercontent.com/MihailRis/VoxelEngine-Cpp/refs/"
|
||||
"heads/curl/res/content/base/blocks/lamp.json";
|
||||
request.onResponse = [](network::HttpResponse response) {
|
||||
if (response.body.empty()) {
|
||||
return;
|
||||
}
|
||||
auto view =
|
||||
std::string_view(response.body.data(), response.body.size());
|
||||
auto value = json::parse(view);
|
||||
std::cout << value << std::endl;
|
||||
};
|
||||
network->request(std::move(request));
|
||||
|
||||
std::cout << "upload: " << network->getTotalUpload() << " B" << std::endl;
|
||||
std::cout << "download: " << network->getTotalDownload() << " B" << std::endl;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ struct Config {
|
|||
fs::path directory;
|
||||
fs::path resDir {"res"};
|
||||
fs::path workingDir {"."};
|
||||
std::string memchecker = "valgrind";
|
||||
std::string memchecker = "";
|
||||
std::string debugger = "";
|
||||
bool outputAlways = false;
|
||||
};
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ static bool perform_keyword(
|
|||
std::cout << " --tests <path>, -d <path> = tests directory path\n";
|
||||
std::cout << " --res <path>, -r <path> = 'res' directory path\n";
|
||||
std::cout << " --user <path>, -u <path> = user directory path\n";
|
||||
std::cout << " --debugger <command> = debugger with run command\n";
|
||||
std::cout << " --memchecker <path> = path to valgrind\n";
|
||||
std::cout << " --output-always = always show tests output\n";
|
||||
std::cout << std::endl;
|
||||
|
|
@ -44,6 +46,8 @@ static bool perform_keyword(
|
|||
config.workingDir = fs::path(reader.next());
|
||||
} else if (keyword == "--output-always") {
|
||||
config.outputAlways = true;
|
||||
} else if (keyword == "--debugger") {
|
||||
config.debugger = reader.next();
|
||||
} else if (keyword == "--memchecker") {
|
||||
config.memchecker = reader.next();
|
||||
} else {
|
||||
|
|
@ -172,7 +176,7 @@ static std::string fix_path(std::string s) {
|
|||
return s;
|
||||
}
|
||||
|
||||
static bool run_test(const Config& config, const fs::path& path, bool memcheck = false) {
|
||||
static bool run_test(const Config& config, const fs::path& path, bool debugRun = false) {
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::high_resolution_clock;
|
||||
using std::chrono::milliseconds;
|
||||
|
|
@ -182,9 +186,16 @@ static bool run_test(const Config& config, const fs::path& path, bool memcheck =
|
|||
|
||||
auto name = path.stem();
|
||||
std::stringstream ss;
|
||||
if (memcheck) {
|
||||
ss << config.memchecker << " --log-file="
|
||||
<< fix_path(memcheckLogFile.string()) << " ";
|
||||
if (debugRun) {
|
||||
if (!config.memchecker.empty()) {
|
||||
ss << config.memchecker;
|
||||
ss << " --log-file=" << fix_path(memcheckLogFile.string());
|
||||
ss << " ";
|
||||
}
|
||||
if (!config.debugger.empty()) {
|
||||
ss << config.debugger;
|
||||
ss << " ";
|
||||
}
|
||||
}
|
||||
ss << fs::canonical(config.executable) << " --headless";
|
||||
ss << " --test " << fix_path(path.string());
|
||||
|
|
@ -203,7 +214,7 @@ static bool run_test(const Config& config, const fs::path& path, bool memcheck =
|
|||
.count();
|
||||
|
||||
if (code) {
|
||||
if (memcheck) {
|
||||
if (debugRun) {
|
||||
// valgrind-specific output
|
||||
display_segfault_valgrind(memcheckLogFile, name, std::cerr);
|
||||
fs::remove(memcheckLogFile);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue