Files
cpp-httplib/README-sse.md
T
yhirose f17d314979 Send the client's credentials and proxy target on a stream request
open_stream(), and with it stream::Get() and SSEClient, wrote its own
request head and left out what a buffered request gets: the
Authorization from set_basic_auth() or set_bearer_token_auth(), and
through a plain HTTP proxy the absolute-form target (RFC 9112 3.2.2)
and Proxy-Authorization. A proxy refused such a request, and an origin
that needs credentials answered 401.

Move the two decisions out of write_request() and handle_request() into
add_credential_headers() and proxy_target_prefix(), and use them in
open_stream() too, after connecting as a buffered request does. A
Digest challenge or a redirect is still handed to the caller.
2026-10-18 20:32:27 -04:00

5.3 KiB

SSEClient - Server-Sent Events Client

A simple, EventSource-like SSE client for C++11.

Features

  • Auto-reconnect: Automatically reconnects on connection loss or an error status (except 204, 403 and 404)
  • Last-Event-ID: Sends last received ID on reconnect for resumption
  • retry field: Respects server's reconnect interval
  • Event types: Supports custom event types via on_event()
  • Async support: Run in background thread with start_async()
  • C++11 compatible: No C++14/17/20 features required

Quick Start

httplib::Client cli("http://localhost:8080");
httplib::sse::SSEClient sse(cli, "/events");

sse.on_message([](const httplib::sse::SSEMessage &msg) {
    std::cout << "Event: " << msg.event << std::endl;
    std::cout << "Data: " << msg.data << std::endl;
});

sse.start();  // Blocking, with auto-reconnect

API Reference

SSEMessage

struct SSEMessage {
    std::string event;  // Event type (default: "message")
    std::string data;   // Event payload
    std::string id;     // Event ID
};

SSEClient

Constructor

// Basic
SSEClient(Client &client, const std::string &path);

// With custom headers
SSEClient(Client &client, const std::string &path, const Headers &headers);

Event Handlers

// Called for events whose type has no on_event() handler
sse.on_message([](const SSEMessage &msg) { });

// Called for specific event types
sse.on_event("update", [](const SSEMessage &msg) { });
sse.on_event("delete", [](const SSEMessage &msg) { });

// Called when connection is established
sse.on_open([]() { });

// Called on connection errors
sse.on_error([](httplib::Error err) { });

Configuration

// Set reconnect interval (default: 3000ms, minimum: 100ms)
sse.set_reconnect_interval(5000);

// Set max reconnect attempts (default: 0 = unlimited)
sse.set_max_reconnect_attempts(10);

// Update headers at any time (thread-safe)
sse.set_headers({{"Authorization", "Bearer new_token"}});

The client reconnects when it cannot connect or the stream ends. A status other than 200 calls on_error(Error::Connection); a 204, 403 or 404 then stops the client, and any other status, 401 included, reconnects, so an on_error handler can refresh credentials with set_headers(). set_max_reconnect_attempts() counts reconnect attempts in a row: the count starts over after each successful connection. Each attempt that fails to connect is also reported to the Client's error logger.

An event, or a line still waiting for its end, longer than CPPHTTPLIB_SSE_MAX_EVENT_LENGTH (16MB by default, 0 for no limit) ends the connection with Error::ExceedMaxPayloadSize, and the client stops rather than reconnect to the same event. The client's set_payload_max_length() does not apply to the stream as a whole, which has no end.

Control

// Blocking start with auto-reconnect
sse.start();

// Non-blocking start (runs in background thread)
sse.start_async();

// Stop the client (thread-safe)
sse.stop();

State

bool connected = sse.is_connected();
const std::string &id = sse.last_event_id();

Examples

Basic Usage

httplib::Client cli("http://localhost:8080");
httplib::sse::SSEClient sse(cli, "/events");

sse.on_message([](const httplib::sse::SSEMessage &msg) {
    std::cout << msg.data << std::endl;
});

sse.start();

With Custom Event Types

httplib::sse::SSEClient sse(cli, "/events");

sse.on_event("notification", [](const httplib::sse::SSEMessage &msg) {
    std::cout << "Notification: " << msg.data << std::endl;
});

sse.on_event("update", [](const httplib::sse::SSEMessage &msg) {
    std::cout << "Update: " << msg.data << std::endl;
});

sse.start();

Async with Stop

httplib::sse::SSEClient sse(cli, "/events");

sse.on_message([](const httplib::sse::SSEMessage &msg) {
    std::cout << msg.data << std::endl;
});

sse.start_async();  // Returns immediately

// ... do other work ...

sse.stop();  // Stop when done

With Custom Headers (e.g., Authentication)

httplib::Headers headers = {
    {"Authorization", "Bearer token123"}
};

httplib::sse::SSEClient sse(cli, "/events", headers);
sse.start();

The Client's set_basic_auth() and set_bearer_token_auth() apply too; an Authorization header passed like this takes precedence.

Refreshing Auth Token on Reconnect

httplib::sse::SSEClient sse(cli, "/events",
    {{"Authorization", "Bearer " + get_token()}});

// Preemptively refresh token on each successful connection
sse.on_open([&sse]() {
    sse.set_headers({{"Authorization", "Bearer " + get_token()}});
});

// Or reactively refresh on auth failure (401 triggers reconnect)
sse.on_error([&sse](httplib::Error) {
    sse.set_headers({{"Authorization", "Bearer " + refresh_token()}});
});

sse.start();

Error Handling

sse.on_error([](httplib::Error err) {
    std::cerr << "Error: " << httplib::to_string(err) << std::endl;
});

sse.set_reconnect_interval(1000);
sse.set_max_reconnect_attempts(5);

sse.start();

SSE Protocol

The client parses SSE format according to the WHATWG HTML Living Standard:

event: custom-type
id: 123
data: {"message": "hello"}

data: simple message

: this is a comment (ignored)