C++ library for easy networking
kurlyk is yet another library implementing HTTP and WebSocket clients for C++. Built as a wrapper around curl and Simple-WebSocket-Server, it provides a simplified interface for working with HTTP and WebSocket in C++ applications. It supports asynchronous HTTP requests with rate limiting and retry attempts, as well as WebSocket connections.
If, for some reason, other libraries such as easyhttp-cpp, curl_request, curlpp-async, curlwrapper, curl-Easy-cpp, curlpp11, easycurl, curl-cpp-wrapper… did not work for you, kurlyk may be worth trying.
- Asynchronous HTTP and WebSocket requests.
- Background worker or synchronous processing via
kurlyk::process(). - HTTP callback API and
std::futureAPI. - Rate limits (partitioned by key), retry, proxy, custom headers, cookies, and timeouts.
- HTTP/HTTPS proxy checks using no-body requests and curl timing metrics.
- Streaming HTTP responses with a callback for each chunk.
- WebSocket events, message sending, and automatic reconnection.
- Bounded admission/backpressure for the HTTP pending queue and WebSocket send queue.
- Bearer token and API-key auth providers (
BearerTokenAuthProvider,ApiKeyAuthProvider). - OAuth2 Authorization Code + PKCE client (
OAuthPkceClient) using standalone HTTP helpers. - Support for C++11 and newer toolchains.
#include <kurlyk.hpp>
#include <iostream>
int main() {
kurlyk::HttpClient client("https://httpbin.org");
auto response = client.get("/ip", kurlyk::QueryParams(), kurlyk::Headers()).get();
if (response && response->ready) {
std::cout << response->content << std::endl;
}
return 0;
}Use kurlyk::init() / kurlyk::deinit() when explicit lifecycle management is required.
#include <kurlyk.hpp>
#include <iostream>
#include <thread>
#include <chrono>
int main() {
kurlyk::WebSocketClient client("wss://echo-websocket.fly.dev/");
client.on_event([](std::unique_ptr<kurlyk::WebSocketEventData> event) {
if (event->event_type == kurlyk::WebSocketEventType::WS_OPEN) {
event->sender->send_message("Hello");
}
if (event->event_type == kurlyk::WebSocketEventType::WS_MESSAGE) {
std::cout << event->message << std::endl;
}
});
client.connect();
std::this_thread::sleep_for(std::chrono::seconds(5));
client.disconnect_and_wait();
return 0;
}Examples are located in the examples folder. Build all repository targets with CMake:
cmake -S . -B build-examples -DKURLYK_BUILD_EXAMPLES=ON
cmake --build build-examples --config ReleaseFor MinGW, you can select the generator and compilers explicitly:
cmake -S . -B build-examples-mingw -G "MinGW Makefiles" `
-DCMAKE_C_COMPILER=gcc `
-DCMAKE_CXX_COMPILER=g++ `
-DKURLYK_BUILD_EXAMPLES=ON
cmake --build build-examples-mingw --config ReleaseFor MinGW, header-only dependencies are available in the repository as git
submodules in the external folder. The fallback CMake options below can
fetch missing binary dependencies, including the pinned Windows curl and
OpenSSL snapshots.
The HTTP client can be used through callbacks, futures, or low-level helpers. With auto-init enabled by default, you can create HttpClient without manual kurlyk::init() / kurlyk::deinit() calls; use the lifecycle functions when synchronous or explicit worker control is required.
#include <kurlyk.hpp>
#include <iostream>
void print_response(const kurlyk::HttpResponsePtr& response) {
if (!response) {
KURLYK_PRINT << "response is null" << std::endl;
return;
}
KURLYK_PRINT
<< "ready: " << std::boolalpha << response->ready << std::endl
<< "response: " << response->content << std::endl
<< "error_code: " << response->error_code.message() << std::endl
<< "status_code: " << response->status_code << std::endl
<< "----------------------------------------" << std::endl;
}Callback overloads of get(...), post(...), and request(...) return bool: true when the request is accepted into the queue and false when it is rejected during admission. The callback itself receives kurlyk::HttpResponsePtr and is not limited to the final response: in streaming mode it is called for each chunk with stream_chunk == true, and during retry it may receive an intermediate non-ready response for a failed attempt. The final result is identified by response && response->ready.
int main() {
kurlyk::HttpClient client("https://httpbin.org");
client.get("/ip", kurlyk::QueryParams(), kurlyk::Headers(),
[](const kurlyk::HttpResponsePtr response) {
print_response(response);
});
client.post("/post", kurlyk::QueryParams(), {{"Content-Type", "application/json"}},
"{\"text\":\"Sample POST Content\"}",
[](const kurlyk::HttpResponsePtr response) {
print_response(response);
});
KURLYK_PRINT << "Press Enter to exit..." << std::endl;
std::cin.get();
return 0;
}If a request is rejected before it enters the pending queue, the future becomes ready immediately and returns an HttpResponse with error_code = QueueLimitExceeded or ShuttingDown.
int main() {
kurlyk::HttpClient client("https://httpbin.org");
auto future_response = client.get("/get", kurlyk::QueryParams{{"param", "value"}}, kurlyk::Headers());
print_response(future_response.get());
auto future_post = client.post("/post", kurlyk::QueryParams(),
kurlyk::Headers{{"Header", "Value"}}, "Async POST Content");
print_response(future_post.get());
return 0;
}int main() {
kurlyk::HttpClient client("https://httpbin.org");
client.set_proxy("127.0.0.1", 8080, "username", "password", kurlyk::ProxyType::PROXY_HTTP);
client.get("/ip", kurlyk::QueryParams(), kurlyk::Headers(),
[](const kurlyk::HttpResponsePtr response) {
print_response(response);
});
KURLYK_PRINT << "Press Enter to exit..." << std::endl;
std::cin.get();
return 0;
}ProxyChecker checks a passive ProxyConfig with one asynchronous HEAD request through the proxy. The test URL is configurable and defaults to https://example.com/; use an endpoint you control when reproducible availability checks matter. Redirect following and proxy tunneling are disabled by default so that a probe measures one endpoint through a regular HTTP proxy. options.proxy_tunnel = true forces HTTP CONNECT tunneling; HTTPS requests through an HTTP proxy may use CONNECT automatically even when this option is false. For an HTTPS endpoint signed by a private CA, set options.ca_file to the CA bundle path. The checker disables retries and does not download the response body, matching the lightweight availability probe used for host discovery.
int main() {
kurlyk::ProxyConfig proxy;
proxy.set_proxy("127.0.0.1", 8080, kurlyk::ProxyType::PROXY_HTTP);
proxy.set_proxy_auth("username", "password");
proxy.use = true;
kurlyk::ProxyCheckOptions options;
options.test_url = "https://service.example/health";
kurlyk::ProxyCheckResult result =
kurlyk::check_proxy(proxy, options).get();
std::cout << "HTTP: " << result.http_ok << '\n'
<< "HTTPS: " << result.https_ok << '\n'
<< "connect: " << result.connect_latency_ms << " ms\n"
<< "TLS/CONNECT: " << result.tls_latency_ms << " ms\n"
<< "TTFB: " << result.ttfb_ms << " ms\n"
<< "total: " << result.total_latency_ms << " ms\n";
}connect_latency_ms is the elapsed curl time until the proxy TCP connection is established and includes name resolution. tls_latency_ms is the interval after TCP connect until TLS setup completes; for HTTPS through an HTTP proxy it also includes CONNECT negotiation. ttfb_ms and total_latency_ms are measured from the beginning of the request. Timeout values are specified in seconds and passed directly to the HTTP transport. reachable means that an HTTP response was received through the proxy; it is not an ICMP ping. Calling check(...) explicitly checks the supplied DTO, so ProxyConfig::use is not consulted.
Low-level helpers are useful when you need the ID of a concrete request or direct access to the standalone HTTP API.
int main() {
const uint64_t request_id = kurlyk::http_get(
"https://httpbin.org/ip",
kurlyk::QueryParams(),
kurlyk::Headers(),
[](const kurlyk::HttpResponsePtr response) {
print_response(response);
});
KURLYK_PRINT << "Request id: " << request_id << std::endl;
KURLYK_PRINT << "Press Enter to exit..." << std::endl;
std::cin.get();
kurlyk::cancel_request_by_id(request_id).wait();
return 0;
}int main() {
auto result = kurlyk::http_get(
"https://httpbin.org/ip",
kurlyk::QueryParams(),
kurlyk::Headers());
KURLYK_PRINT << "Request id: " << result.first << std::endl;
print_response(result.second.get());
return 0;
}WebSocketClient connects to a server, reports events through on_event(...), and lets you send messages through the sender from an event or through the client itself. For a simple scenario, handle WS_OPEN, WS_MESSAGE, WS_CLOSE, and WS_ERROR.
#include <kurlyk.hpp>
#include <thread>
#include <chrono>
int main() {
kurlyk::WebSocketClient client("wss://echo-websocket.fly.dev/");
client.on_event([](std::unique_ptr<kurlyk::WebSocketEventData> event) {
switch (event->event_type) {
case kurlyk::WebSocketEventType::WS_OPEN:
KURLYK_PRINT << "Connection established" << std::endl;
event->sender->send_message("Hello, WebSocket!");
break;
case kurlyk::WebSocketEventType::WS_MESSAGE:
KURLYK_PRINT << "Message received: " << event->message << std::endl;
break;
case kurlyk::WebSocketEventType::WS_CLOSE:
KURLYK_PRINT << "Connection closed: " << event->message
<< "; Status code: " << event->status_code << std::endl;
break;
case kurlyk::WebSocketEventType::WS_ERROR:
KURLYK_PRINT << "Error: " << event->error_code.message() << std::endl;
break;
}
});
client.connect();
std::this_thread::sleep_for(std::chrono::seconds(10));
client.disconnect_and_wait();
return 0;
}send_message(...) returns bool and fits simple code. submit_message(...) returns SubmitResult and lets you distinguish successful queue admission from an admission reject.
client.send_message("Hello");
kurlyk::SubmitResult submit = client.submit_message(
"Hello with admission check",
0,
[](const std::error_code& ec) {
if (ec) {
std::cout << "Send failed: " << ec.message() << std::endl;
}
});
if (!submit) {
std::cout << "WebSocket submit rejected: " << submit.error_code.message() << std::endl;
}To protect the producer, you can limit the size of the outbound WebSocket send/close queue. A value of 0 means the queue is unbounded.
kurlyk::WebSocketClient client("wss://echo-websocket.fly.dev/");
client.set_max_send_queue_size(32);Auto-initialization is available by default in C++11 and newer, so simple examples can create HttpClient or WebSocketClient immediately. Disable auto init and call init() / deinit() when explicit lifecycle control is required.
#define KURLYK_AUTO_INIT 0
#include <kurlyk.hpp>
int main() {
kurlyk::init(true);
kurlyk::HttpClient client("https://httpbin.org");
kurlyk::deinit();
return 0;
}kurlyk::deinit() is the normal cleanup call for both asynchronous init(true) and synchronous init(false) modes. kurlyk::shutdown() remains available for explicit manager cleanup/reset scenarios, but normal manual lifetime management should use the init() / deinit() pair.
A rate limit controls how quickly HTTP requests are released to the network backend. For a simple per-client speed limit, use set_rate_limit_rps(...) or set_rate_limit_rpm(...).
kurlyk::HttpClient client("https://api.example.com");
client.set_rate_limit_rps(1);To share one limit between multiple clients, use HttpRateLimitHandlePtr:
auto limit = kurlyk::create_rate_limit_rps(5);
kurlyk::HttpClient client_a("https://api.example.com");
kurlyk::HttpClient client_b("https://api.example.com");
client_a.set_rate_limit_handle(limit);
client_b.set_rate_limit_handle(limit);HttpRateLimitHandlePtr keeps the physical limit data alive while at least one handle copy exists. Pending, active, and retrying requests copy their assigned handles, so remove_limit(id) or remove_limit(handle) only releases the manager-owned reference and does not invalidate already queued requests.
Prefer handle-based APIs for new code:
HttpClient::set_rate_limit_handle(...)HttpClient::assign_rate_limit_handle(...)- per-request overloads that accept
HttpRateLimitHandlePtr
ID-based APIs such as set_rate_limit_id(...) and per-request long specific_rate_limit_id overloads remain legacy lookup helpers. If the manager-owned handle has already been removed, lookup by ID returns an empty handle and the request is submitted without that additional specific limit.
HTTP requests can be cancelled by the ID of one concrete request or by the ID of a related request group. HttpClient assigns one group_id to all requests created by that client, so HttpClient::cancel_requests() cancels the client's request group.
uint64_t request_id = kurlyk::http_get(
"https://httpbin.org/delay/5",
kurlyk::QueryParams(),
kurlyk::Headers(),
[](kurlyk::HttpResponsePtr response) {
print_response(response);
});
kurlyk::cancel_request_by_id(request_id).wait();HTTP requests have two identifiers:
request_idis the ID of one concrete request and is used bycancel_request_by_id(...).group_idis the ID of a related request group and is used bycancel_requests_by_group_id(...).
For manually constructed low-level requests, set group_id explicitly if you want to cancel a group:
const uint64_t group_id = kurlyk::generate_group_id();
std::unique_ptr<kurlyk::HttpRequest> request(new kurlyk::HttpRequest());
request->request_id = kurlyk::generate_request_id();
request->group_id = group_id;
request->method = "GET";
request->set_url("https://httpbin.org", "/delay/5");
kurlyk::submit_http_request(std::move(request), [](kurlyk::HttpResponsePtr response) {
print_response(response);
});
kurlyk::cancel_requests_by_group_id(group_id).wait();A rate limit slows request dispatch. Backpressure limits how many requests or messages are accepted into a queue at all.
kurlyk::set_max_pending_requests(64);
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", kurlyk::QueryParams());
kurlyk::SubmitResult submit = kurlyk::submit_http_request(
std::move(request),
[](kurlyk::HttpResponsePtr response) {
if (response && response->error_code) {
std::cout << "HTTP runtime error: " << response->error_code.message() << std::endl;
}
});
if (!submit) {
std::cout << "HTTP submit rejected: " << submit.error_code.message() << std::endl;
}Current bounded queues:
- HTTP uses a global pending-queue limit configured with
kurlyk::set_max_pending_requests(...). - WebSocket uses a per-client outbound send/close queue limit configured with
WebSocketClient::set_max_send_queue_size(...). - A value of
0means an unbounded queue.
Public admission helpers:
kurlyk::SubmitResultkurlyk::submit_http_request(...)IWebSocketSender::submit_message(...)IWebSocketSender::submit_close(...)WebSocketClient::submit_message(...)WebSocketClient::submit_close(...)
Compatibility notes:
- older
boolAPIs remain available as wrappers; - HTTP future overloads become ready immediately on admission reject and return an
HttpResponsewitherror_codeinstead of throwingruntime_error; - this pass does not bound
NetworkWorker, WebSocket FSM queues, or accumulated event queues.
Streaming lets you receive an HTTP body in parts instead of waiting for the whole response to finish. The callback is invoked for each chunk with response->stream_chunk == true and response->ready == false, and the last callback is the normal completed response with ready == true.
int main() {
kurlyk::http_post(
"https://api.example.com/v1/chat/completions",
kurlyk::QueryParams(),
kurlyk::Headers{{"Content-Type", "application/json"}},
R"({"stream":true})",
true,
[](kurlyk::HttpResponsePtr response) {
if (!response) return;
if (response->stream_chunk) {
std::cout << response->content;
return;
}
if (response->error_code) {
std::cerr << response->error_code.message() << std::endl;
}
});
std::cin.get();
return 0;
}For requests created through HttpClient, enable streaming on the client:
kurlyk::HttpClient client("http://httpbin.org");
client.set_streaming(true);Use stream_chunk first to classify body chunk callbacks. A chunk callback is not a success marker; the final ready response remains the authoritative transfer result. If at least one streaming chunk was emitted, kurlyk does not retry that transfer automatically, because the caller may already have forwarded bytes to a downstream client.
Retry repeats a failed HTTP request after a configured delay. It is useful for temporary network errors and unstable upstream APIs.
kurlyk::HttpClient client("https://httpbin.org");
client.set_retry_attempts(3, 1000);The number of performed attempts is available in HttpResponse::retry_attempt. For streaming requests, automatic retry is not performed after at least one body chunk has been emitted.
Proxy settings can be configured on HttpClient, and then they apply to requests created by that client.
kurlyk::HttpClient client("https://httpbin.org");
client.set_proxy("127.0.0.1", 8080, "username", "password", kurlyk::ProxyType::PROXY_HTTP);- MSVC
- MinGW (GCC)
- AppleClang (macOS)
The supported language baseline is C++11. Examples and integration tests use C++17 where required by their source code. MSVC, MinGW, and AppleClang are covered by CI; macOS dependencies are provided by the system or Homebrew.
Add the path to the library headers to your project:
kurlyk/include
kurlyk is a header-only library, so including it with #include <kurlyk.hpp> is enough to start using it.
Install the headers and CMake package metadata into a staging prefix:
cmake -S . -B build-package \
-DKURLYK_BUILD_EXAMPLES=OFF
cmake --build build-package
cmake --install build-package --prefix /path/to/kurlyk-installAn external CMake project can then use the exported kurlyk::kurlyk target:
find_package(kurlyk CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE kurlyk::kurlyk)The installed package still requires the transport dependencies used by the
selected configuration: OpenSSL, libcurl, standalone Asio or Boost.Asio,
Simple-WebSocket-Server headers. OAuth PKCE uses the existing OpenSSL dependency.
If Asio or Simple-WebSocket-Server are not discoverable through a package
manager, set KURLYK_ASIO_INCLUDE_DIR and
KURLYK_SIMPLE_WS_INCLUDE_DIR when configuring the consumer.
For an installed package, KURLYK_SIMPLE_WS_INCLUDE_DIR must name an include
root containing simple-websocket-server/client_ws.hpp; the installed contract
is namespaced-only. The flat upstream layout is supported only for source-tree
and build-tree dependency discovery.
To use kurlyk in a MinGW environment, you need these dependencies:
-
For WebSocket:
- Simple-WebSocket-Server
- Boost.Asio or standalone Asio
- OpenSSL (LTS version Win64 OpenSSL v3.5.8)
-
For HTTP:
Header-only dependencies such as Asio and Simple-WebSocket-Server are included
as submodules in the external folder and are used from the checkout when
available. Windows curl and OpenSSL fallback binaries are fetched from their
pinned dependency snapshot repositories when the corresponding fallback option
is enabled.
Add OpenSSL paths to the project, for example for version 3.5.8:
openssl-win64-v3.5.8/include
openssl-win64-v3.5.8/lib/VC/x64/MD
openssl-win64-v3.5.8/bin
For shared OpenSSL builds, link these libraries from lib/VC/x64/MD:
libcrypto.lib
libssl.lib
For static OpenSSL builds, link these libraries instead:
libcrypto_static.lib
libssl_static.lib
Add the path to asio:
asio/asio/include
Standalone Asio is the default backend for native WebSocket support. Kurlyk's
native WebSocket headers define ASIO_STANDALONE before including
Simple-WebSocket-Server, so no extra macro is required:
#include <kurlyk.hpp>To select Boost.Asio for a direct header consumer, define
KURLYK_USE_BOOST_ASIO=1 before including kurlyk.hpp:
#define KURLYK_USE_BOOST_ASIO 1
#include <kurlyk.hpp>Do not define ASIO_STANDALONE together with KURLYK_USE_BOOST_ASIO=1.
Use the same KURLYK_USE_BOOST_ASIO value in every translation unit that
includes Kurlyk WebSocket headers.
Add paths for curl, for example for version 8.22.0:
curl-8.22.0_1-win64-mingw/bin
curl-8.22.0_1-win64-mingw/include
curl-8.22.0_1-win64-mingw/lib
Link the curl libraries from the lib folder:
libcurl.dll.a
Add the path to the library headers:
Simple-WebSocket-Server
Also add these libraries to the linker:
ws2_32
wsock32
crypt32
The library supports automatically downloading dependencies when they are missing. Fallback availability depends on the compiler and linkage type.
| Dependency | MinGW (Shared) | MinGW (Static) | MSVC (Shared) | MSVC (Static) | macOS (system/Homebrew) |
|---|---|---|---|---|---|
| OpenSSL | yes | yes | yes | yes | yes |
| curl | yes | yes | yes | no | yes |
Asio and Simple-WebSocket-Server are header-only libraries and work for all listed build variants.
| Option | Description |
|---|---|
KURLYK_USE_FALLBACK_OPENSSL |
Enables OpenSSL fallback. |
KURLYK_USE_FALLBACK_CURL |
Enables libcurl fallback. |
KURLYK_USE_STANDALONE_ASIO |
Selects standalone Asio when enabled (default); set to OFF to use Boost.Asio. |
KURLYK_USE_FALLBACK_ASIO |
Enables Asio fallback. |
KURLYK_USE_FALLBACK_SIMPLE_WS_SERVER |
Enables Simple-WebSocket-Server fallback. |
KURLYK_OPENSSL_SHARED |
Loads OpenSSL as a shared library when fallback is enabled. |
KURLYK_CURL_SHARED |
Loads libcurl as a shared library when fallback is enabled. |
KURLYK_BUILD_EXAMPLES |
Builds all targets from the examples/ directory. |
Define these macros before including kurlyk.hpp to configure the library:
| Macro | Default | Description |
|---|---|---|
KURLYK_AUTO_INIT |
1 |
Automatically registers managers during static initialization. |
KURLYK_AUTO_INIT_USE_ASYNC |
1 |
Starts the network thread in the background when auto init is enabled. Set to 0 for manual processing. |
KURLYK_HTTP_SUPPORT |
1 |
Enables or disables the HTTP subsystem. |
KURLYK_WEBSOCKET_SUPPORT |
1 |
Enables or disables the WebSocket subsystem. |
KURLYK_USE_BOOST_ASIO |
0 |
Selects Boost.Asio for native WebSocket support. 0 selects standalone Asio and automatically defines ASIO_STANDALONE. |
KURLYK_AUTH_SUPPORT |
1 |
Enables authentication providers (BearerTokenAuthProvider, ApiKeyAuthProvider). |
KURLYK_OAUTH_SUPPORT |
KURLYK_AUTH_SUPPORT |
Enables the OAuth2 PKCE client (OAuthPkceClient). Requires KURLYK_AUTH_SUPPORT=1. |
KURLYK_JSON_SUPPORT |
0 |
Enables nlohmann::json include and JSON-aware types, plus enum-to-JSON helpers in type_utils.hpp. |
| Path | Purpose |
|---|---|
include/ |
Public header-only library. |
include/kurlyk/core |
Core infrastructure with NetworkWorker and base interfaces. |
include/kurlyk/http |
HTTP client and request management. |
include/kurlyk/websocket |
WebSocket client and connection management. |
include/kurlyk/types |
Shared enums, cookie, proxy config, and helpers. |
include/kurlyk/utils |
Encoding, URL, HTTP, path, and error helpers. |
tests/integration |
Cross-platform local HTTP/WebSocket integration build checks. |
tests/odr |
Header-only ODR checks. |
tests/smoke |
Portable header smoke checks. |
tests/package |
Install-tree and external CMake consumer checks. |
examples/ |
Usage examples. |
The repository includes agent configuration and orchestration metadata for AI coding tools (e.g., Claude Code with oh-my-claudecode). The following MCP servers and plugins are recommended for working with the codebase:
| Category | MCP server / plugin | Purpose |
|---|---|---|
| Document lookup | context7 | SDK/API docs resolution before web search |
| Web search | DDG Search (no key) | Primary web search fallback |
| Web search | Tavily | Deep web search and extraction |
| Web content | Fetch | Markdown/JSON/TXT fetch for known URLs |
| Browser automation | Playwright | UI automation and screenshot testing |
| Repo operations | GitHub | Issues, PRs, and repository file operations |
| Code intelligence | Codebase Memory | Graph-based code discovery and call chains |
| Large output | Context-Mode | Batch execution, indexing, and analysis |
| Structural code | AST grep (OMC plugin) | Structural search and replace |
| Runtime | Python REPL (OMC plugin) | In-session script execution |
| Diagnostics | LSP | Symbols, definitions, and diagnostics |
For the full tool priority chain and fallback rules, see .claude/rules/tool-priority.md.
Run the Windows integration suite:
powershell -ExecutionPolicy Bypass -File tests/integration/run_integration_tests.ps1Run the ODR suite:
powershell -ExecutionPolicy Bypass -File tests/odr/run_odr_tests.ps1Run the auth unit tests:
cmake -S tests/auth -B build-auth-tests -G Ninja \
-DKURLYK_BUILD_EXAMPLES=OFF \
-DKURLYK_USE_FALLBACK_ASIO=ON \
-DKURLYK_USE_FALLBACK_SIMPLE_WS_SERVER=ON
cmake --build build-auth-tests
ctest --test-dir build-auth-testsOn macOS, install the system dependencies with Homebrew and point CMake at the keg-only OpenSSL installation:
brew install ninja curl openssl@3
OSSL="$(brew --prefix openssl@3)"
CURL="$(brew --prefix curl)"
PATH="$CURL/bin:$PATH" cmake -S tests/integration -B build-macos -G Ninja \
-DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD_REQUIRED=ON \
-DOPENSSL_ROOT_DIR="$OSSL" -DCMAKE_PREFIX_PATH="$OSSL;$CURL" \
-DKURLYK_USE_FALLBACK_ASIO=ON \
-DKURLYK_USE_FALLBACK_SIMPLE_WS_SERVER=ON
cmake --build build-macos
ctest --test-dir build-macos --output-on-failureBuild the portable smoke test manually:
c++ tests/smoke/header_smoke.cpp -Iinclude -std=c++11 -o header_smoke
./header_smoke
c++ tests/smoke/header_smoke.cpp -Iinclude -std=c++17 -o header_smoke
./header_smoke
c++ tests/smoke/http_header_smoke.cpp -Iinclude -std=c++11 -o http_header_smoke
./http_header_smoke
c++ tests/smoke/proxy_config_smoke.cpp -Iinclude -std=c++11 -o proxy_config_smoke
./proxy_config_smoke
cmake -S tests/smoke -B build-full-cpp11-smoke -G Ninja \
-DCMAKE_CXX_STANDARD=11 -DCMAKE_CXX_STANDARD_REQUIRED=ON \
-DKURLYK_USE_STANDALONE_ASIO=ON \
-DKURLYK_USE_FALLBACK_ASIO=ON \
-DKURLYK_USE_FALLBACK_SIMPLE_WS_SERVER=ON
cmake --build build-full-cpp11-smokekurlyk provides lightweight, header-only authentication helpers on top of the existing HTTP layer.
BearerTokenAuthProvider— injectsAuthorization: Bearer <token>.ApiKeyAuthProvider— injects an API key as a custom header or as a query parameter (with automatic percent-encoding).OAuthPkceClient— builds authorization URLs and exchanges authorization codes for tokens using PKCE (RFC 7636). It uses the standalonekurlyk::http_posthelper, so it does not require anHttpClientinstance.
See the full guides for details:
| Platform | Coverage |
|---|---|
| Windows | MinGW and MSVC integration builds with fallback dependencies, HTTP backpressure regression, and local WebSocket integration coverage. |
| Windows extras | ODR checks for singleton and auto-initialization headers. |
| Linux | C++11/C++17 header smoke, C++11 HTTP/proxy checks, full C++11 HTTP/WebSocket compile-only smoke, and C++17 integration examples. |
| macOS | C++11/C++17 header smoke, C++11 HTTP/proxy checks, full C++11 HTTP/WebSocket compile-only smoke, and C++17 integration tests. |
| Packaging | CMake install-tree validation and an external C++11 consumer check. |
Generate Doxygen documentation:
doxygen DoxyfilePublished documentation: https://newyaroslav.github.io/kurlyk/.
This library is distributed under the MIT license. See LICENSE for details.
If you have questions or issues while using the library, consult the documentation or ask a question in GitHub Issues.
In short, kurlyk!

