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)