add basic HTTP client and its testing in main()
This commit is contained in:
commit
d29c8dfcc9
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/CMakeFiles/
|
||||
/CMakeCache.txt
|
||||
/build/
|
||||
/.cache/
|
12
.gitmodules
vendored
Normal file
12
.gitmodules
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
[submodule "td"]
|
||||
path = td
|
||||
url = git@github.com:tdlib/td.git
|
||||
[submodule "libuv"]
|
||||
path = libuv
|
||||
url = git@github.com:libuv/libuv.git
|
||||
[submodule "spdlog"]
|
||||
path = spdlog
|
||||
url = git@github.com:gabime/spdlog.git
|
||||
[submodule "curl"]
|
||||
path = curl
|
||||
url = git@github.com:curl/curl.git
|
11
CMakeLists.txt
Normal file
11
CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(mmcs-quotes-bridge LANGUAGES CXX)
|
||||
|
||||
add_subdirectory(curl)
|
||||
add_subdirectory(libuv)
|
||||
add_subdirectory(spdlog)
|
||||
add_subdirectory(td)
|
||||
|
||||
add_executable(${PROJECT_NAME} main.cpp http.cpp)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl uv spdlog::spdlog Td::TdStatic $<$<BOOL:${MINGW}>:ws2_32>)
|
1
curl
Submodule
1
curl
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit acc73edce8c08e24ceadb4f46d2d812cf03fc6bb
|
182
http.cpp
Normal file
182
http.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
#include "http.h"
|
||||
#include "curl/curl.h"
|
||||
#include "curl/easy.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
|
||||
using namespace http;
|
||||
|
||||
HttpClient::HttpClient(uv_loop_t *loop): m_eventLoop(loop) {
|
||||
m_curlMulti = curl_multi_init();
|
||||
curl_multi_setopt(m_curlMulti , CURLMOPT_SOCKETFUNCTION, &HttpClient::curl_socket_cb);
|
||||
curl_multi_setopt(m_curlMulti, CURLMOPT_SOCKETDATA, this);
|
||||
curl_multi_setopt(m_curlMulti, CURLMOPT_TIMERFUNCTION, &HttpClient::curl_timer_cb);
|
||||
curl_multi_setopt(m_curlMulti, CURLMOPT_TIMERDATA, this);
|
||||
m_curlTimer = new uv_timer_t;
|
||||
uv_timer_init(loop, m_curlTimer);
|
||||
m_curlTimer->data = this;
|
||||
m_logger = spdlog::get("httpclient");
|
||||
if (!m_logger) {
|
||||
m_logger = spdlog::stdout_color_mt("httpclient");
|
||||
m_logger->set_level(spdlog::level::debug);
|
||||
}
|
||||
}
|
||||
|
||||
HttpClient::~HttpClient() {
|
||||
uv_close((uv_handle_t*)m_curlTimer, [](uv_handle_t *h){
|
||||
delete h;
|
||||
});
|
||||
spdlog::warn("freeing curl structures is not yet implemented!");
|
||||
}
|
||||
|
||||
bool HttpClient::send_request(std::string method, std::string url, HttpOptions opts, ResponseCallback cb) {
|
||||
m_logger->debug("send request {} {}", method, url);
|
||||
CURL *requestHandle = curl_easy_init();
|
||||
std::pair<decltype(m_requests)::iterator, bool> insertResult = m_requests.emplace(requestHandle, this);
|
||||
if (!insertResult.second) {
|
||||
curl_easy_cleanup(requestHandle);
|
||||
return false;
|
||||
}
|
||||
auto requestData = insertResult.first;
|
||||
requestData->second.callback = cb;
|
||||
requestData->second.response = std::make_unique<HttpResponse>();
|
||||
curl_easy_setopt(requestHandle, CURLOPT_WRITEFUNCTION, &HttpClient::curl_data_cb);
|
||||
curl_easy_setopt(requestHandle, CURLOPT_WRITEDATA, requestHandle);
|
||||
curl_easy_setopt(requestHandle, CURLOPT_PRIVATE, this);
|
||||
curl_easy_setopt(requestHandle, CURLOPT_FOLLOWLOCATION, 1);
|
||||
|
||||
curl_easy_setopt(requestHandle, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, method.c_str());
|
||||
|
||||
if (opts.headers) {
|
||||
curl_slist *headerList = nullptr;
|
||||
auto inputHeaderList = *opts.headers;
|
||||
for (auto i = inputHeaderList.cbegin(); i != inputHeaderList.cend(); ++i) {
|
||||
std::string headerLine;
|
||||
headerLine.reserve(i->first.size() + 1 + i->second.size());
|
||||
headerLine += i->first;
|
||||
headerLine += ':';
|
||||
headerLine += i->second;
|
||||
curl_slist_append(headerList, headerLine.c_str());
|
||||
}
|
||||
curl_easy_setopt(requestHandle, CURLOPT_HTTPHEADER, headerList);
|
||||
requestData->second.requestHeaders = headerList;
|
||||
}
|
||||
|
||||
if (opts.body) {
|
||||
curl_easy_setopt(requestHandle, CURLOPT_POSTFIELDS, opts.body->c_str());
|
||||
}
|
||||
return CURLM_OK == curl_multi_add_handle(m_curlMulti, requestHandle);
|
||||
}
|
||||
|
||||
int HttpClient::curl_socket_cb(CURL *curl, curl_socket_t curlSocket, int action, HttpClient *self, void *socketPtr) {
|
||||
(void)curl;
|
||||
int pollFlags = 0;
|
||||
CurlSocketData_ *data;
|
||||
switch (action) {
|
||||
case CURL_POLL_IN:
|
||||
case CURL_POLL_INOUT:
|
||||
case CURL_POLL_OUT:
|
||||
self->m_logger->debug("polling socket {}", curlSocket);
|
||||
if (!socketPtr) {
|
||||
data = new CurlSocketData_;
|
||||
data->curlSocket = curlSocket;
|
||||
data->client = self;
|
||||
data->pollHandle = new uv_poll_t;
|
||||
uv_poll_init(self->m_eventLoop, data->pollHandle, curlSocket);
|
||||
data->pollHandle->data = data;
|
||||
} else {
|
||||
data = reinterpret_cast<CurlSocketData_*>(socketPtr);
|
||||
}
|
||||
curl_multi_assign(self->m_curlMulti, curlSocket, data);
|
||||
if (action != CURL_POLL_OUT) pollFlags |= UV_READABLE;
|
||||
if (action != CURL_POLL_IN) pollFlags |= UV_WRITABLE;
|
||||
uv_poll_start(data->pollHandle, pollFlags, &HttpClient::uv_socket_cb);
|
||||
break;
|
||||
case CURL_POLL_REMOVE:
|
||||
if (socketPtr) {
|
||||
self->m_logger->debug("removing socket {}", curlSocket);
|
||||
data = reinterpret_cast<CurlSocketData_*>(socketPtr);
|
||||
uv_poll_stop(data->pollHandle);
|
||||
curl_multi_assign(self->m_curlMulti, curlSocket, nullptr);
|
||||
data->pollHandle->data = nullptr;
|
||||
uv_close((uv_handle_t*)data->pollHandle, [](uv_handle_t *h){ delete h; });
|
||||
delete data;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpClient::curl_timer_cb(CURLM *curl, long timeout, HttpClient *self) {
|
||||
if (timeout < 0)
|
||||
uv_timer_stop(self->m_curlTimer);
|
||||
else {
|
||||
if (timeout == 0) timeout = 1;
|
||||
uv_timer_start(self->m_curlTimer, &HttpClient::uv_timeout_cb, timeout, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HttpClient::uv_socket_cb(uv_poll_t *h, int status, int events) {
|
||||
(void)status;
|
||||
auto data = reinterpret_cast<CurlSocketData_*>(h->data);
|
||||
HttpClient *client = data->client;
|
||||
if (!data) return;
|
||||
data->client->m_logger->debug("socket {}", events & UV_READABLE ? (events & UV_WRITABLE ? "readable and writable" : "readable") : "writable");
|
||||
int flags = 0;
|
||||
if (events & UV_READABLE) flags |= CURL_CSELECT_IN;
|
||||
if (events & UV_WRITABLE) flags |= CURL_CSELECT_OUT;
|
||||
int runningHandles;
|
||||
curl_multi_socket_action(data->client->m_curlMulti, data->curlSocket, flags, &runningHandles);
|
||||
client->check_curl_messages();
|
||||
}
|
||||
|
||||
void HttpClient::uv_timeout_cb(uv_timer_t *h) {
|
||||
auto self = reinterpret_cast<HttpClient*>(h->data);
|
||||
self->m_logger->debug("curl timeout");
|
||||
if (self) {
|
||||
int runningHandles;
|
||||
curl_multi_socket_action(self->m_curlMulti, CURL_SOCKET_TIMEOUT, 0, &runningHandles);
|
||||
self->check_curl_messages();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpClient::check_curl_messages() {
|
||||
CURLMsg *msg;
|
||||
int pending;
|
||||
while ((msg = curl_multi_info_read(m_curlMulti, &pending))) {
|
||||
switch (msg->msg) {
|
||||
case CURLMSG_DONE: {
|
||||
CURLcode r = msg->data.result;
|
||||
auto &request = m_requests.at(msg->easy_handle);
|
||||
if (r == CURLE_OK) {
|
||||
m_logger->debug("curl transfer done");
|
||||
long statusCode = 0;
|
||||
curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &statusCode);
|
||||
request.response->status = statusCode;
|
||||
request.callback(std::move(request.response), CURLE_OK);
|
||||
} else {
|
||||
m_logger->error("curl transfer error: {}", (int)r);
|
||||
request.callback(nullptr, r);
|
||||
}
|
||||
curl_multi_remove_handle(m_curlMulti, msg->easy_handle);
|
||||
curl_easy_cleanup(msg->easy_handle);
|
||||
if (request.requestHeaders)
|
||||
curl_slist_free_all(request.requestHeaders);
|
||||
m_requests.erase(msg->easy_handle);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t HttpClient::curl_data_cb(char *ptr, size_t size, size_t nmemb, CURL *userdata) {
|
||||
HttpClient *self;
|
||||
curl_easy_getinfo(userdata, CURLINFO_PRIVATE, &self);
|
||||
HttpRequestData_ &req = self->m_requests.at(userdata);
|
||||
self->m_logger->debug("received {} bytes", nmemb);
|
||||
req.response->body.append(ptr, nmemb);
|
||||
return nmemb;
|
||||
}
|
62
http.h
Normal file
62
http.h
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
#include <curl/curl.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <string>
|
||||
#include <uv.h>
|
||||
#include <vector>
|
||||
|
||||
namespace http {
|
||||
struct HttpResponse {
|
||||
int status;
|
||||
std::string body;
|
||||
};
|
||||
|
||||
struct HttpRequestData_;
|
||||
struct HttpOptions;
|
||||
typedef std::function<void(std::unique_ptr<HttpResponse>, CURLcode)> ResponseCallback;
|
||||
|
||||
class HttpClient {
|
||||
public:
|
||||
HttpClient(uv_loop_t *loop);
|
||||
HttpClient(HttpClient&&) = delete;
|
||||
~HttpClient();
|
||||
bool send_request(std::string method, std::string url, HttpOptions opts, ResponseCallback cb);
|
||||
private:
|
||||
void check_curl_messages();
|
||||
static int curl_socket_cb(CURL *curl, curl_socket_t curlSocket, int action, HttpClient *self, void *socketPtr);
|
||||
static int curl_timer_cb(CURLM *curl, long timeout, HttpClient *self);
|
||||
static size_t curl_data_cb(char *ptr, size_t size, size_t nmemb, CURL *userdata);
|
||||
static void uv_socket_cb(uv_poll_t *h, int status, int events);
|
||||
static void uv_timeout_cb(uv_timer_t *h);
|
||||
|
||||
uv_loop_t *m_eventLoop;
|
||||
uv_timer_t *m_curlTimer;
|
||||
CURLM *m_curlMulti;
|
||||
std::shared_ptr<spdlog::logger> m_logger;
|
||||
std::map<CURL*, HttpRequestData_> m_requests;
|
||||
};
|
||||
|
||||
struct CurlSocketData_ {
|
||||
HttpClient *client;
|
||||
curl_socket_t curlSocket;
|
||||
uv_poll_t *pollHandle;
|
||||
};
|
||||
|
||||
struct HttpRequestData_ {
|
||||
HttpClient *client;
|
||||
curl_slist *requestHeaders = nullptr;
|
||||
ResponseCallback callback;
|
||||
std::unique_ptr<HttpResponse> response;
|
||||
HttpRequestData_(HttpClient *client) {
|
||||
this->client = client;
|
||||
}
|
||||
};
|
||||
|
||||
struct HttpOptions {
|
||||
std::optional<std::vector<std::pair<std::string, std::string>>> headers;
|
||||
std::optional<std::string> body;
|
||||
};
|
||||
}
|
1
libuv
Submodule
1
libuv
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit d4ab6fbba4669935a6bc23645372dfe4ac29ab39
|
21
main.cpp
Normal file
21
main.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "http.h"
|
||||
#include <uv.h>
|
||||
#include <td/telegram/td_api.h>
|
||||
#include <td/telegram/td_api.hpp>
|
||||
|
||||
int main() {
|
||||
uv_loop_t *loop = uv_default_loop();
|
||||
|
||||
http::HttpClient httpClient(loop);
|
||||
spdlog::info("sending request");
|
||||
httpClient.send_request("GET", "https://slavasil.ru/", {}, [](auto resp, CURLcode code){
|
||||
if (code == 0) {
|
||||
spdlog::info("got response! {} {}", resp->status, resp->body);
|
||||
} else {
|
||||
spdlog::error("got error!");
|
||||
}
|
||||
});
|
||||
|
||||
uv_run(loop, UV_RUN_DEFAULT);
|
||||
return 0;
|
||||
}
|
1
spdlog
Submodule
1
spdlog
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 51a0deca2c825f1d4461655a18bb37d6df76646d
|
1
td
Submodule
1
td
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 18618cad563bf65848b64375b295421e014d4ae7
|
Loading…
Reference in New Issue
Block a user