Skip to content

HTTP Client

The Glaze HTTP client provides a simple and efficient way to make HTTP requests with connection pooling and asynchronous operations.

Basic Usage

#include "glaze/net/http_client.hpp"

int main() {
    glz::http_client client;

    auto response = client.get("https://example.com");

    if (response) {
        std::cout << "Status: " << response->status_code << std::endl;
        std::cout << "Body: " << response->response_body << std::endl;

        // Access response headers
        for (const auto& [name, value] : response->response_headers) {
            std::cout << name << ": " << value << std::endl;
        }
    } else {
        std::cerr << "Error: " << response.error().message() << std::endl;
    }

    return 0;
}

Features

  • Connection Pooling: Automatically reuses connections for better performance
  • Asynchronous Operations: Non-blocking requests with futures or completion handlers
  • JSON Support: Built-in JSON serialization for POST requests
  • Thread-Safe: Multiple threads can safely use the same client instance
  • Error Handling: Uses std::expected for clean error handling

Synchronous Methods

GET Request

std::expected<response, std::error_code> get(
    std::string_view url,
    const std::unordered_map<std::string, std::string>& headers = {}
);

POST Request

std::expected<response, std::error_code> post(
    std::string_view url, 
    std::string_view body,
    const std::unordered_map<std::string, std::string>& headers = {}
);

JSON POST Request

template<class T>
std::expected<response, std::error_code> post_json(
    std::string_view url, 
    const T& data,
    const std::unordered_map<std::string, std::string>& headers = {}
);

Asynchronous Methods

All asynchronous methods come in two variants:

  1. Future-based: Returns a std::future for the response
  2. Callback-based: Takes a completion handler that's called when the operation completes

Async GET Request

Future-based:

std::future<std::expected<response, std::error_code>> get_async(
    std::string_view url,
    const std::unordered_map<std::string, std::string>& headers = {}
);

Callback-based:

template<typename CompletionHandler>
void get_async(
    std::string_view url,
    const std::unordered_map<std::string, std::string>& headers,
    CompletionHandler&& handler
);

Async POST Request

Future-based:

std::future<std::expected<response, std::error_code>> post_async(
    std::string_view url, 
    std::string_view body,
    const std::unordered_map<std::string, std::string>& headers = {}
);

Callback-based:

template<typename CompletionHandler>
void post_async(
    std::string_view url, 
    std::string_view body,
    const std::unordered_map<std::string, std::string>& headers,
    CompletionHandler&& handler
);

Async JSON POST Request

Future-based:

template<class T>
std::future<std::expected<response, std::error_code>> post_json_async(
    std::string_view url, 
    const T& data,
    const std::unordered_map<std::string, std::string>& headers = {}
);

Callback-based:

template<class T, typename CompletionHandler>
void post_json_async(
    std::string_view url, 
    const T& data,
    const std::unordered_map<std::string, std::string>& headers,
    CompletionHandler&& handler
);

Streaming Requests

The HTTP client supports streaming requests, which allow you to receive data in chunks.

template<typename OnData, typename OnError, typename OnConnect, typename OnDisconnect>
stream_connection::pointer stream_request(stream_options options);

The stream_options struct contains the following fields:

struct stream_options {
    std::string url;
    std::string method = "GET";
    std::unordered_map<std::string, std::string> headers;
    OnData on_data;
    OnError on_error;
    OnConnect on_connect;
    OnDisconnect on_disconnect;
};
  • url: The URL to request.
  • method: The HTTP method to use (default: "GET").
  • headers: The HTTP headers to send.
  • on_data: A callback that's called when data is received.
  • on_error: A callback that's called when an error occurs.
  • on_connect: A callback that's called when the connection is established and the headers are received.
  • on_disconnect: A callback that's called when the connection is closed.

The stream_connection::pointer object contains a disconnect() method that can be used to close the connection.

Response Structure

The response object contains:

struct response {
    uint16_t status_code;                                      // HTTP status code
    std::unordered_map<std::string, std::string> response_headers; // Response headers
    std::string response_body;                                 // Response body
};

Error Handling

The HTTP client returns a std::expected object for synchronous and asynchronous requests, which contains either the response or an error code. You can check for errors using the has_value() method or by accessing the error() method.

auto response = client.get("https://example.com");

if (response) {
    // Request was successful
    std::cout << "Status: " << response->status_code << std::endl;
} else {
    std::error_code ec = response.error();
    std::cerr << "Error: " << ec.message() << std::endl;
}

For streaming requests, errors are reported via the on_error callback in the stream_options struct. The client translates HTTP error statuses (4xx/5xx) into std::errc::connection_refused errors.

Examples

Simple GET Request

#include "glaze/net/http_client.hpp"

int main() {
    glz::http_client client;

    auto response = client.get("https://api.github.com/users/octocat");

    if (response) {
        std::cout << "Status: " << response->status_code << std::endl;
        std::cout << "Content-Type: " << response->response_headers["Content-Type"] << std::endl;
        std::cout << "Body: " << response->response_body << std::endl;
    } else {
        std::cerr << "Error: " << response.error().message() << std::endl;
    }

    return 0;
}

POST Request with Custom Headers

#include "glaze/net/http_client.hpp"

int main() {
    glz::http_client client;

    std::unordered_map<std::string, std::string> headers = {
        {"Content-Type", "text/plain"},
        {"Authorization", "Bearer your-token"}
    };

    auto response = client.post("https://api.example.com/data", "Hello, World!", headers);

    if (response) {
        std::cout << "Status: " << response->status_code << std::endl;
        std::cout << "Response: " << response->response_body << std::endl;
    } else {
        std::cerr << "Error: " << response.error().message() << std::endl;
    }

    return 0;
}

JSON POST Request

#include "glaze/net/http_client.hpp"
#include "glaze/glaze.hpp"

struct User {
    int id;
    std::string name;
    std::string email;
};

int main() {
    glz::http_client client;

    User user{123, "John Doe", "john@example.com"};

    // Using the convenient post_json method
    auto response = client.post_json("https://api.example.com/users", user);

    if (response) {
        std::cout << "User created! Status: " << response->status_code << std::endl;
        std::cout << "Response: " << response->response_body << std::endl;
    } else {
        std::cerr << "Error: " << response.error().message() << std::endl;
    }

    return 0;
}

Asynchronous Requests with Futures

#include "glaze/net/http_client.hpp"
#include <future>
#include <vector>

int main() {
    glz::http_client client;

    // Launch multiple async requests
    std::vector<std::future<std::expected<glz::response, std::error_code>>> futures;

    futures.push_back(client.get_async("https://api.github.com/users/octocat"));
    futures.push_back(client.get_async("https://api.github.com/users/defunkt"));
    futures.push_back(client.get_async("https://api.github.com/users/pjhyett"));

    // Wait for all requests to complete
    for (auto& future : futures) {
        auto response = future.get();
        if (response) {
            std::cout << "Status: " << response->status_code << std::endl;
        } else {
            std::cerr << "Error: " << response.error().message() << std::endl;
        }
    }

    return 0;
}

Asynchronous Requests with Callbacks

#include "glaze/net/http_client.hpp"
#include <iostream>

int main() {
    glz::http_client client;

    // Async GET with callback
    client.get_async("https://api.github.com/users/octocat", {},
        [](std::expected<glz::response, std::error_code> result) {
            if (result) {
                std::cout << "Async GET completed! Status: " << result->status_code << std::endl;
            } else {
                std::cerr << "Async GET failed: " << result.error().message() << std::endl;
            }
        });

    // Async JSON POST with callback
    struct Data { int value = 42; };
    Data data;

    client.post_json_async("https://httpbin.org/post", data, {},
        [](std::expected<glz::response, std::error_code> result) {
            if (result) {
                std::cout << "Async JSON POST completed! Status: " << result->status_code << std::endl;
            } else {
                std::cerr << "Async JSON POST failed: " << result.error().message() << std::endl;
            }
        });

    // Keep the main thread alive long enough for async operations to complete
    std::this_thread::sleep_for(std::chrono::seconds(2));

    return 0;
}

URL Parsing

The client includes a URL parsing utility:

#include "glaze/net/http_client.hpp"

auto url_parts = glz::parse_url("https://api.example.com:8080/v1/users");
if (url_parts) {
    std::cout << "Protocol: " << url_parts->protocol << std::endl; // "https"
    std::cout << "Host: " << url_parts->host << std::endl;         // "api.example.com"
    std::cout << "Port: " << url_parts->port << std::endl;         // 8080
    std::cout << "Path: " << url_parts->path << std::endl;         // "/v1/users"
}

Performance Notes

  • The client automatically pools connections for better performance when making multiple requests to the same host
  • Multiple worker threads are used internally to handle concurrent requests
  • The connection pool has a configurable limit (default: 10 connections per host), which can be adjusted using the http_client::options::max_connections option.
  • Connections are automatically returned to the pool when requests complete successfully