A typed, async HTTP client library for Qt 6.2+ and C++23. Wraps QNetworkAccessManager with a codec-generic API, automatic retries, and first-class support for signals/slots, callbacks, futures, and coroutines.
- Typed responses (
client->get<User>(...)returnsQHttpReply<User>*) - Codec-generic design (ships with
QJsonCodec, bring your own via theCodecconcept) - Per-request option overrides with
QHttpOptions - Configurable retry policies (linear, exponential, Fibonacci backoff)
- Five async patterns: reply objects, signals/slots, callbacks, futures, coroutines
- Qt 6.2+
- C++23 compiler (GCC 13+, Clang 17+, MSVC 17.10+)
- CMake 3.19+
| Platform | Qt | Compiler |
|---|---|---|
| Ubuntu 24.04 (WSL2) | 6.4.2 | GCC 13.3 |
| Windows 11 | 6.x | MSVC |
All methods below expose the same CMake target. In your code:
#include <QHttpClient>include(FetchContent)
FetchContent_Declare(
QHttpClient
GIT_REPOSITORY https://github.com/goeries/qhttpclient.git
GIT_TAG v0.1.0-alpha
)
FetchContent_MakeAvailable(QHttpClient)
target_link_libraries(your_target PRIVATE QHttpClient::QHttpClient)git submodule add git@github.com:goeries/qhttpclient.git external/qhttpclientadd_subdirectory(external/qhttpclient)
target_link_libraries(your_target PRIVATE QHttpClient::QHttpClient)Clone with --recurse-submodules to pull it automatically.
Copy the include/, src/, and CMakeLists.txt into your project (e.g., external/qhttpclient/):
add_subdirectory(external/qhttpclient)
target_link_libraries(your_target PRIVATE QHttpClient::QHttpClient)QNetworkAccessManager nam;
QHttpClientConfig config {
.baseUrl = "https://api.example.com",
.bearerToken = "my-token",
.timeout = std::chrono::seconds(30),
.retry = QHttpRetryPolicy {
.maxAttempts = 3,
.backoff = Backoff::Exponential,
},
};
QHttpClient client(&nam, config);// Use client defaults
auto *reply = client.get<User>("/api/users/1");
// Override timeout and add a header for this request only
auto *reply2 = client.get<User>("/api/users/2", {
.headers = {{"X-Request-Id", "abc123"}},
.timeout = std::chrono::seconds(5),
});Every request returns a QHttpReply<T>* that can be consumed in any of the following ways.
auto *reply = client.get<User>("/api/users/1");
// reply is live immediately; attach any handler belowClient-level signals for centralized logging:
QObject::connect(&client, &QHttpClientBase::replyFinished,
[](QHttpReplyBase *reply) {
qDebug() << reply->httpStatusCode();
});
QObject::connect(&client, &QHttpClientBase::errorOccurred,
[](const QHttpError &error) {
qWarning() << error.message();
});Reply-level signals for per-request handling:
auto *reply = client.get<User>("/api/users/1");
QObject::connect(reply, &QHttpReplyBase::finished,
reply, [reply]() {
if (!reply->hasError())
useValue(reply->getValue());
});Inline with the request:
auto *reply = client.get<User>("/api/users/1",
this, [](QHttpReply<User> *reply) {
if (reply->hasError())
return handleError(*reply->error());
useValue(reply->getValue());
});Or attached to the reply:
auto *reply = client.get<User>("/api/users/1");
reply->onFinished(this, [](QHttpReply<User> *reply) {
if (reply->hasError())
return handleError(*reply->error());
useValue(reply->getValue());
});auto *reply = client.get<User>("/api/users/1");
reply->makeFuture()
.then([](std::expected<User, QHttpError> result) {
if (!result)
return handleError(result.error());
useValue(*result);
});auto *reply = client.get<User>("/api/users/1");
co_await *reply;
if (auto err = reply->error()) {
handleError(*err);
co_return;
}
useValue(reply->getValue());client.get<T>(path, opts);
client.get<T>(path, body, opts);
client.post<T>(path, body, opts);
client.put<T>(path, body, opts);
client.patch<T>(path, body, opts);
client.deleteResource<T>(path, body, opts);
client.head<T>(path, body, opts);
client.sendCustomRequest<T>(path, body, opts);Use <void> for requests where you don't need a response body:
auto *reply = client.post<void>("/api/users", userJson);git clone https://github.com/goeries/qhttpclient.git
cd qhttpclient
cmake -B build -G Ninja -DQHTTPCLIENT_BUILD_TESTING=ON
cmake --build build
ctest --test-dir build --output-on-failureMIT © 2026 Jeandré Gouws