Skip to content

Latest commit

 

History

History
600 lines (439 loc) · 19.3 KB

File metadata and controls

600 lines (439 loc) · 19.3 KB

REST API for C++

This is a C++ library originally written for accessing GitHub REST API v3. It is now organized to work with any REST API.

It supports three interchangeable HTTP backends for connecting to remote API servers: Qt 6/5, libcurl and cpp-httplib. All backends expose the same IConnection API, so applications can choose the HTTP stack that best fits their existing dependencies.

Requirements

  • CMake 3.16 or newer.
  • A C++23 compiler. The public API uses C++23 library facilities such as std::expected.
  • At least one HTTP backend must be enabled at configure time.
Backend option Public factory header Factory function External dependency
CppRestAPI_QtBackend=ON <cpp_restapi/create_qt_connection.hpp> cpp_restapi::createQtConnection() Qt Network/Core
CppRestAPI_CurlBackend=ON <cpp_restapi/create_curl_connection.hpp> cpp_restapi::createCurlConnection() libcurl
CppRestAPI_CppHttplibBackend=ON <cpp_restapi/create_cpp-httplib_connection.hpp> cpp_restapi::createCppHttplibConnection() cpp-httplib

Typical applications enable one backend: for example, Qt applications usually use the Qt backend, while non-Qt applications can choose libcurl or cpp-httplib. Multiple backends can be enabled in one build because each backend provides its own factory function behind the same IConnection interface. This is mainly useful for examples, tests or comparing backends rather than for normal application builds. If you do enable more than one backend, all corresponding dependencies must be available to CMake.

The Qt backend uses Qt 6 by default and falls back to Qt 5 when Qt 6 is not found. Set CppRestAPI_UseQt5 CMake variable to TRUE to force Qt 5 usage when both versions are available.

How to use it

This is a CMake-based project and is primarily meant to be included as a subproject.

Simply embed cpp_restapi's sources in your project, choose the HTTP backend you want to use and include the cpp_restapi project in your CMakeLists.txt like this:

# Pick one backend for a typical application.
set(CppRestAPI_CurlBackend ON)         # libcurl backend
# set(CppRestAPI_QtBackend ON)         # Qt backend
# set(CppRestAPI_CppHttplibBackend ON) # cpp-httplib backend
add_subdirectory(cpp_restapi)

Then you can link your application against cpp_restapi:

target_link_libraries(app
    PRIVATE
        cpp_restapi
)
JSON-aware pagination:

cpp_restapi::LinkHeaderPaginationStrategy is an RFC 5988 Link-header based pagination strategy with JSON-aware merging (concatenates arrays, deep-merges objects).

This is useful for REST APIs that split large list responses into pages. For example, GitHub returns only one page of issues, releases or repositories at a time and exposes the next page through the HTTP Link header. The pagination strategy lets the library follow those links and return one merged response instead of forcing each caller to repeat the same "read header, fetch next page, merge JSON" loop.

It is built by default (controlled by the CppRestAPI_JsonPagination CMake option) because the GitHub helpers use it for paginated endpoints. It adds a dependency on the jsoncpp library. To drop the jsoncpp dependency entirely, disable both JSON pagination and GitHub helpers: -DCppRestAPI_GitHub=OFF -DCppRestAPI_JsonPagination=OFF.

GitHub helpers:

cpp_restapi::GitHub::ConnectionBuilder and cpp_restapi::GitHub::Request are convenience wrappers for the GitHub REST API. They are built by default (controlled by the CppRestAPI_GitHub CMake option; enabling it automatically enables CppRestAPI_JsonPagination). Disable with -DCppRestAPI_GitHub=OFF if not needed.

Examples

Simplest usage

#include <iostream>

#include <cpp_restapi/create_curl_connection.hpp>
#include <cpp_restapi/iconnection.hpp>


int main(int argc, char** argv)
{
    // Access The Star Wars API
    auto connection = cpp_restapi::createCurlConnection("https://swapi.dev/api", {});

    // fetch() returns std::expected<std::string, HttpError>
    for (const auto& endpoint: {"people/1", "starships/12/"})
    {
        const auto result = connection->fetch(endpoint);
        if (result)
            std::cout << result.value() << '\n';
        else
            std::cerr << "Error " << result.error().statusCode << ": " << result.error().message << '\n';
    }

    return 0;
}

This example accesses The Star Wars API using the libcurl backend.
fetch() returns std::expected<std::string, HttpError> — on success the response body is available via value(), on failure the HttpError carries the HTTP status code, response body and a human-readable message.

Qt version

#include <iostream>
#include <QCoreApplication>
#include <QNetworkAccessManager>

#include <cpp_restapi/create_qt_connection.hpp>
#include <cpp_restapi/iconnection.hpp>


int main(int argc, char** argv)
{
    QCoreApplication qapp(argc, argv);
    QNetworkAccessManager manager;

    // Access The Star Wars API
    auto connection = cpp_restapi::createQtConnection(manager, "https://swapi.dev/api", {});

    // fetch() returns std::expected<std::string, HttpError>
    for (const auto& endpoint: {"people/1", "starships/12/"})
    {
        const auto result = connection->fetch(endpoint);
        if (result)
            std::cout << result.value() << '\n';
        else
            std::cerr << "Error " << result.error().statusCode << ": " << result.error().message << '\n';
    }

    return 0;
}

cpp-httplib version

#include <iostream>

#include <cpp_restapi/create_cpp-httplib_connection.hpp>
#include <cpp_restapi/iconnection.hpp>


int main(int argc, char** argv)
{
    // Access The Star Wars API
    auto connection = cpp_restapi::createCppHttplibConnection("https://swapi.dev/api", {});

    // fetch() returns std::expected<std::string, HttpError>
    for (const auto& endpoint: {"people/1", "starships/12/"})
    {
        const auto result = connection->fetch(endpoint);
        if (result)
            std::cout << result.value() << '\n';
        else
            std::cerr << "Error " << result.error().statusCode << ": " << result.error().message << '\n';
    }

    return 0;
}

Dedicated GitHub helpers

For accessing the GitHub API it is possible to use exactly the same approach as presented above.
However, for convenience, there are also additional helpers available:

Qt example

#include <QCoreApplication>
#include <QDebug>
#include <QNetworkAccessManager>

#include <utility>

#include <cpp_restapi/create_qt_connection.hpp>
#include <cpp_restapi/github/connection_builder.hpp>
#include <cpp_restapi/github/request.hpp>


int main(int argc, char** argv)
{
    QCoreApplication qapp(argc, argv);
    QNetworkAccessManager manager;

    auto connection = cpp_restapi::GitHub::ConnectionBuilder().build(cpp_restapi::createQtConnection, manager);
    cpp_restapi::GitHub::Request request(std::move(connection));

    qInfo() << request.getRateLimit().c_str();
    qInfo() << request.getUserInfo("Kicer86").c_str();

    return 0;
}

Here the connection is built with ConnectionBuilder.
The builder sets the GitHub API URL and headers automatically. When calling build(factory, args...), any backend-specific arguments are forwarded to the factory before the URL and headers. For example, the Qt factory receives the QNetworkAccessManager first. The returned std::unique_ptr<IConnection> is then moved into GitHub::Request, which owns the connection. Refer to the documentation of ConnectionBuilder for more details.

The cpp_restapi::GitHub::Request class provides accessors for the most common GitHub API requests.

libcurl example

#include <iostream>

#include <utility>

#include <cpp_restapi/create_curl_connection.hpp>
#include <cpp_restapi/github/connection_builder.hpp>
#include <cpp_restapi/github/request.hpp>


int main(int argc, char** argv)
{
    auto connection = cpp_restapi::GitHub::ConnectionBuilder().build(cpp_restapi::createCurlConnection);
    cpp_restapi::GitHub::Request request(std::move(connection));

    std::cout << request.getRateLimit() << '\n';
    std::cout << request.getUserInfo("Kicer86") << '\n';

    return 0;
}

cpp-httplib example:

#include <iostream>

#include <utility>

#include <cpp_restapi/create_cpp-httplib_connection.hpp>
#include <cpp_restapi/github/connection_builder.hpp>
#include <cpp_restapi/github/request.hpp>


int main(int argc, char** argv)
{
    auto connection = cpp_restapi::GitHub::ConnectionBuilder().build(cpp_restapi::createCppHttplibConnection);
    cpp_restapi::GitHub::Request request(std::move(connection));

    std::cout << request.getRateLimit() << '\n';
    std::cout << request.getUserInfo("Kicer86") << '\n';

    return 0;
}

See the examples directory for complete example programs.

Server-Sent Events (SSE)

In addition to regular REST requests, the library supports Server-Sent Events — a standard mechanism for receiving a stream of events from a server over HTTP.

SSE support is available for all three backends via IConnection::subscribe(). The method connects to an SSE endpoint, delivers parsed events through a callback and returns an ISseConnection handle. The call is non-blocking — events are received on an internal thread (or via the Qt event loop for the Qt backend). Use close() to stop. Keep the returned ISseConnection alive for as long as you want to receive events. For the Qt backend, the Qt event loop must be running.

SSE with libcurl

#include <iostream>
#include <thread>
#include <chrono>

#include <cpp_restapi/create_curl_connection.hpp>
#include <cpp_restapi/iconnection.hpp>
#include <cpp_restapi/isse_connection.hpp>
#include <cpp_restapi/sse_event.hpp>


int main(int argc, char** argv)
{
    auto connection = cpp_restapi::createCurlConnection("http://localhost:8080", {});

    auto sse = connection->subscribe("events", [](const cpp_restapi::SseEvent& event)
    {
        std::cout << "Event: " << event.event << '\n';
        std::cout << "Data: " << event.data << '\n';
    });

    // Do other work while events arrive in the background
    std::this_thread::sleep_for(std::chrono::seconds(30));

    sse->close();
    return 0;
}

SSE with Qt

#include <iostream>
#include <QCoreApplication>
#include <QNetworkAccessManager>

#include <cpp_restapi/create_qt_connection.hpp>
#include <cpp_restapi/iconnection.hpp>
#include <cpp_restapi/isse_connection.hpp>
#include <cpp_restapi/sse_event.hpp>


int main(int argc, char** argv)
{
    QCoreApplication qapp(argc, argv);
    QNetworkAccessManager manager;

    auto connection = cpp_restapi::createQtConnection(manager, "http://localhost:8080", {});

    auto sse = connection->subscribe("events", [](const cpp_restapi::SseEvent& event)
    {
        std::cout << "Event: " << event.event << '\n';
        std::cout << "Data: " << event.data << '\n';
    });

    return qapp.exec();
}

SSE with cpp-httplib

#include <iostream>
#include <thread>
#include <chrono>

#include <cpp_restapi/create_cpp-httplib_connection.hpp>
#include <cpp_restapi/iconnection.hpp>
#include <cpp_restapi/isse_connection.hpp>
#include <cpp_restapi/sse_event.hpp>


int main(int argc, char** argv)
{
    auto connection = cpp_restapi::createCppHttplibConnection("http://localhost:8080", {});

    auto sse = connection->subscribe("events", [](const cpp_restapi::SseEvent& event)
    {
        std::cout << "Event: " << event.event << '\n';
        std::cout << "Data: " << event.data << '\n';
    });

    // Do other work while events arrive in the background
    std::this_thread::sleep_for(std::chrono::seconds(30));

    sse->close();
    return 0;
}

SseEvent fields

The SseEvent struct exposes all standard SSE fields:

Field Type Description
event std::string Event type (from event: field, empty if not specified)
data std::string Event payload (from data: field(s), joined with \n)
id std::string Last event ID (from id: field)
retry int Reconnection time in ms (from retry: field, -1 if N/A)

Asynchronous Requests

All three backends support non-blocking HTTP requests via a callback-based API. fetch() returns immediately and delivers the result through onSuccess / onError callbacks. It also returns a CancellationToken that can be used to suppress callbacks before they fire.

For non-Qt backends (curl, cpp-httplib) callbacks run on a background std::thread. For the Qt backend, callbacks are invoked on the Qt event-loop thread.

Async with libcurl

#include <iostream>
#include <future>

#include <cpp_restapi/create_curl_connection.hpp>
#include <cpp_restapi/iconnection.hpp>


int main()
{
    auto connection = cpp_restapi::createCurlConnection("https://swapi.dev/api", {});

    std::promise<void> done;
    auto future = done.get_future();

    auto cancel = connection->fetch("people/1",
        [&done](cpp_restapi::Response resp)
        {
            std::cout << "Status: " << resp.statusCode << '\n';
            std::cout << "Body:   " << resp.body << '\n';
            done.set_value();
        },
        [&done](cpp_restapi::HttpError err)
        {
            std::cerr << "Error " << err.statusCode << ": " << err.message << '\n';
            done.set_value();
        });

    // Do other work while the request runs in the background...
    std::cout << "Request in flight — doing other work...\n";

    future.wait();
    return 0;
}

Async with Qt

#include <iostream>
#include <QCoreApplication>
#include <QNetworkAccessManager>

#include <cpp_restapi/create_qt_connection.hpp>
#include <cpp_restapi/iconnection.hpp>


int main(int argc, char** argv)
{
    QCoreApplication qapp(argc, argv);
    QNetworkAccessManager manager;

    auto connection = cpp_restapi::createQtConnection(manager, "https://swapi.dev/api", {});

    auto cancel = connection->fetch("people/1",
        [&qapp](cpp_restapi::Response resp)
        {
            std::cout << "Status: " << resp.statusCode << '\n';
            std::cout << "Body:   " << resp.body << '\n';
            qapp.quit();
        },
        [&qapp](cpp_restapi::HttpError err)
        {
            std::cerr << "Error " << err.statusCode << ": " << err.message << '\n';
            qapp.quit();
        });

    return qapp.exec();
}

Cancellation

The CancellationToken returned by async fetch() is a std::shared_ptr<std::atomic<bool>>. Setting it to true suppresses further callbacks. It does not guarantee that an already-started network request is aborted immediately:

auto cancel = connection->fetch("slow/endpoint",
    [](cpp_restapi::Response) { /* ... */ },
    [](cpp_restapi::HttpError) { /* ... */ });

// Changed our mind — suppress callbacks
cancel->store(true);

Async pagination

Paginated requests can also be performed asynchronously. The merged result is delivered through a BodyCallback once all pages have been collected:

cpp_restapi::LinkHeaderPaginationStrategy strategy;

auto cancel = connection->fetch("repos/owner/repo/issues", strategy,
    [](std::string mergedBody)
    {
        std::cout << "All pages: " << mergedBody << '\n';
    },
    [](cpp_restapi::HttpError err)
    {
        std::cerr << "Error on page: " << err.statusCode << '\n';
    });

Coroutine Helpers

The header-only <cpp_restapi/coroutine.hpp> provides lightweight coroutine wrappers around the callback-based async API. The project requires C++23, and the coroutine helpers also require compiler support for <coroutine>.

Key types

Type / Function Description
Detached Fire-and-forget wrapper — starts a coroutine that runs to completion on its own.
coFetch(conn, request) Returns an awaitable yielding std::expected<Response, HttpError>.
coFetch(conn, request, strategy) Returns an awaitable yielding std::expected<std::string, HttpError> (paginated).

Example (libcurl backend)

#include <iostream>
#include <future>

#include <cpp_restapi/coroutine.hpp>
#include <cpp_restapi/create_curl_connection.hpp>


int main()
{
    auto connection = cpp_restapi::createCurlConnection("https://swapi.dev/api", {});

    std::promise<void> done;
    auto future = done.get_future();

    [&]() -> cpp_restapi::Detached
    {
        auto result = co_await cpp_restapi::coFetch(*connection, "people/1");

        if (result)
            std::cout << "Status: " << result->statusCode << '\n'
                      << "Body:   " << result->body << '\n';
        else
            std::cerr << "Error: " << result.error().message << '\n';

        done.set_value();
    }();

    future.wait();
    return 0;
}

Standalone builds, examples and tests

Standalone builds are mostly useful when you want to run the examples or unit tests from this repository. The main integration scenario is still to include cpp_restapi as a CMake subproject.

Basic standalone build

It is possible to build this project as any other regular CMake project by invoking:

cmake -B build -DCppRestAPI_CurlBackend=ON
cmake --build build

Replace CppRestAPI_CurlBackend with another backend option if you prefer Qt or cpp-httplib. At least one backend option must be enabled, otherwise configuration fails.

Using bundled vcpkg

This repository has an optional vcpkg submodule. It is not required when you include cpp_restapi as a subproject, but it can simplify standalone builds by providing dependencies.

Please mind that vcpkg uses telemetry.
Visit https://learn.microsoft.com/vcpkg/about/privacy for more details.

If you want to use the bundled vcpkg checkout, initialize it and configure CMake with the vcpkg toolchain file:

git submodule update --init vcpkg
./vcpkg/bootstrap-vcpkg.sh
cmake -B build -DCMAKE_TOOLCHAIN_FILE=$PWD/vcpkg/scripts/buildsystems/vcpkg.cmake -DCppRestAPI_CppHttplibBackend=ON

The repository also provides vcpkg-based CMake presets in CMakePresets.json.

Building examples

Examples are located in the examples directory of the project. To build them set CppRestAPI_Examples CMake variable to ON. It can be done when invoking cmake command by providing the -DCppRestAPI_Examples=ON command-line argument (see the Basic standalone build section), or by modifying the CppRestAPI_Examples entry in the CMakeCache.txt file located in the build directory of an already configured project.

Please mind that setting CppRestAPI_Examples to ON forces all backends and optional components to be used, so all backend dependencies must be available.

Building unit tests

Unit tests are located in the tests directory of the project. To build them set CppRestAPI_Tests CMake variable to ON.

Please mind that setting CppRestAPI_Tests to ON forces all backends and optional components to be used, so all backend dependencies must be available.

Links

Code documentation available at https://kicer86.github.io/cpp_restapi/index.html