oklib is a C++20 networking library inspired by muduo's reactor architecture.
It uses modern standard-library facilities, avoids Boost, and targets Linux and
macOS.
oklib::base: small concurrency, time, and logging utilities.oklib::net: reactor core, TCP server/client, thread-safe send path.oklib::http: a compact HTTP server for validation and simple services.
cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failureRun the example HTTP server:
./build/examples/oklib_http_server 8080
curl http://127.0.0.1:8080/HTTP handlers can also respond asynchronously from worker threads. This keeps
the EventLoop free while business code waits for a database or another HTTP
service:
oklib::ThreadPool workers("http-workers");
workers.start(4);
server.set_async_http_callback(
[&workers](oklib::http::HttpRequest request,
oklib::http::HttpResponseWriter writer) {
workers.run([request = std::move(request), writer] {
auto response = writer.make_response();
response.set_status_code(oklib::http::HttpStatusCode::ok);
response.set_body("hello " + request.query());
writer.send(std::move(response));
});
});For method-aware routing, use HttpRouter. It dispatches by method + path,
returns 404 for missing paths, and returns 405 Method Not Allowed with an
Allow header when the path exists but the method does not match:
oklib::http::HttpRouter router;
router.get("/", [](const oklib::http::HttpRequest&,
oklib::http::HttpResponseWriter writer) {
auto response = writer.make_response();
response.set_status_code(200);
response.set_body("hello");
writer.send(std::move(response));
});
router.post_streaming("/upload",
[](oklib::http::HttpRequest,
oklib::http::HttpRequestBodyStream body,
oklib::http::HttpResponseWriter writer) {
body.set_complete_callback([writer] {
auto response = writer.make_response();
response.set_status_code(200);
response.set_body("uploaded");
writer.send(std::move(response));
});
});
server.set_router(router);Build TLS support and run the HTTPS demo server with a local self-signed certificate:
cmake -S . -B build-tls -DOKLIB_ENABLE_TLS=ON
cmake --build build-tls
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout /tmp/oklib-demo.key \
-out /tmp/oklib-demo.crt \
-subj /CN=localhost -days 1
./build-tls/examples/oklib_https_server /tmp/oklib-demo.crt /tmp/oklib-demo.key 8443The full HTTP/HTTPS demo routes include raw file upload. This saves the request
body to uploads/photo.jpg relative to the server process working directory:
curl -k -H 'Content-Type: image/jpeg' \
--data-binary @photo.jpg \
'https://127.0.0.1:8443/upload-file?name=photo.jpg'For large responses, stream HTTP/1.1 chunks instead of building one large body:
workers.run([writer] {
auto response = writer.make_response();
response.set_status_code(oklib::http::HttpStatusCode::ok);
response.set_content_type("text/plain");
writer.start_chunked(std::move(response));
writer.write_chunk("part one\n");
writer.write_chunk("part two\n");
writer.finish();
});For large Content-Length or Transfer-Encoding: chunked request bodies, register
streaming body callbacks:
server.set_streaming_http_callback(
[](oklib::http::HttpRequest request,
oklib::http::HttpRequestBodyStream body,
oklib::http::HttpResponseWriter writer) {
body.set_data_callback([](std::string_view chunk) {
// Copy or process chunk before the callback returns.
});
body.set_complete_callback([writer] {
auto response = writer.make_response();
response.set_status_code(oklib::http::HttpStatusCode::ok);
response.set_body("uploaded");
writer.send(std::move(response));
});
});HTTP servers can also observe TCP backpressure through the high-watermark callback:
server.set_high_water_mark_callback(
[](const oklib::net::TcpConnectionPtr&, std::size_t buffered) {
OKLIB_LOG_WARN << "http output buffered bytes=" << buffered;
},
1024 * 1024);Use HttpClient for nonblocking outbound HTTP/1.1 calls from an EventLoop.
It is built on TcpClient, so it uses the same epoll/kqueue reactor path rather
than select:
oklib::http::HttpClient client(&loop, address, "api.internal", "api-client");
client.set_response_callback([](oklib::http::HttpResponseMessage response) {
OKLIB_LOG_INFO << "status=" << response.status_code
<< " body=" << response.body;
});
oklib::http::HttpClientRequest request("GET", "/v1/users");
request.add_header("Accept", "application/json");
client.send(std::move(request));For large responses, use streaming callbacks:
client.set_streaming_response_callback(
[](oklib::http::HttpResponseMessage response,
oklib::http::HttpClientResponseStream stream) {
stream.set_data_callback([](std::string_view chunk) {
// Process or copy the chunk before returning.
});
stream.set_complete_callback([] {
OKLIB_LOG_INFO << "response stream complete";
});
});Requests with Expect: 100-continue send headers first and only send the body
after the peer replies with 100 Continue:
oklib::http::HttpClientRequest upload("POST", "/v1/upload");
upload.add_header("Expect", "100-continue");
upload.set_body(payload);
client.send(std::move(upload));Run the TCP echo examples:
./build/examples/oklib_tcp_echo_server 9000 2
./build/examples/oklib_tcp_echo_client 127.0.0.1 9000 "hello"Configure file logging:
oklib::Logger::set_log_directory("logs");
oklib::Logger::set_file_basename("gateway");
oklib::Logger::set_flush_interval(std::chrono::seconds(3));
OKLIB_LOG_INFO << "server started";
oklib::Logger::flush();By default, logs are written asynchronously to per-level files named from the
current program, such as my_server.info.log and my_server.error.log.
Buffered logs are flushed at least every 3 seconds by default, and the interval
can be changed with Logger::set_flush_interval.
MIT. The design studies muduo's public architecture, but the implementation is new code.