HTTP Client#

HttpClient provides a simple HTTP/HTTPS client for making web requests. It handles connections, keep-alive, compression, and chunked encoding automatically.

HttpClient is designed to be:

  • easy — simple API for common tasks
  • efficient — supports HTTP keep-alive
  • complete — handles encoding and compression
  • secure — HTTPS support via HttpsClient

Basic usage#

Simple GET request#

#include <join/httpclient.hpp>

// Create client
HttpClient client("api.example.com", 80);

// Create request
HttpRequest req(HttpMethod::Get);
req.path("/users");

// Send request
client << req;

// Receive response
HttpResponse res;
client >> res;

// Read body
std::string body;
std::getline(client, body, '\0');

std::cout << res.status() << ": " << body << "\n";

HTTPS request#

// HTTPS client (port 443 is default)
HttpsClient client("api.example.com");

HttpRequest req(HttpMethod::Get);
req.path("/secure/data");

client << req;

HttpResponse res;
client >> res;

Construction#

// HTTP client with defaults
HttpClient client("example.com", 80);

// HTTPS client
HttpsClient client("example.com", 443);

// Disable keep-alive
HttpClient client("example.com", 80, false);

Making requests#

GET request#

HttpClient client("api.example.com");

HttpRequest req(HttpMethod::Get);
req.path("/api/v1/users");
req.parameter("page", "2");
req.parameter("limit", "50");

client << req;

HttpResponse res;
client >> res;

if (res.status() == "200")
{
    std::string data;
    std::getline(client, data, '\0');
    processData(data);
}

POST request#

HttpClient client("api.example.com");

HttpRequest req(HttpMethod::Post);
req.path("/api/users");
req.header("Content-Type", "application/json");

std::string json = R"({"name":"Alice","age":30})";
req.header("Content-Length", std::to_string(json.size()));

// Send request and body
client << req;
client << json;
client.flush();

// Receive response
HttpResponse res;
client >> res;

PUT request#

HttpRequest req(HttpMethod::Put);
req.path("/api/users/123");
req.header("Content-Type", "application/json");

std::string json = R"({"name":"Bob"})";
req.header("Content-Length", std::to_string(json.size()));

client << req;
client << json;
client.flush();

HttpResponse res;
client >> res;

DELETE request#

HttpRequest req(HttpMethod::Delete);
req.path("/api/users/123");

client << req;

HttpResponse res;
client >> res;

Headers and authentication#

Custom headers#

HttpRequest req(HttpMethod::Get);
req.path("/api/data");

// Add headers
req.header("Accept", "application/json");
req.header("User-Agent", "MyApp/1.0");
req.header("X-API-Key", apiKey);

client << req;

Bearer token authentication#

HttpRequest req(HttpMethod::Get);
req.path("/api/protected");
req.header("Authorization", "Bearer " + accessToken);

client << req;

Basic authentication#

// Encode credentials (username:password in base64)
std::string credentials = base64Encode("user:pass");
req.header("Authorization", "Basic " + credentials);

Keep-alive#

Persistent connections#

// Enable keep-alive (default)
HttpClient client("api.example.com", 80, true);

// Multiple requests on same connection
for (int i = 0; i < 10; ++i)
{
    HttpRequest req(HttpMethod::Get);
    req.path("/data/" + std::to_string(i));

    client << req;

    HttpResponse res;
    client >> res;

    // Process response...
}
// Connection automatically reused

Connection management#

HttpClient client("example.com");

// Check keep-alive settings
auto timeout = client.keepAliveTimeout();
int maxRequests = client.keepAliveMax();

// Manually close connection
client.close();

Response handling#

Reading body#

client << req;

HttpResponse res;
client >> res;

// Read entire body
std::string body;
std::getline(client, body, '\0');

// Or read with Content-Length
size_t length = res.contentLength();
if (length > 0)
{
    std::string body(length, '\0');
    client.read(&body[0], length);
}

Checking status#

HttpResponse res;
client >> res;

if (res.status() == "200")
{
    // Success
}
else if (res.status() == "404")
{
    // Not found
}
else if (res.status()[0] == '5')
{
    // Server error
}

Compression and encoding#

The client automatically handles:

  • Gzip compression — transparent decompression
  • Deflate compression — transparent decompression
  • Chunked transfer — automatic reassembly
// Request compressed response
HttpRequest req(HttpMethod::Get);
req.header("Accept-Encoding", "gzip, deflate");

client << req;

HttpResponse res;
client >> res;

// Body is automatically decompressed
std::string body;
std::getline(client, body, '\0');

Error handling#

HttpClient client("api.example.com");

try
{
    client << req;

    if (client.fail())
    {
        std::cerr << "Request failed\n";
        return;
    }

    HttpResponse res;
    client >> res;

    if (client.fail())
    {
        std::cerr << "Response failed\n";
        return;
    }

}
catch (const std::exception& e)
{
    std::cerr << "Error: " << e.what() << "\n";
}

Common patterns#

JSON API client#

class ApiClient
{
    HttpsClient client;
    std::string token;

public:
    ApiClient(const std::string& host, const std::string& token)
    : client(host), token(token)
    {}

    std::string get(const std::string& endpoint)
    {
        HttpRequest req(HttpMethod::Get);
        req.path(endpoint);
        req.header("Accept", "application/json");
        req.header("Authorization", "Bearer " + token);

        client << req;

        HttpResponse res;
        client >> res;

        if (res.status() != "200")
        {
            throw std::runtime_error("Request failed: " + res.status());
        }

        std::string body;
        std::getline(client, body, '\0');
        return body;
    }

    std::string post(const std::string& endpoint, const std::string& json)
    {
        HttpRequest req(HttpMethod::Post);
        req.path(endpoint);
        req.header("Content-Type", "application/json");
        req.header("Authorization", "Bearer " + token);
        req.header("Content-Length", std::to_string(json.size()));

        client << req;
        client << json;
        client.flush();

        HttpResponse res;
        client >> res;

        std::string body;
        std::getline(client, body, '\0');
        return body;
    }
};

File download#

void downloadFile(const std::string& url, const std::string& output)
{
    HttpClient client("example.com");

    HttpRequest req(HttpMethod::Get);
    req.path("/files/document.pdf");

    client << req;

    HttpResponse res;
    client >> res;

    if (res.status() == "200")
    {
        std::ofstream file(output, std::ios::binary);
        file << client.rdbuf();
    }
}

Retry logic#

HttpResponse requestWithRetry(HttpClient& client, HttpRequest& req, int maxRetries = 3)
{
    for (int i = 0; i < maxRetries; ++i)
    {
        try
        {
            client << req;

            HttpResponse res;
            client >> res;

            if (res.status()[0] != '5')
            {
                return res;  // Success or client error
            }

            // Server error - retry
            std::this_thread::sleep_for(std::chrono::seconds(1 << i));

        }
        catch (...)
        {
            if (i == maxRetries - 1) throw;
        }
    }

    throw std::runtime_error("Max retries exceeded");
}

HTTPS specifics#

// HTTPS with default port (443)
HttpsClient client("secure.example.com");

// HTTPS with custom port
HttpsClient client("secure.example.com", 8443);

// Everything else works the same as HTTP
client << req;
client >> res;

Best practices#

Reuse clients#

// Good: Reuse client for multiple requests
HttpClient client("api.example.com");
for (const auto& endpoint : endpoints)
{
    HttpRequest req(HttpMethod::Get);
    req.path(endpoint);
    client << req;
    // ...
}

// Avoid: Creating new client each time
for (const auto& endpoint : endpoints)
{
    HttpClient client("api.example.com");  // Slow!
    // ...
}

Set timeouts#

// Connection timeout via socket buffer
// (requires access to underlying socket)
client.timeout(5000);  // 5 seconds

Always check status#

client << req;
HttpResponse res;
client >> res;

if (res.status()[0] == '2')
{
    // Success (2xx)
}
else
{
    // Handle error
}

Summary#

FeatureSupported
HTTP/1.1
HTTPS
Keep-alive
Gzip/Deflate
Chunked encoding
All HTTP methods
Custom headers
Stream interface