From 8f0ff32056acce787803689ba9bb9906dd363cbf Mon Sep 17 00:00:00 2001 From: yhirose Date: Fri, 7 Aug 2026 17:17:07 -0400 Subject: [PATCH] Add WebSocket TLS and timeout recipes to the Cookbook T04 (mTLS) had grown a "WebSocketClient" subsection describing wss:// client certificates, and c12/t02 were getting similar WebSocketClient asides for timeouts and CA paths. The Cookbook's own index already separates WebSocket into its own category (W01-W04) from TLS/Security (T01-T05) and Client (C01-C19), so burying WebSocketClient specifics inside those pages fought the site's structure. Move that content into two new recipes under the WebSocket category instead: - W05: wss:// TLS setup (set_ca_cert_path CA directory parity, PemMemory client certificate) - W06: WebSocketClient's three timeouts, including the recently added chrono overloads T04, T02, C12, and W01 now carry a single reference link to the new pages instead of duplicated explanations, matching the site's existing cross-link convention. While rewriting T04's client-side section, noticed it documented SSLClient's file-path constructor but not its PemMemory one, even though the server-side section covered both forms for SSLServer. Added the missing PemMemory example so both sides are symmetric. --- docs-src/pages/en/cookbook/c12-timeouts.md | 2 + docs-src/pages/en/cookbook/index.md | 2 + .../en/cookbook/t02-cert-verification.md | 2 + docs-src/pages/en/cookbook/t04-mtls.md | 14 ++--- .../pages/en/cookbook/w01-websocket-echo.md | 2 +- .../pages/en/cookbook/w05-websocket-tls.md | 49 ++++++++++++++++++ .../en/cookbook/w06-websocket-timeouts.md | 51 +++++++++++++++++++ docs-src/pages/ja/cookbook/c12-timeouts.md | 2 + docs-src/pages/ja/cookbook/index.md | 2 + .../ja/cookbook/t02-cert-verification.md | 2 + docs-src/pages/ja/cookbook/t04-mtls.md | 14 ++--- .../pages/ja/cookbook/w01-websocket-echo.md | 2 +- .../pages/ja/cookbook/w05-websocket-tls.md | 49 ++++++++++++++++++ .../ja/cookbook/w06-websocket-timeouts.md | 51 +++++++++++++++++++ 14 files changed, 224 insertions(+), 20 deletions(-) create mode 100644 docs-src/pages/en/cookbook/w05-websocket-tls.md create mode 100644 docs-src/pages/en/cookbook/w06-websocket-timeouts.md create mode 100644 docs-src/pages/ja/cookbook/w05-websocket-tls.md create mode 100644 docs-src/pages/ja/cookbook/w06-websocket-timeouts.md diff --git a/docs-src/pages/en/cookbook/c12-timeouts.md b/docs-src/pages/en/cookbook/c12-timeouts.md index 83c2c9c..ff2e67e 100644 --- a/docs-src/pages/en/cookbook/c12-timeouts.md +++ b/docs-src/pages/en/cookbook/c12-timeouts.md @@ -48,3 +48,5 @@ cli.set_read_timeout(10s); ``` > **Warning:** The read timeout covers a single receive call — not the whole request. If data keeps trickling in during a large download, the request can take half an hour without ever hitting the timeout. To cap the total request time, use [C13. Set an overall timeout](../c13-max-timeout). + +> For WebSocket client timeouts, see [W06. Set Timeouts](../w06-websocket-timeouts). diff --git a/docs-src/pages/en/cookbook/index.md b/docs-src/pages/en/cookbook/index.md index fd58c4c..097552b 100644 --- a/docs-src/pages/en/cookbook/index.md +++ b/docs-src/pages/en/cookbook/index.md @@ -94,3 +94,5 @@ A collection of recipes that answer "How do I...?" questions. Each recipe is sel - [W02. Set a WebSocket heartbeat](w02-websocket-ping) - [W03. Handle connection close](w03-websocket-close) - [W04. Send and receive binary frames](w04-websocket-binary) +- [W05. Configure TLS for wss:// connections](w05-websocket-tls) +- [W06. Set timeouts](w06-websocket-timeouts) diff --git a/docs-src/pages/en/cookbook/t02-cert-verification.md b/docs-src/pages/en/cookbook/t02-cert-verification.md index 91fe678..d4ed118 100644 --- a/docs-src/pages/en/cookbook/t02-cert-verification.md +++ b/docs-src/pages/en/cookbook/t02-cert-verification.md @@ -51,3 +51,5 @@ On most Linux distributions, root certificates live in a single file like `/etc/ > The same APIs work on the mbedTLS and wolfSSL backends. For choosing between backends, see [T01. Choosing between OpenSSL, mbedTLS, and wolfSSL](../t01-tls-backends). > For details on diagnosing failures, see [C18. Handle SSL errors](../c18-ssl-errors). + +> For TLS configuration on a WebSocket client (`wss://`), see [W05. Configure TLS for wss:// Connections](../w05-websocket-tls). diff --git a/docs-src/pages/en/cookbook/t04-mtls.md b/docs-src/pages/en/cookbook/t04-mtls.md index 1a57fd7..6558b64 100644 --- a/docs-src/pages/en/cookbook/t04-mtls.md +++ b/docs-src/pages/en/cookbook/t04-mtls.md @@ -57,25 +57,21 @@ auto res = cli.Get("/"); Note you're using `SSLClient` directly, not `Client`. If the private key has a password, pass it as the fifth argument. -## WebSocket clients - -`ws::WebSocketClient` has the same `PemMemory` struct, so `wss://` connections can present a client certificate too. +The client side has the same `PemMemory` struct too, letting you set the client certificate from PEM in memory. ```cpp -httplib::ws::WebSocketClient::PemMemory pem{}; +httplib::SSLClient::PemMemory pem{}; pem.cert_pem = client_cert.data(); pem.cert_pem_len = client_cert.size(); pem.key_pem = client_key.data(); pem.key_pem_len = client_key.size(); -httplib::ws::WebSocketClient ws("wss://api.example.com/ws", pem); +httplib::SSLClient cli("api.example.com", 443, pem); -if (ws.connect()) { - ws.send("hello"); -} +auto res = cli.Get("/"); ``` -Passing `PemMemory` to a `ws://` (non-TLS) URL is silently ignored. There's no constructor that reads the cert files directly, so unlike `SSLClient` you always load the PEM into memory yourself before passing it in. +> For mTLS with a WebSocket client (`wss://`), see [W05. Configure TLS for wss:// Connections](../w05-websocket-tls). ## Read client info from a handler diff --git a/docs-src/pages/en/cookbook/w01-websocket-echo.md b/docs-src/pages/en/cookbook/w01-websocket-echo.md index a9920b8..e6f9b9d 100644 --- a/docs-src/pages/en/cookbook/w01-websocket-echo.md +++ b/docs-src/pages/en/cookbook/w01-websocket-echo.md @@ -85,4 +85,4 @@ svr.new_task_queue = [] { See [S21. Configure the thread pool](../s21-thread-pool). -> **Note:** To run WebSocket over HTTPS, use `httplib::SSLServer` instead of `httplib::Server` — the same `WebSocket()` handler just works. On the client side, use a `wss://` URL. +> **Note:** To run WebSocket over HTTPS, use `httplib::SSLServer` instead of `httplib::Server` — the same `WebSocket()` handler just works. On the client side, use a `wss://` URL. For CA and client certificate configuration, see [W05. Configure TLS for wss:// Connections](../w05-websocket-tls). diff --git a/docs-src/pages/en/cookbook/w05-websocket-tls.md b/docs-src/pages/en/cookbook/w05-websocket-tls.md new file mode 100644 index 0000000..34fa609 --- /dev/null +++ b/docs-src/pages/en/cookbook/w05-websocket-tls.md @@ -0,0 +1,49 @@ +--- +title: "W05. Configure TLS for wss:// Connections" +order: 55 +status: "draft" +--- + +Client-side TLS configuration for `wss://` (WebSocket over TLS) connections uses almost the same API as `SSLClient`. `ws::WebSocketClient` handles both `ws://` and `wss://` through the same class, so there's no separate class to switch to the way `SSLClient` requires. + +```cpp +httplib::ws::WebSocketClient ws1("ws://localhost:8080/ws"); // plaintext +httplib::ws::WebSocketClient ws2("wss://localhost:8443/ws"); // TLS +``` + +## Verifying the server certificate + +Use `set_ca_cert_path()` to point at your own CA certificate. The signature matches `SSLClient`: the first argument is the CA certificate file, the second is an optional CA directory. + +```cpp +httplib::ws::WebSocketClient ws("wss://internal.example.com/ws"); +ws.set_ca_cert_path("/etc/ssl/certs/internal-ca.pem"); + +if (ws.connect()) { + ws.send("hello"); +} +``` + +To disable certificate verification entirely, use `enable_server_certificate_verification(false)`. For details on that behavior, see [T02. Control SSL Certificate Verification](../t02-cert-verification). + +## Presenting a client certificate (mTLS) + +`ws::WebSocketClient` has a constructor overload that takes a `PemMemory` struct, letting `wss://` connections present a client certificate. + +```cpp +httplib::ws::WebSocketClient::PemMemory pem{}; +pem.cert_pem = client_cert.data(); +pem.cert_pem_len = client_cert.size(); +pem.key_pem = client_key.data(); +pem.key_pem_len = client_key.size(); + +httplib::ws::WebSocketClient ws("wss://api.example.com/ws", pem); + +if (ws.connect()) { + ws.send("hello"); +} +``` + +Passing `PemMemory` to a `ws://` (non-TLS) URL is silently ignored. There's no constructor that reads the cert files directly, so unlike `SSLClient` you always load the PEM into memory yourself before passing it in. + +For the full mTLS picture, including server-side setup and use cases, see [T04. Configure mTLS](../t04-mtls). diff --git a/docs-src/pages/en/cookbook/w06-websocket-timeouts.md b/docs-src/pages/en/cookbook/w06-websocket-timeouts.md new file mode 100644 index 0000000..0161959 --- /dev/null +++ b/docs-src/pages/en/cookbook/w06-websocket-timeouts.md @@ -0,0 +1,51 @@ +--- +title: "W06. Set Timeouts" +order: 56 +status: "draft" +--- + +`ws::WebSocketClient` has the same three kinds of timeouts as `Client`, with the same meaning. + +| Kind | API | Default | +| --- | --- | --- | +| Connection | `set_connection_timeout` | 300s | +| Read | `set_read_timeout` | 300s (`CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND`) | +| Write | `set_write_timeout` | 5s | + +## Basic usage + +```cpp +httplib::ws::WebSocketClient ws("ws://localhost:8080/ws"); + +ws.set_connection_timeout(5, 0); // 5 seconds +ws.set_read_timeout(30, 0); // 30 seconds +ws.set_write_timeout(10, 0); // 10 seconds + +if (ws.connect()) { + ws.send("hello"); +} +``` + +Set these before calling `connect()`. + +## Use `std::chrono` + +Just like `Client`, there's an overload that takes a `std::chrono` duration directly. + +```cpp +using namespace std::chrono_literals; + +ws.set_connection_timeout(5s); +ws.set_read_timeout(30s); +ws.set_write_timeout(10s); +``` + +## Watch out for what the read timeout means + +`set_read_timeout()` applies to a single `read()` call. If no message arrives within that time, `read()` returns `ReadResult::Fail`. For connections where long idle periods are normal — waiting on notifications, for example — set a longer timeout, or reconnect from your application code when the read fails. + +> Unresponsive-peer detection via Ping/Pong is a separate mechanism. See [W02. Set a WebSocket Heartbeat](../w02-websocket-ping) for details. + +## How this differs from `Client` + +For `Client`'s timeout configuration, see [C12. Set Timeouts](../c12-timeouts). The behavior and API are nearly identical, but `WebSocketClient` has no equivalent to `set_max_timeout()` for capping the whole request — once connected, the connection stays open for as long as you keep calling `read()`. diff --git a/docs-src/pages/ja/cookbook/c12-timeouts.md b/docs-src/pages/ja/cookbook/c12-timeouts.md index 1fb04f3..b098ecb 100644 --- a/docs-src/pages/ja/cookbook/c12-timeouts.md +++ b/docs-src/pages/ja/cookbook/c12-timeouts.md @@ -48,3 +48,5 @@ cli.set_read_timeout(10s); ``` > **Warning:** 読み取りタイムアウトは「1回の受信待ち」に対するタイムアウトです。大きなファイルのダウンロードで途中ずっとデータが流れている限り、リクエスト全体で30分かかっても発火しません。リクエスト全体の時間制限を設けたい場合は[C13. 全体タイムアウトを設定する](../c13-max-timeout)を使ってください。 + +> WebSocketクライアントのタイムアウト設定は[W06. タイムアウトを設定する](../w06-websocket-timeouts)を参照してください。 diff --git a/docs-src/pages/ja/cookbook/index.md b/docs-src/pages/ja/cookbook/index.md index 01ff253..d761e8a 100644 --- a/docs-src/pages/ja/cookbook/index.md +++ b/docs-src/pages/ja/cookbook/index.md @@ -94,3 +94,5 @@ status: "draft" - [W02. ハートビートを設定する](w02-websocket-ping) - [W03. 接続クローズをハンドリングする](w03-websocket-close) - [W04. バイナリフレームを送受信する](w04-websocket-binary) +- [W05. wss接続でTLSを設定する](w05-websocket-tls) +- [W06. タイムアウトを設定する](w06-websocket-timeouts) diff --git a/docs-src/pages/ja/cookbook/t02-cert-verification.md b/docs-src/pages/ja/cookbook/t02-cert-verification.md index 85495b8..6fe716b 100644 --- a/docs-src/pages/ja/cookbook/t02-cert-verification.md +++ b/docs-src/pages/ja/cookbook/t02-cert-verification.md @@ -51,3 +51,5 @@ cli.enable_server_hostname_verification(false); > mbedTLSやwolfSSLバックエンドでも同じAPIが使えます。バックエンドの選び方は[T01. OpenSSL・mbedTLS・wolfSSLの選択指針](../t01-tls-backends)を参照してください。 > 失敗したときの詳細を調べる方法は[C18. SSLエラーをハンドリングする](../c18-ssl-errors)を参照してください。 + +> WebSocketクライアント(`wss://`)のTLS設定は[W05. wss接続でTLSを設定する](../w05-websocket-tls)を参照してください。 diff --git a/docs-src/pages/ja/cookbook/t04-mtls.md b/docs-src/pages/ja/cookbook/t04-mtls.md index ba30965..f41199f 100644 --- a/docs-src/pages/ja/cookbook/t04-mtls.md +++ b/docs-src/pages/ja/cookbook/t04-mtls.md @@ -57,25 +57,21 @@ auto res = cli.Get("/"); `Client`ではなく`SSLClient`を直接使う点に注意してください。秘密鍵にパスワードがある場合は第5引数で渡せます。 -## WebSocketクライアントの場合 - -`ws::WebSocketClient`にも同じ`PemMemory`構造体があり、`wss://`接続でクライアント証明書を使えます。 +クライアント側にも同じ`PemMemory`構造体があり、メモリ上のPEMからクライアント証明書を設定できます。 ```cpp -httplib::ws::WebSocketClient::PemMemory pem{}; +httplib::SSLClient::PemMemory pem{}; pem.cert_pem = client_cert.data(); pem.cert_pem_len = client_cert.size(); pem.key_pem = client_key.data(); pem.key_pem_len = client_key.size(); -httplib::ws::WebSocketClient ws("wss://api.example.com/ws", pem); +httplib::SSLClient cli("api.example.com", 443, pem); -if (ws.connect()) { - ws.send("hello"); -} +auto res = cli.Get("/"); ``` -`ws://`(非TLS)のURLに`PemMemory`を渡した場合は黙って無視されます。ファイルパスから直接読み込むコンストラクタは用意されていないので、`SSLClient`と違いPEMをメモリ上に読み込んでから渡す必要があります。 +> WebSocketクライアント(`wss://`)でmTLSを使う場合は[W05. wss接続でTLSを設定する](../w05-websocket-tls)を参照してください。 ## ハンドラからクライアント情報を取得する diff --git a/docs-src/pages/ja/cookbook/w01-websocket-echo.md b/docs-src/pages/ja/cookbook/w01-websocket-echo.md index 6a96eec..d7a38f9 100644 --- a/docs-src/pages/ja/cookbook/w01-websocket-echo.md +++ b/docs-src/pages/ja/cookbook/w01-websocket-echo.md @@ -85,4 +85,4 @@ svr.new_task_queue = [] { 詳細は[S21. マルチスレッド数を設定する](../s21-thread-pool)を参照してください。 -> **Note:** HTTPSサーバーの上でWebSocketを動かしたいときは、`httplib::Server`の代わりに`httplib::SSLServer`を使えば、同じ`WebSocket()`ハンドラがそのまま動きます。クライアント側は`wss://`スキームを指定するだけです。 +> **Note:** HTTPSサーバーの上でWebSocketを動かしたいときは、`httplib::Server`の代わりに`httplib::SSLServer`を使えば、同じ`WebSocket()`ハンドラがそのまま動きます。クライアント側は`wss://`スキームを指定するだけです。CA証明書やクライアント証明書の設定は[W05. wss接続でTLSを設定する](../w05-websocket-tls)を参照してください。 diff --git a/docs-src/pages/ja/cookbook/w05-websocket-tls.md b/docs-src/pages/ja/cookbook/w05-websocket-tls.md new file mode 100644 index 0000000..ca9bdf9 --- /dev/null +++ b/docs-src/pages/ja/cookbook/w05-websocket-tls.md @@ -0,0 +1,49 @@ +--- +title: "W05. wss接続でTLSを設定する" +order: 55 +status: "draft" +--- + +`wss://`(WebSocket over TLS)接続のクライアント側TLS設定は、`SSLClient`とほぼ同じAPIです。`ws::WebSocketClient`は`ws://`と`wss://`を同じクラスで扱うので、`SSLClient`のような別クラスへの切り替えは不要です。 + +```cpp +httplib::ws::WebSocketClient ws1("ws://localhost:8080/ws"); // 平文 +httplib::ws::WebSocketClient ws2("wss://localhost:8443/ws"); // TLS +``` + +## サーバー証明書の検証 + +`set_ca_cert_path()`で独自のCA証明書を指定できます。シグネチャは`SSLClient`と同じで、第1引数がCA証明書ファイル、第2引数がCA証明書ディレクトリ(省略可)です。 + +```cpp +httplib::ws::WebSocketClient ws("wss://internal.example.com/ws"); +ws.set_ca_cert_path("/etc/ssl/certs/internal-ca.pem"); + +if (ws.connect()) { + ws.send("hello"); +} +``` + +証明書検証そのものを無効にしたい場合は`enable_server_certificate_verification(false)`が使えます。挙動の詳細は[T02. SSL証明書の検証を制御する](../t02-cert-verification)を参照してください。 + +## クライアント証明書を使う(mTLS) + +`ws::WebSocketClient`には`PemMemory`構造体を受け取るコンストラクタがあり、`wss://`接続でクライアント証明書を提示できます。 + +```cpp +httplib::ws::WebSocketClient::PemMemory pem{}; +pem.cert_pem = client_cert.data(); +pem.cert_pem_len = client_cert.size(); +pem.key_pem = client_key.data(); +pem.key_pem_len = client_key.size(); + +httplib::ws::WebSocketClient ws("wss://api.example.com/ws", pem); + +if (ws.connect()) { + ws.send("hello"); +} +``` + +`ws://`(非TLS)のURLに`PemMemory`を渡した場合は黙って無視されます。`SSLClient`と違い、ファイルパスから直接読み込むコンストラクタは用意されていないので、PEMをメモリ上に読み込んでから渡す必要があります。 + +mTLSの全体像(サーバー側の設定や用途の解説を含む)は[T04. mTLSを設定する](../t04-mtls)を参照してください。 diff --git a/docs-src/pages/ja/cookbook/w06-websocket-timeouts.md b/docs-src/pages/ja/cookbook/w06-websocket-timeouts.md new file mode 100644 index 0000000..ccb80ee --- /dev/null +++ b/docs-src/pages/ja/cookbook/w06-websocket-timeouts.md @@ -0,0 +1,51 @@ +--- +title: "W06. タイムアウトを設定する" +order: 56 +status: "draft" +--- + +`ws::WebSocketClient`には`Client`と同じ3種類のタイムアウトがあり、意味も同じです。 + +| 種類 | API | デフォルト | +| --- | --- | --- | +| 接続タイムアウト | `set_connection_timeout` | 300秒 | +| 読み取りタイムアウト | `set_read_timeout` | 300秒(`CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND`) | +| 書き込みタイムアウト | `set_write_timeout` | 5秒 | + +## 基本の使い方 + +```cpp +httplib::ws::WebSocketClient ws("ws://localhost:8080/ws"); + +ws.set_connection_timeout(5, 0); // 5秒 +ws.set_read_timeout(30, 0); // 30秒 +ws.set_write_timeout(10, 0); // 10秒 + +if (ws.connect()) { + ws.send("hello"); +} +``` + +`connect()`を呼ぶ前に設定してください。 + +## `std::chrono`で指定する + +`Client`と同じく、`std::chrono`の期間を直接渡すオーバーロードもあります。 + +```cpp +using namespace std::chrono_literals; + +ws.set_connection_timeout(5s); +ws.set_read_timeout(30s); +ws.set_write_timeout(10s); +``` + +## 読み取りタイムアウトの意味に注意 + +`set_read_timeout()`は「1回の`read()`呼び出し」に対するタイムアウトです。メッセージが届かないまま指定時間が経過すると`read()`が`ReadResult::Fail`を返します。通知の待受のように長時間メッセージが来ないことが正常な接続では、意図せず切断されないよう長めに設定するか、切断されたらアプリケーション側で再接続してください。 + +> Ping/Pongによる無応答ピア検出は別の仕組みです。詳しくは[W02. ハートビートを設定する](../w02-websocket-ping)を参照してください。 + +## `Client`との違い + +`Client`のタイムアウト設定については[C12. タイムアウトを設定する](../c12-timeouts)を参照してください。挙動とAPIはほぼ同じですが、`WebSocketClient`には`set_max_timeout()`に相当するリクエスト全体のタイムアウトはありません。接続を確立したあとは、`read()`のループを回し続ける限り接続が維持されます。