Kurlyk
Loading...
Searching...
No Matches
Kurlyk Library

Introduction

Kurlyk is a header-only C++ library that currently wraps libcurl and Simple-WebSocket-Server** to provide convenient HTTP and WebSocket clients. Its core is backend-agnostic, so alternative implementations can be plugged in if needed—planned support includes an Emscripten backend. The library hides much of the networking boilerplate and offers an easy way to issue asynchronous requests, process WebSocket events and apply rate limiting.

Features

  • HTTP and WebSocket clients with a common, class-based API.
  • Asynchronous or synchronous execution via a background worker.
  • Rate limiting for both HTTP requests and WebSocket messages.
  • Optional bounded admission for selected HTTP and WebSocket queues.
  • Automatic reconnection logic for WebSocket connections.
  • Proxy support, configurable timeouts and custom headers.
  • Retry logic and ability to cancel outstanding requests.
  • Standalone helper functions for one-off HTTP calls.
  • Header-only design, works with C++11 and newer.

Usage

By default Kurlyk initializes itself before main and shuts down automatically thanks to an internal startup::AutoInitializer. It registers HttpRequestManager and WebSocketManager with the core::NetworkWorker and starts the worker thread using the KURLYK_AUTO_INIT_USE_ASYNC setting. A shutdown happens when the program exits, so you can usually skip explicit kurlyk::init() and kurlyk::deinit() calls.

Manual control is still possible by disabling automatic startup:

#define KURLYK_AUTO_INIT 0 // opt out of AutoInitializer
#include <kurlyk.hpp>
int main() {
kurlyk::init(true); // start worker thread explicitly
// ... use HTTP or WebSocket clients ...
kurlyk::deinit(); // stop async processing or clean up sync state
}
Main header file for the Kurlyk library, providing HTTP and WebSocket support.
void init(const bool use_async=true)
Initializes the Kurlyk library, setting up necessary managers and the network worker.
Definition runtime.hpp:13
void deinit()
Deinitializes the Kurlyk library, stopping async processing or cleaning up synchronous state.
Definition runtime.hpp:26

HTTP Client

HTTP callback threading

HTTP callbacks run on the NetworkWorker processing path. With asynchronous startup this is the background worker thread; with synchronous startup it is the thread that calls kurlyk::process(). Keep callbacks short and hand work off to application-owned queues or threads when needed, because blocking a callback delays other HTTP/WebSocket work handled by the same worker.

Basic GET request

kurlyk::HttpClient http("https://httpbin.org");
http.get("/ip", {}, {}, [](kurlyk::HttpResponsePtr res) {
std::cout << res->content << std::endl;
});
Concrete HTTP client for making requests to a specific host.
std::unique_ptr< HttpResponse > HttpResponsePtr
A unique pointer to an HttpResponse object for memory management.

Using a proxy

kurlyk::HttpClient client("https://httpbin.org");
client.set_proxy("127.0.0.1", 8080, "user", "pass", kurlyk::ProxyType::HTTP);
client.get("/ip", {}, {}, [](kurlyk::HttpResponsePtr res) {
std::cout << res->content << std::endl;
});

Measuring latency

#include <chrono>
kurlyk::HttpClient client("https://httpbin.org");
auto start = std::chrono::steady_clock::now();
client.get("/ip", {}, {}, [start](kurlyk::HttpResponsePtr res) {
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);
std::cout << "Ping: " << ms.count() << " ms" << std::endl;
});

Calling HTTP requests

Requests can be issued with callbacks, futures or standalone functions:

kurlyk::HttpClient cli("https://httpbin.org");
auto fut = cli.get("/ip", {}, {});
auto resp = fut.get();
auto [id, future] = kurlyk::http_get("https://httpbin.org/ip", {}, {});
auto resp2 = future.get();
uint64_t http_get(const std::string &url, const QueryParams &query, const Headers &headers, HttpResponseCallback callback)
Sends an asynchronous HTTP GET request with a callback.
Definition utils.hpp:362

Streaming response chunks

Enable streaming when the caller needs body chunks before the transfer finishes, for example when proxying SSE responses. Chunk callbacks carry stream_chunk == true and ready == false; the final callback keeps the usual ready == true contract.

Use stream_chunk first to classify body chunk callbacks. A chunk callback is not a success marker; the final ready response remains authoritative for the transfer result. status_code on a chunk contains the current HTTP status when libcurl has one, but it is not a completion marker. Non-ready callbacks with stream_chunk == false are reserved for non-final request states such as failed attempts before retry and may carry error_code. If at least one streaming chunk was emitted, kurlyk does not retry that transfer automatically, since the caller may already have forwarded bytes to a downstream client.

"https://api.example.com/v1/chat/completions",
{},
{{"Content-Type", "application/json"}},
R"({"stream":true})",
true,
if (!res)
return;
if (res->stream_chunk) {
std::cout << res->content;
return;
}
if (res->error_code)
std::cerr << res->error_code.message() << std::endl;
});
uint64_t http_post(const std::string &url, const QueryParams &query, const Headers &headers, const std::string &content, HttpResponseCallback callback)
Sends an asynchronous HTTP POST request with a callback.
Definition utils.hpp:430

HTTP admission and queue limits

Kurlyk separates rate limiting from bounded admission. Rate limiting throttles when requests are dispatched to the backend, while backpressure limits how many pending requests are accepted into the global HTTP queue.

std::unique_ptr<kurlyk::HttpRequest> request(new kurlyk::HttpRequest());
request->request_id = kurlyk::generate_request_id();
request->method = "GET";
request->set_url("https://httpbin.org/get", {});
std::move(request),
if (res && res->error_code)
std::cerr << res->error_code.message() << std::endl;
});
if (!submit)
std::cerr << "Rejected: " << submit.error_code.message() << std::endl;
Represents an HTTP request.
void set_max_pending_requests(std::size_t max_pending_requests)
Sets the maximum number of requests accepted into the global pending queue.
Definition utils.hpp:51
uint64_t generate_request_id()
Generates a new unique request ID.
Definition utils.hpp:45
SubmitResult submit_http_request(std::unique_ptr< HttpRequest > request_ptr, HttpResponseCallback callback)
Attempts to submit an HTTP request and reports the admission result.
Definition utils.hpp:136
Represents the synchronous result of trying to enqueue or submit work.
std::error_code error_code
Describes the rejection reason when accepted is false.

For future-based HTTP calls, an admission reject does not throw a runtime_error; instead the future becomes ready immediately and returns an HttpResponse with error_code set to QueueLimitExceeded or ShuttingDown.

WebSocket Client

kurlyk::WebSocketClient ws("wss://echo-websocket.fly.dev/");
ws.on_event([](std::unique_ptr<kurlyk::WebSocketEventData> e) {
std::cout << "Message: " << e->message << std::endl;
});
ws.connect_and_wait();
ws.send_message("Hello!");
ws.disconnect_and_wait();
Public facade for managing WebSocket connections, events, and message sending.
@ WS_MESSAGE
Message received.
Definition enums.hpp:33

WebSocket send queue admission

WebSocket backpressure applies to the per-client outbound send queue. It does not bound the internal FSM queue or stored event queue in this pass.

kurlyk::WebSocketClient ws("wss://echo-websocket.fly.dev/");
ws.set_max_send_queue_size(32);
kurlyk::SubmitResult submit = ws.submit_message("Hello with admission check");
if (!submit)
std::cerr << "Rejected: " << submit.error_code.message() << std::endl;

Error Handling

The library centralizes exception reporting through the core::NetworkWorker. Applications may register callbacks with kurlyk::add_error_handler to intercept errors raised inside Kurlyk and forward them to a logging system or metrics collector.

Handlers use the kurlyk::core::NetworkWorker::ErrorHandler signature:

using ErrorHandler = std::function<
void(const std::exception&,
const char* msg,
const char* file,
int line,
const char* func)>;

Whenever an internal operation encounters an exception and invokes KURLYK_HANDLE_ERROR, all registered handlers receive the exception, an optional message, source file, line and function. This allows detailed logging of issues without terminating the program.

For synchronous admission rejects, the public API uses kurlyk::SubmitResult and std::error_code values such as QueueLimitExceeded and ShuttingDown instead of reporting the condition through KURLYK_HANDLE_ERROR.

kurlyk::add_error_handler([](const std::exception& ex,
const char* msg,
const char* file,
int line,
const char* func) {
std::cerr << "Kurlyk error: " << msg
<< " (" << ex.what() << ") at "
<< file << ':' << line
<< " in " << func << std::endl;
});
void add_error_handler(::kurlyk::core::NetworkWorker::ErrorHandler handler)
Registers a global error handler for the network worker.
Definition runtime.hpp:44

Installation

Add the include directory to your compiler search path and include <kurlyk.hpp> in your sources. The repository contains third party dependencies used by the examples, but for your own project you need libcurl, OpenSSL and either Boost.Asio or standalone Asio together with Simple-WebSocket-Server. Kurlyk itself is header-only and works with C++11 or later.

Repository examples can also be built through CMake by configuring the root project with -DKURLYK_BUILD_EXAMPLES=ON.

Configuration Macros

Several macros can be defined before including the library to tailor the build:

Repository

Kurlyk Library GitHub repository.

License

This library is licensed under the MIT License. See the LICENSE file for more details.