Skip to main content
LibHTTP is Ladybird’s HTTP/1.1 client library that handles web requests, HTTP headers, cookies, and caching. It works in conjunction with RequestServer for network operations.

HTTP request handling

HttpRequest

Build and send HTTP requests:
#include <LibHTTP/HttpRequest.h>

// Create request
auto headers = adopt_ref(*new HTTP::HeaderList);
HTTP::HttpRequest request { headers };

request.set_method(HTTP::HttpRequest::Method::GET);
request.set_url(URL::URL("https://example.com/api"sv));

// Set custom headers
headers->set("User-Agent"sv, "Ladybird/1.0"sv);
headers->set("Accept"sv, "application/json"sv);

// Convert to raw bytes
auto raw_request = TRY(request.to_raw_request());

HTTP methods

Supported HTTP methods:

GET

Retrieve resource

POST

Submit data

PUT

Update resource

DELETE

Remove resource

HEAD

Get headers only

OPTIONS

Query supported methods

PATCH

Partial update

TRACE

Diagnostic trace

CONNECT

Establish tunnel
// Set method
request.set_method(HTTP::HttpRequest::Method::POST);

// Add request body
ByteBuffer body;
TRY(body.try_append("{\"key\": \"value\"}"sv));
request.set_body(move(body));

Parsing requests

Parse raw HTTP requests:
// Parse from bytes
auto request_or_error = HTTP::HttpRequest::from_raw_request(
    raw_bytes
);

if (request_or_error.is_error()) {
    switch (request_or_error.error()) {
    case HTTP::HttpRequest::ParseError::RequestTooLarge:
        // Handle too large
        break;
    case HTTP::HttpRequest::ParseError::InvalidURL:
        // Handle invalid URL
        break;
    // ...
    }
}

auto request = request_or_error.release_value();
auto method = request.method();
auto url = request.url();

Header management

HeaderList

Manage HTTP headers:
auto headers = adopt_ref(*new HTTP::HeaderList);

// Set headers
headers->set("Content-Type"sv, "application/json"sv);
headers->set("Authorization"sv, "Bearer token123"sv);
headers->set("Accept-Encoding"sv, "gzip, deflate"sv);

// Get header value
if (auto content_type = headers->get("Content-Type"sv)) {
    dbgln("Content-Type: {}", *content_type);
}

// Check existence
if (headers->contains("Authorization"sv)) {
    // Header exists
}

// Remove header
headers->remove("X-Custom-Header"sv);

// Iterate all headers  
headers->for_each([](auto const& name, auto const& value) {
    dbgln("{}: {}", name, value);
});

Header parsing

// Parse header value
auto header = TRY(HTTP::Header::from_string(
    "Content-Type: text/html; charset=utf-8"sv
));

dbgln("Name: {}", header.name);     // "Content-Type"
dbgln("Value: {}", header.value);   // "text/html; charset=utf-8"

HTTP status codes

Status handling

// Check status code
if (response.status_code() == 200) {
    // Success
} else if (response.status_code() >= 400) {
    // Client or server error
}

// Common status codes
switch (response.status_code()) {
case 200: // OK
    break;
case 301: // Moved Permanently
case 302: // Found (temporary redirect)
    auto location = response.headers().get("Location"sv);
    // Follow redirect
    break;
case 304: // Not Modified
    // Use cached version
    break;
case 404: // Not Found
    // Resource doesn't exist
    break;
case 500: // Internal Server Error
    // Server error
    break;
}
Status codes follow the HTTP/1.1 specification (RFC 7231). LibHTTP provides the Status enum for common status codes.

Request building patterns

GET request

auto headers = adopt_ref(*new HTTP::HeaderList);
HTTP::HttpRequest request { headers };

request.set_method(HTTP::HttpRequest::Method::GET);
request.set_url(URL::URL("https://api.example.com/users"sv));

headers->set("Accept"sv, "application/json"sv);
headers->set("User-Agent"sv, "Ladybird Browser"sv);

POST with JSON

auto headers = adopt_ref(*new HTTP::HeaderList);
HTTP::HttpRequest request { headers };

request.set_method(HTTP::HttpRequest::Method::POST);
request.set_url(URL::URL("https://api.example.com/users"sv));

headers->set("Content-Type"sv, "application/json"sv);
headers->set("Accept"sv, "application/json"sv);

ByteBuffer body;
TRY(body.try_append(R"({"name": "Alice", "age": 30})"sv));
request.set_body(move(body));

Form data submission

auto headers = adopt_ref(*new HTTP::HeaderList);
HTTP::HttpRequest request { headers };

request.set_method(HTTP::HttpRequest::Method::POST);
request.set_url(url);

headers->set("Content-Type"sv, 
    "application/x-www-form-urlencoded"sv);

ByteBuffer body;
TRY(body.try_append("username=alice&password=secret"sv));
request.set_body(move(body));
LibHTTP includes cookie support for maintaining session state:

Setting cookies

// Parse Set-Cookie header
auto cookie = TRY(HTTP::Cookie::parse_cookie(
    "sessionid=abc123; Path=/; HttpOnly; Secure"sv
));

dbgln("Name: {}", cookie.name);
dbgln("Value: {}", cookie.value);
dbgln("Path: {}", cookie.path);
dbgln("Secure: {}", cookie.secure);
dbgln("HttpOnly: {}", cookie.http_only);
  • Domain - Cookie domain scope
  • Path - URL path scope
  • Expires - Expiration timestamp
  • Max-Age - Lifetime in seconds
  • Secure - HTTPS only
  • HttpOnly - No JavaScript access
  • SameSite - CSRF protection (Strict/Lax/None)

Caching

HTTP cache support

LibHTTP supports HTTP caching directives:
// Check cache headers
auto cache_control = headers->get("Cache-Control"sv);
if (cache_control && cache_control->contains("no-cache"sv)) {
    // Don't use cached version
}

// ETag validation
if (auto etag = headers->get("ETag"sv)) {
    // Store etag for conditional requests
    next_headers->set("If-None-Match"sv, *etag);
}

// Last-Modified validation
if (auto last_modified = headers->get("Last-Modified"sv)) {
    next_headers->set("If-Modified-Since"sv, *last_modified);
}

Cache directives

Cache-Control

no-cache, no-store, max-age, must-revalidate

ETag

Entity tag for conditional requests

Last-Modified

Resource modification timestamp

Expires

Explicit expiration date

Content negotiation

Accept headers

headers->set("Accept"sv, 
    "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"sv);
headers->set("Accept-Language"sv, "en-US,en;q=0.9"sv);
headers->set("Accept-Encoding"sv, "gzip, deflate, br"sv);

Content-Type parsing

auto content_type = headers->get("Content-Type"sv);
if (content_type) {
    if (content_type->contains("application/json"sv)) {
        // Parse as JSON
    } else if (content_type->contains("text/html"sv)) {
        // Parse as HTML
    }
}

Integration with RequestServer

LibHTTP typically works through RequestServer for actual network I/O:
// RequestServer handles:
// - DNS resolution
// - TCP connection management  
// - TLS/SSL (via LibTLS)
// - Request queuing
// - Connection pooling

// Application uses high-level API:
auto request = HTTP::HttpRequest { headers };
request.set_url(url);
request.set_method(HTTP::HttpRequest::Method::GET);

// RequestServer executes the request
// and returns response via IPC
LibHTTP handles HTTP protocol details but doesn’t perform actual network I/O. Use RequestServer (via IPC) for network operations.

Utilities

URL handling

LibHTTP works with URL::URL from LibURL:
auto url = URL::URL("https://example.com:8080/path?query=value#fragment"sv);

dbgln("Scheme: {}", url.scheme());     // "https"
dbgln("Host: {}", url.host());         // "example.com"
dbgln("Port: {}", url.port());         // 8080
dbgln("Path: {}", url.serialize_path()); // "/path"
dbgln("Query: {}", url.query());       // "query=value"

Method conversion

// String to method
auto method_name = "POST"sv;
auto method = HTTP::to_method(method_name);

// Method to string
auto name = HTTP::to_string_view(HTTP::HttpRequest::Method::GET);
dbgln("Method: {}", name); // "GET"

Error handling

// Parse errors
auto result = HTTP::HttpRequest::from_raw_request(bytes);
if (result.is_error()) {
    auto error = result.error();
    auto message = HTTP::HttpRequest::parse_error_to_string(error);
    dbgln("Parse failed: {}", message);
}

// Build errors
if (auto raw = request.to_raw_request(); raw.is_error()) {
    dbgln("Failed to build request: {}", raw.error());
}

Advanced features

Custom headers

// Add custom application headers
headers->set("X-Request-ID"sv, generate_uuid());
headers->set("X-API-Key"sv, api_key);
headers->set("X-Client-Version"sv, "1.0.0"sv);

Range requests

// Request partial content
headers->set("Range"sv, "bytes=0-1023"sv);

// Server responds with 206 Partial Content
if (response.status_code() == 206) {
    auto content_range = response.headers().get("Content-Range"sv);
    // Parse range information
}

Performance considerations

Reuse HeaderList objects when making multiple similar requests to reduce allocations. Clone the base headers and modify as needed.
// Base headers for all requests
auto base_headers = adopt_ref(*new HTTP::HeaderList);
base_headers->set("User-Agent"sv, "Ladybird"sv);
base_headers->set("Accept-Encoding"sv, "gzip"sv);

// Clone and customize per request
auto request_headers = base_headers->clone();
request_headers->set("Authorization"sv, token);

Source location

  • Repository: ~/workspace/source/Libraries/LibHTTP/
  • Key headers: HttpRequest.h, HeaderList.h, Header.h, Status.h, Method.h
  • Cookie support: LibHTTP/Cookie/
  • Caching: LibHTTP/Cache/
LibHTTP implements HTTP/1.1 per RFC 7230-7235. HTTP/2 and HTTP/3 support is planned for future releases.

Build docs developers (and LLMs) love