Forget slow, bloated web frameworks. Learn how to build high-performance, asynchronous HTTP/1.1 and HTTP/2 servers in modern C++ using qb
's powerful qbm-http
module.
Target Audience: Intermediate C++ developers looking to build fast and scalable web backends.
GitHub: https://github.com/isndev/qbm-http
When you think of C++ and web servers, you might think of old, complex libraries. The qbm-http
module, part of the qb
actor framework, changes that. It provides a modern, expressive, and incredibly fast way to build HTTP services, from simple REST APIs to complex, real-time applications.
Built on qb-io
's asynchronous, non-blocking core, qbm-http
is designed for high throughput and low latency, making it perfect for performance-critical web services.
Core Features of qbm-http
- Asynchronous by Default: Handles thousands of concurrent connections without breaking a sweat.
- Expressive Routing: A powerful, fluent API for defining routes, including path parameters and wildcards.
- Middleware Support: Easily add cross-cutting concerns like logging, authentication, CORS, and compression.
- Controller Pattern: Organize your API into clean, reusable
qb::http::Controller
classes. - HTTP/2 Ready: Full support for HTTP/2, including multiplexing and server push, for modern web performance.
- Seamless Integration: Works perfectly within the
qb
actor model, allowing your HTTP server to be just one part of a larger concurrent system.
Your First HTTP Server
Let's build a simple "Hello World" server. The entire server, with routing, fits inside a single qb
actor.
The key components are:
-
qb::http::Server<>
: A base class that gives our actor HTTP server capabilities. -
router()
: The entry point for defining URL endpoints. -
router().get(path, handler)
: Defines a handler for an HTTP GET request. -
ctx
(Context): An object passed to every handler, containing therequest
,response
, and session information. -
listen()
andstart()
: Methods to bind the server to a port and begin accepting connections.
From examples/qbm/http/01_hello_world_server.cpp
#include <iostream> #include <qb/main.h> #include <http/http.h> // Define our HTTP server actor by inheriting from qb::Actor and qb::http::Server class HelloWorldServer : public qb::Actor, public qb::http::Server<> { public: HelloWorldServer() = default; // onInit is the entry point for actor initialization bool onInit() override { std::cout << "Initializing Hello World HTTP Server..." << std::endl; // 1. Set up a route for the root path "/" router().get("/", [](auto ctx) { ctx->response().status() = qb::http::Status::OK; ctx->response().add_header("Content-Type", "text/plain; charset=utf-8"); ctx->response().body() = "Hello, World!\nWelcome to the QB HTTP Framework!"; // 2. Signal that the request is complete ctx->complete(); }); // 3. Set up another route for a JSON response router().get("/hello", [](auto ctx) { ctx->response().status() = qb::http::Status::OK; ctx->response().add_header("Content-Type", "application/json"); ctx->response().body() = R"({"message": "Hello from QB!", "framework": "qb-http"})"; ctx->complete(); }); // 4. Compile the router after defining all routes router().compile(); // 5. Start listening for connections on port 8080 if (listen({"tcp://0.0.0.0:8080"})) { start(); // Starts the internal acceptor std::cout << "Server listening on http://localhost:8080" << std::endl; std::cout << "Routes:\n GET /\n GET /hello" << std::endl; } else { std::cerr << "Failed to start listening on port 8080" << std::endl; return false; } return true; } }; int main() { try { qb::Main engine; // Add our server actor to the engine engine.addActor<HelloWorldServer>(0); // Start the engine and wait for it to stop (e.g., via Ctrl+C) engine.start(); engine.join(); std::cout << "Server stopped gracefully." << std::endl; } catch (const std::exception& e) { std::cerr << "Server error: " << e.what() << std::endl; return 1; } return 0; }
The Power of Asynchronous Handlers
In the example above, the handlers are simple. But what if you need to query a database or call another microservice? qbm-http
's asynchronous nature shines here. A handler can initiate a long-running operation and return immediately, freeing up the server thread to handle other requests. When the operation completes, its callback finishes the HTTP response.
#include <qb/io/async.h> // For qb::io::async::callback // ... inside your server actor ... router().get("/async/data", [](auto ctx) { std::cout << "Request received. Starting simulated DB query..." << std::endl; // Simulate a 1-second database query without blocking the server qb::io::async::callback([ctx]() { // This lambda executes after 1 second on the actor's event loop // Check if the request context is still valid (client might have disconnected) if (!ctx->session() || !ctx->session()->is_alive()) return; std::cout << "DB query finished. Sending response." << std::endl; ctx->response().status() = qb::http::Status::OK; ctx->response().add_header("Content-Type", "application/json"); ctx->response().body() = R"({"data": "This data came from an async operation"})"; // Complete the request ctx->complete(); }, 1.0); // 1.0 second delay });
This pattern allows a single-threaded qb
actor to handle tens of thousands of concurrent requests that are waiting on I/O, making it incredibly efficient.
qbm-http
combines the raw performance of C++ with the modern, high-level abstractions you expect from a web framework. Whether you're building a simple API or a complex distributed backend, it provides the tools to do it efficiently and elegantly.
Next Steps:
- Explore
qbm-http
on GitHub: https://github.com/isndev/qbm-http - Check out more examples: https://github.com/isndev/qb-examples/tree/main/examples/qbm/http
- Learn about routing and middleware in our next article!
Top comments (0)