github yhirose/cpp-httplib v0.56.0

2 hours ago

What's Changed

New features: WebSocket read timeout

  • ws::WebSocket::read() now reports a read timeout you set yourself as the new ReadResult::Timeout, and leaves the connection open. Previously every failure, a timeout included, returned Fail and marked the connection closed, so a read timeout could not be used to get control back and send on the same connection. Timeout is reported only when the timeout elapses on a message boundary with nothing consumed; a timeout partway through a fragmented message is still Fail. msg is left untouched on Timeout. Only a timeout set at runtime through set_read_timeout() comes back this way; the compile-time defaults are a backstop against a peer that has gone quiet, and when one of them elapses read() returns Fail and closes the connection as before, so code that never calls set_read_timeout() can keep using while (ws.read(msg))
  • Because Timeout is non-zero, while (ws.read(msg)) is not usable once you have set a read timeout: the loop body would run again with the previous message still in msg. Test the result instead:
    ws.set_read_timeout(std::chrono::milliseconds(100));
    std::string msg;
    while (ws.is_open()) {
      auto r = ws.read(msg);
      if (r == httplib::ws::Timeout) { continue; }  // nothing yet; send if you like
      if (r == httplib::ws::Fail) { break; }
      handle(msg);
    }
  • Add ws::WebSocket::set_read_timeout() (seconds/microseconds and std::chrono overloads), so a server handler can bound its own reads, for example to relay between connections instead of parking in read(). WebSocketClient::set_read_timeout() now also takes effect on an already-open connection instead of only seeding the next connect()
  • CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND is split into CPPHTTPLIB_WEBSOCKET_CLIENT_READ_TIMEOUT_SECOND (default 0, wait forever) and CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND (default 300). A client's read timeout is the caller's tool for taking back control, not a liveness check (that is ping/pong's job, see set_websocket_max_missed_pongs()), so a WebSocketClient no longer gives up on its own after 300s of silence. A server keeps the 300s ceiling that reclaims a worker from a peer that has gone quiet. The old macro still works, sets both, and emits a deprecation #pragma message

Security-relevant fixes

  • Send each credential only to the hop that reads it (#2579). An SSLClient behind a proxy sent Proxy-Authorization inside the TLS tunnel, where the origin reads it, and sent the origin's Authorization from set_basic_auth()/set_bearer_token_auth() on the CONNECT request, where the proxy reads it in plaintext before the tunnel exists. Headers from set_default_headers(), commonly origin credentials such as Authorization, Cookie or API keys, were attached to CONNECT as well. Each now goes only on the message its own hop reads
  • Reject ambiguously framed responses on the client (#2581). A response carrying both Transfer-Encoding and a non-zero Content-Length (RFC 9112 §6.3) is now rejected with Error::Read by both the buffered read path and open_stream(). The client delimited such a body by the transfer coding while an intermediary may go by Content-Length, so the two could disagree on where the body ends and desynchronize a reused connection. The server already rejected the same framing on requests; the check now lives in one shared helper. HEAD, 204 and 304 responses are exempt, Content-Length: 0 is tolerated, and a response whose final transfer coding is not chunked is still read until the connection closes, as RFC 9112 specifies for responses

Bug fixes

  • Reject a Range first-byte-pos that overflows ssize_t with 416 (#2580). When parse_range_header moved onto detail::from_chars, an out-of-range first-byte-pos stopped being an error and left the "no first-byte-pos" sentinel in place, so bytes=9223372036854775808-100 was served as the suffix range bytes=-100. An oversized last-byte-pos is still accepted, since RFC 9110 §14.1.2 reads it as "the remainder of the representation"
  • Don't compress a response whose handler already set Content-Encoding (#2575). A handler serving content it encoded itself, such as an asset compressed at build time, now keeps its own coding and bytes. This covers file-backed responses too: with static file compression enabled, a mount point or set_file_content() response that named its coding had its stored bytes compressed a second time and got a second Content-Encoding field line. Vary: Accept-Encoding is added only to a coding the server chose, so a handler that picks a representation from Accept-Encoding should set Vary itself
  • Read every multi-byte WebSocket frame field until it is complete. Stream::read() may return fewer bytes than requested, but only the payload was read in a loop, so a frame header that straddled the read buffer's boundary failed the frame and closed the connection
  • Record an error reason on the two WebSocketSSLStream::read() failure paths that returned -1 without one, so get_error() can no longer report a previous call's timeout

Documentation

  • README: document that a response already carrying Content-Encoding is sent as is, and when a handler has to add Vary itself
  • README-websocket and cookbook W01/W02/W04/W06 (en/ja): cover ReadResult::Timeout, which timeout produces it, the split client/server read timeout macros, and the while (ws.read(msg)) pitfall once a read timeout is set

Development

  • test/Makefile: put $(EXTRA_CXXFLAGS) after -fsanitize=address so it can override it (#2577)
  • CI: style-check now runs on macOS with Homebrew's clang-format, and the pre-commit hook calls the system clang-format, so local commits and CI use the same binary
  • Proxy tests: replace the external httpbin-style sites (httpcan.org, httpbingo.org) with a self-hosted go-httpbin container behind nginx in the squid docker-compose stack, so an upstream outage no longer fails CI
  • CI: remove the temporary windows-without-SSL flaky failure reporter now that the failures it tracked have stopped

Version 0.55.0 was tagged locally but never published; this release supersedes it.

Full Changelog: v0.54.1...v0.56.0

Don't miss a new cpp-httplib release

NewReleases is sending notifications on new releases.