DEV Community

ISNDEV
ISNDEV

Posted on

Blazing-Fast C++ Web Services with `qbm-http`

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 the request, response, and session information.
  • listen() and start(): 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; } 
Enter fullscreen mode Exit fullscreen mode

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 }); 
Enter fullscreen mode Exit fullscreen mode

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:

Top comments (0)