Pistache: A Powerful C++ Library for High-Performance HTTP and REST Frameworks

Pistache is a high-performance HTTP and REST framework written in modern C++, fully compliant with the C++17 standard, providing a clear and user-friendly API for building efficient web services and RESTful APIs. Below, I will introduce the core features of Pistache, installation methods, basic usage, and some advanced functionalities.

🚀 Pistache: A High-Performance HTTP and REST Framework in Modern C++

Framework Overview

Pistache is a modern, elegant C++ HTTP and REST framework focused on performance and providing a graceful asynchronous API. It is entirely written in pure C++17, offering a clear API that makes building high-performance web applications and RESTful services straightforward and intuitive.

The design philosophy of Pistache is to provide a simple, intuitive, and easy-to-use API for building RESTful services, with its core based on an asynchronous I/O high-performance network stack, utilizing features from C++11 and C++14 to simplify and accelerate server-side programming.

Installation and Configuration

System Dependencies

Before using Pistache, ensure that the necessary dependencies are installed on your system. On Ubuntu, you can run the following commands to install:

sudo apt-get update
sudo apt-get install -y cmake g++ meson doxygen gtest git openssl rapidjson libhiredis-dev

Installing Pistache

There are several methods to install Pistache:

Method 1: Install via PPA (Ubuntu only)

sudo add-apt-repository ppa:pistache+team/unstable
sudo apt update
sudo apt install libpistache-dev

Method 2: Compile from Source

git clone https://github.com/pistacheio/pistache.git
cd pistache
meson setup build
meson install -C build

Or use CMake:

mkdir build
cd build
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release ..
make
sudo make install

Note: Pistache currently does not support Windows but works well under WSL.

Core Architecture and Features

Asynchronous I/O Architecture

The core of Pistache is based on an asynchronous I/O high-performance network stack, utilizing an event-driven architecture and non-blocking I/O operations, capable of handling a large number of concurrent connections. Its asynchronous write mechanism is implemented based on Promise and PollableQueue, allowing it to maintain excellent performance in high-load environments.

Component Structure

The Pistache project typically includes the following core directory structure:

  • src: Core source code, including implementations of HTTP server, client, routing, and other components
  • include: Header files for external use of the Pistache library
  • examples: Example programs to help understand how to use Pistache to build REST APIs and services
  • docs: Documentation materials
  • cmake: Configuration files for the CMake build system

Basic Usage and Examples

Simple Hello World Server

Below is a basic example of a Pistache server:

#include <pistache/endpoint.h>

using namespace Pistache;

class HelloHandler : public Http::Handler {
public:
    HTTP_PROTOTYPE(HelloHandler)

    void onRequest(const Http::Request& request, Http::ResponseWriter response) override {
        response.send(Http::Code::Ok, "Hello, World!");
    }
};

int main() {
    Http::listenAndServe<HelloHandler>("*:9080");
    return 0;
}

Compilation command:

g++ -std=c++17 -o hello_server hello_server.cpp -lpistache -pthread

Creating a Server Using the Endpoint Class

For scenarios requiring more control, you can use the Endpoint class:

#include <pistache/endpoint.h>

using namespace Pistache;

class HelloHandler : public Http::Handler {
public:
    HTTP_PROTOTYPE(HelloHandler)

    void onRequest(const Http::Request& request, Http::ResponseWriter response) override {
        response.send(Http::Code::Ok, "Hello, World!\n");
    }
};

int main() {
    Address addr(Ipv4::any(), Port(9080));

    auto opts = Http::Endpoint::options().threads(1);
    Http::Endpoint server(addr);
    server.init(opts);
    server.setHandler(Http::make_handler<HelloHandler>());
    server.serve();
}

Implementing a RESTful API

Pistache is well-suited for building RESTful APIs. Below is an example that handles GET and POST requests:

#include <pistache/http.h>
#include <iostream>

using namespace Pistache;

struct ApiHandler : public Http::Handler {
    void handle(Http::Request req, Http::ResponseWriter res) override {
        if (req.method() == Http::Method::Get) {
            res.send(Http::Code::Ok, "Welcome to Pistache!");
        } else if (req.method() == Http::Method::Post) {
            auto body = req.payload().toString();
            std::cout << "Received POST payload: " << body << std::endl;
            res.send(Http::Code::Ok, "Payload received");
        }
    }
};

int main() {
    auto addr = Address::fromIpPort("0.0.0.0", 8080);
    auto server = Http::Server(addr);

    server.setHandler(std::make_shared<ApiHandler>());
    server.listen();

    std::cout << "Server listening on port 8080" << std::endl;
    server.wait();
}

Advanced Features

Routing System

Pistache provides a flexible routing system that allows defining complex URL patterns and handling functions:

#include <pistache/rest.h>

using namespace Pistache;
using namespace Pistache::Rest;

class UserHandler : public Http::Handler {
public:
    HTTP_PROTOTYPE(UserHandler)

    void onRequest(const Http::Request& request, Http::ResponseWriter response) override {
        auto segments = request.pathSegments();

        if (segments.size() == 2 && segments[0] == "users") {
            if (request.method() == Http::Method::Get) {
                // Get specific user information
                std::string userId = segments[1];
                response.send(Http::Code::Ok, "User ID: " + userId);
            }
        } else if (request.path() == "/users" && request.method() == Http::Method::Get) {
            // Get all user list
            response.send(Http::Code::Ok, "User list");
        } else {
            response.send(Http::Code::Not_Found, "Not found");
        }
    }
};

Response Streaming

For scenarios requiring data streaming, Pistache provides the ResponseStream class:

class StreamHandler : public Http::Handler {
public:
    HTTP_PROTOTYPE(StreamHandler)

    void onRequest(const Http::Request& request, Http::ResponseWriter response) override {
        // Set headers first
        response.headers()
           .add<Header::Server>("MyServer")
           .add<Header::ContentType>(MIME(Text, Plain));

        auto stream = response.stream(Http::Code::Ok);
        stream << "First chunk of data";
        stream << "Second chunk of data";
        stream << ends;  // End stream
    }
};

Request Parsing

Pistache allows easy access to various parts of the request:

class DetailHandler : public Http::Handler {
public:
    HTTP_PROTOTYPE(DetailHandler)

    void onRequest(const Http::Request& request, Http::ResponseWriter response) override {
        // Get query parameters
        auto query = request.query();
        if (query.has("name")) {
            auto name = query.get("name").value_or("");
            response.send(Http::Code::Ok, "Hello, " + name);
            return;
        }

        // Get headers
        auto headers = request.headers();
        if (headers.has<Header::UserAgent>()) {
            auto agent = headers.get<Header::UserAgent>();
            // Process User-Agent information
        }

        // Get request body
        auto body = request.body();
        if (!body.empty()) {
            response.send(Http::Code::Ok, "Received body: " + body);
        } else {
            response.send(Http::Code::Ok, "No body received");
        }
    }
};

Performance Optimization

Thread Configuration

By configuring the number of threads appropriately, server performance can be optimized:

int main() {
    Address addr(Ipv4::any(), Port(9080));

    // Set the number of threads based on CPU core count
    auto opts = Http::Endpoint::options()
        .threads(std::thread::hardware_concurrency())
        .flags(Tcp::Options::ReuseAddr);

    Http::Endpoint server(addr);
    server.init(opts);
    server.setHandler(Http::make_handler<HelloHandler>());
    server.serve();
}

Asynchronous Processing

Pistache’s asynchronous mechanism is based on Promise, allowing for non-blocking operations:

class AsyncHandler : public Http::Handler {
public:
    HTTP_PROTOTYPE(AsyncHandler)

    void onRequest(const Http::Request& request, Http::ResponseWriter response) override {
        // Simulate asynchronous operation
        Async::Promise<void> promise = Async::Promise<void>([&](auto& resolve, auto&) {
            // Perform time-consuming operation
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            response.send(Http::Code::Ok, "Async operation completed");
            resolve();
        });
    }
};

Real-World Application Scenarios

Pistache is suitable for various scenarios:

  1. RESTful API Server: Build high-performance REST API backend services
  2. Real-Time Communication Server: Handle real-time applications with a large number of concurrent connections
  3. High-Performance Web Applications: Web services requiring extreme performance
  4. Microservices Architecture: As a single service component in a microservices architecture

Ecological Integration

Pistache can integrate well with other C++ libraries:

  • Boost.Asio: Provides advanced networking capabilities
  • nlohmann/json: Handles JSON formatted requests and responses
  • Catch2: Write and run test cases to ensure code quality

Conclusion

Pistache is a powerful and high-performance modern C++ HTTP framework that, through its concise API and robust asynchronous capabilities, makes building high-performance web services simple and efficient. Whether building a simple HTTP server or a complex RESTful API, Pistache can deliver outstanding performance and flexibility.

Leave a Comment