Files
cpp-httplib/docs-src/pages/en/cookbook/e04-sse-client.md
T
yhirose 9bfd2ddf1c Fix README and docs pages that no longer match the code
Bring the READMEs and the docs pages (en and ja) in line with the
current header:

- Fix the client sample that does not compile, the Transfer-Encoding
  statement for default headers, the verifier callback comment, the
  thread pool spawn rule, the sanitize_filename description, and the
  inverted Accept-Encoding sentence.
- Describe recent behaviour: TLS errors behind Read/Write failures,
  Host validation, Connection: close for a body without length,
  content_reader rules, refused CONNECT, WebSocketClient proxies,
  CPPHTTPLIB_INTERIM_RESPONSE_MAX_COUNT, the SSE event size limit, and
  the payload limit of the Stream API.
- Complete the API references of WebSocket, WebSocketClient and the
  stream:: helpers, and the SSE reconnect rules.
- Fix cookbook pages: keychain loading on every backend, received Close
  status, a multipart sample that trusted the client's file name, an
  echo sample that sent binary as text, SNI, pre-request handler scope,
  stop() with WebSocket handlers, and the thread pool notes.
- Restore the mutex in the LLM app's Chapter 5 code.
2026-10-18 10:13:57 -04:00

3.5 KiB

title, order, status
title order status
E04. Receive SSE on the Client 51 draft

cpp-httplib ships a dedicated sse::SSEClient class. It handles auto-reconnect, per-event-name dispatch, and Last-Event-ID tracking for you — so receiving SSE is painless.

Basic usage

#include <httplib.h>

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

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

sse.start(); // blocking

Build an SSEClient with a Client and a path, register a callback with on_message(), and call start(). The event loop kicks in and automatically reconnects if the connection drops.

Dispatch by event name

When the server sends events with an event: field, register a handler per name via on_event().

sse.on_event("message", [](const auto &msg) {
  std::cout << "chat: " << msg.data << std::endl;
});

sse.on_event("join", [](const auto &msg) {
  std::cout << msg.data << " joined" << std::endl;
});

sse.on_event("leave", [](const auto &msg) {
  std::cout << msg.data << " left" << std::endl;
});

on_message() is the generic fallback: it receives every event that has no handler registered through on_event(). With on_event("message", ...) registered as above, message events go there instead.

Connection lifecycle and errors

sse.on_open([] {
  std::cout << "connected" << std::endl;
});

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

Hook into connection open and error events. Even when the error handler fires, SSEClient keeps trying to reconnect in the background. It stops after a 204, 403, or 404 response, or once the attempts set with set_max_reconnect_attempts() run out.

It also stops when an event, or a line still waiting for its end, grows past CPPHTTPLIB_SSE_MAX_EVENT_LENGTH (16MB by default, 0 for no limit). The error handler then gets Error::ExceedMaxPayloadSize. The client's set_payload_max_length() doesn't apply to the stream as a whole, since an event stream has no end.

Run asynchronously

If you don't want to block the main thread, use start_async().

sse.start_async();

// main thread continues to do other things
do_other_work();

// when you're done, stop it
sse.stop();

start_async() spawns a background thread to run the event loop. Use stop() to shut it down cleanly.

Configure reconnection

You can tune the reconnect interval and maximum retries.

sse.set_reconnect_interval(5000);    // 5 seconds
sse.set_max_reconnect_attempts(10);  // up to 10 (0 = unlimited)

If the server sends a retry: field, that takes precedence.

Automatic Last-Event-ID

SSEClient tracks the id of each received event internally and sends it back as Last-Event-ID on reconnect. As long as the server sends events with id:, this all works automatically.

An empty id: clears the stored id. An id: that can't be sent as a header value (one with NUL or other control characters, or with leading or trailing whitespace) is ignored, and the previous id stays.

std::cout << "last id: " << sse.last_event_id() << std::endl;

Use last_event_id() to read the current value.

Note: SSEClient::start() blocks, which is fine for a one-off command-line tool. For GUI apps or embedded in a server, the start_async() + stop() pair is the usual pattern.

For the server side, see E01. Implement an SSE server.