Return ws::Result from WebSocketClient::connect() instead of bool

Issue #2531 asked for connect() to expose error detail the way
ClientImpl/SSLClient do via Result, instead of collapsing every failure
into a bare bool. The groundwork (detail::ClientTlsSessionError) was
already laid during the WebSocketClient/SSLClient dedup but left
unwired.

- Add httplib::ws::Result: explicit operator bool(), error(), and
  flattened upgrade-response accessors (status(), headers(),
  get_header_value(), has_header()); ssl_error()/ssl_backend_error() on
  SSL builds.
- Add Error::WebSocketHandshake for upgrade-validation failures
  (non-101 status, bad Sec-WebSocket-Accept, bad Upgrade/Connection
  headers).
- Extract detail::parse_status_line from ClientImpl::read_response_line
  and reuse it in read_websocket_upgrade_response, replacing the
  previous "HTTP/1.1 101" substring match with a proper parse. Non-101
  responses now surface their status and headers instead of being read
  and discarded.
- Wire WebSocketClient::create_stream() to capture ClientTlsSessionError
  so TLS failures (SSLServerVerification, SSLServerHostnameVerification,
  ...) reach the caller with backend error codes.
- Update tests and README-websocket.md accordingly.

This is a source-breaking change for callers that assign the result to
bool (e.g. bool ok = cli.connect();); if (cli.connect()) and gtest's
ASSERT_TRUE/EXPECT_FALSE(...) macros are unaffected since operator bool
still participates in contextual conversion.
This commit is contained in:
yhirose
2026-08-07 18:02:29 -04:00
parent 8d5085df1b
commit 6018c7feb3
3 changed files with 232 additions and 59 deletions

View File

@@ -151,8 +151,15 @@ explicit WebSocketClient(const std::string &scheme_host_port_path,
// Check if the URL was parsed successfully
bool is_valid() const;
// Connect (performs HTTP upgrade handshake)
bool connect();
// Connect (performs HTTP upgrade handshake). The returned Result is truthy
// only when the handshake fully succeeded; on failure it describes what went
// wrong:
// res.error() httplib::Error identifying the failing layer
// res.status() HTTP status of the upgrade response (-1 if none)
// res.headers() headers of the upgrade response
// res.ssl_error() TLS error detail (wss://, SSL builds only)
// res.ssl_backend_error() backend-specific TLS error code (SSL builds only)
Result connect();
// Get the subprotocol selected by the server (empty if none)
const std::string &subprotocol() const;
@@ -221,6 +228,26 @@ if (ws.connect()) {
}
```
### Inspecting Connection Failures
`connect()` returns a `Result` that tells you why a connection attempt failed.
`error()` distinguishes network problems (`Connection`, `ConnectionTimeout`),
TLS problems (`SSLConnection`, `SSLServerVerification`,
`SSLServerHostnameVerification`), and upgrade rejections
(`WebSocketHandshake`). When the server answered with something other than
`101 Switching Protocols`, `status()` and `headers()` carry that response:
```cpp
auto res = ws.connect();
if (!res) {
std::cerr << "connect failed: " << httplib::to_string(res.error()) << std::endl;
if (res.status() != -1) {
// The server responded but refused the upgrade (e.g. 401, 404)
std::cerr << "HTTP status: " << res.status() << std::endl;
}
}
```
### Text and Binary Messages
Check the `ReadResult` return value to distinguish between text and binary: