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)