mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-19 11:19:36 +00:00
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.
This commit is contained in:
1 parent
94cf1b5c9a
commit
9bfd2ddf1c
77 files changed
+378
-145
No files matched your search
@@ -50,6 +50,6 @@ httplib::Headers headers = {
|
||||
auto res = cli.Get("/users", headers);
|
||||
```
|
||||
|
||||
リクエスト単位で渡したヘッダーはデフォルトヘッダーに**追加**されます。両方がサーバーに送られます。
|
||||
リクエスト単位で渡したヘッダーはデフォルトヘッダーに**追加**され、両方がサーバーに送られます。デフォルトヘッダーと同じ名前のヘッダーを渡した場合は、リクエスト単位のものだけが送られます。
|
||||
|
||||
> Bearerトークンを使った認証の詳細は[C06. BearerトークンでAPIを呼ぶ](../c06-bearer-token)を参照してください。
|
||||
@@ -33,6 +33,8 @@ auto res = cli.Get("/");
|
||||
|
||||
多くのサイトはHTTPアクセスをHTTPSへリダイレクトします。`set_follow_location(true)`を有効にしておけば、こうしたケースも透過的に扱えます。スキームやホストが変わっても自動で追従します。
|
||||
|
||||
リダイレクト先が別のオリジン(スキーム、ホスト、ポートのどれかが違う)の場合、`Authorization`、`Cookie`、`Proxy-Authorization`ヘッダーは外され、`set_basic_auth()`などで設定した認証情報も送られません。クライアント証明書も提示しません。それ以外のヘッダーはデフォルトヘッダーも含めてそのまま送られ、タイムアウト、ペイロードの上限、TLSの検証設定も引き継がれます。
|
||||
|
||||
> **Warning:** HTTPSへのリダイレクトを追従するには、cpp-httplibをOpenSSL(または他のTLSバックエンド)付きでビルドしておく必要があります。TLSサポートがないと、HTTPSへのリダイレクトは失敗します。
|
||||
|
||||
> **Note:** リダイレクトを追従すると、リクエストの実行時間は伸びます。タイムアウトの設定は[C12. タイムアウトを設定する](../c12-timeouts)を参照してください。
|
||||
@@ -12,7 +12,7 @@ status: "draft"
|
||||
httplib::Client cli("https://storage.example.com");
|
||||
|
||||
auto [size, provider] = httplib::make_file_body("backup.tar.gz");
|
||||
if (size == 0) {
|
||||
if (!provider) {
|
||||
std::cerr << "Failed to open file" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ auto res = cli.Put("/bucket/backup.tar.gz", size,
|
||||
|
||||
## ファイルが開けなかったとき
|
||||
|
||||
`make_file_body()`は開けなかった場合、`size`を`0`、`provider`を空の関数オブジェクトとして返します。そのまま送信するとおかしな結果になるので、必ず`size`をチェックしてください。
|
||||
`make_file_body()`は開けなかった場合、`size`を`0`、`provider`を空の関数オブジェクトとして返します。そのまま送信すると空のボディが送られてしまうので、必ず`provider`をチェックしてください。`size`では判定できません。空のファイルでも`size`は`0`になり、そのときの`provider`は正しく使えるからです。
|
||||
|
||||
> **Warning:** `make_file_body()`はContent-Lengthを最初に確定させる必要があるため、ファイルサイズをあらかじめ取得します。送信中にファイルサイズが変わる可能性がある場合は、このAPIには向きません。
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ status: "draft"
|
||||
| 読み取りタイムアウト | `set_read_timeout` | 300秒 | レスポンスを受信する際の1回の`recv`待ち時間 |
|
||||
| 書き込みタイムアウト | `set_write_timeout` | 5秒 | リクエストを送信する際の1回の`send`待ち時間 |
|
||||
|
||||
HTTPSでは、接続タイムアウトがTLSハンドシェイク全体の制限時間にもなります。時間内にハンドシェイクが終わらないと、`Error::SSLConnection`で失敗します。
|
||||
|
||||
## 基本の使い方
|
||||
|
||||
```cpp
|
||||
|
||||
@@ -4,7 +4,7 @@ order: 13
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
[C12. タイムアウトを設定する](../c12-timeouts)で紹介した3種類のタイムアウトは、いずれも「1回の`send`や`recv`」に対するものです。リクエスト全体の所要時間に上限を設けたい場合は、`set_max_timeout()`を使います。
|
||||
[C12. タイムアウトを設定する](../c12-timeouts)で紹介した3種類のタイムアウトは、どれも一部の待ち時間にしか効きません。読み取りと書き込みのタイムアウトは「1回の`recv`や`send`」に、接続タイムアウトは接続の確立に対するものです。リクエスト全体の所要時間に上限を設けたい場合は、`set_max_timeout()`を使います。
|
||||
|
||||
## 基本の使い方
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ std::string big_payload = build_payload();
|
||||
auto res = cli.Post("/api/data", big_payload, "application/json");
|
||||
```
|
||||
|
||||
`set_compress(true)`を呼んでおくと、POSTやPUTのリクエストボディが圧縮されて送信されます。圧縮方式は、ビルドで有効にしたもののうち、Brotli、gzip、Zstdの順で最初に当てはまるものです。サーバー側もその方式に対応している必要があります。
|
||||
`set_compress(true)`を呼んでおくと、POST、PUT、PATCHのリクエストボディが圧縮されて送信されます。圧縮方式は、ビルドで有効にしたもののうち、Brotli、gzip、Zstdの順で最初に当てはまるものです。サーバー側もその方式に対応している必要があります。
|
||||
|
||||
## レスポンスを解凍する
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ auto res = cli.Get("/users");
|
||||
|
||||
プロキシ経由でリクエストが送られます。HTTPSの場合はCONNECTメソッドでトンネルが張られるので、cpp-httplib側で特別な設定は要りません。
|
||||
|
||||
プロキシがトンネルを拒否すると、リクエストは`Error::ProxyConnection`で失敗します。ただし407だけは例外で、レスポンスとして返ってきます。`res`はtruthyで`res->status`は407ですが、`res.error()`は`Error::ProxyConnection`のままです。
|
||||
|
||||
## プロキシに認証を設定する
|
||||
|
||||
プロキシ自体が認証を要求する場合は、`set_proxy_basic_auth()`や`set_proxy_bearer_token_auth()`を使います。
|
||||
|
||||
@@ -4,7 +4,7 @@ order: 17
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
`cli.Get()`や`cli.Post()`は`Result`型を返します。リクエストが失敗したとき(サーバーに到達できなかった、タイムアウトしたなど)、返り値は「falsy」になります。詳しい原因を知りたい場合は`Result::error()`を使います。
|
||||
`cli.Get()`や`cli.Post()`は`Result`型を返します。リクエストが失敗したとき(サーバーに到達できなかった、タイムアウトしたなど)、返り値は「falsy」になります(例外が1つあり、下の表で説明します)。詳しい原因を知りたい場合は`Result::error()`を使います。
|
||||
|
||||
## 基本の判定
|
||||
|
||||
@@ -27,14 +27,15 @@ if (res) {
|
||||
|
||||
| 値 | 意味 |
|
||||
| --- | --- |
|
||||
| `Error::Connection` | サーバーに接続できなかった |
|
||||
| `Error::Connection` | サーバーに接続できなかった。または、使えないURL(authorityにuserinfoやバックスラッシュがある、ポートが不正など)から`Client`を作った。後者の場合は`cli.is_valid()`が`false`になる |
|
||||
| `Error::ConnectionTimeout` | 接続タイムアウト(`set_connection_timeout`) |
|
||||
| `Error::Read` / `Error::Write` | 送受信中のエラー。`set_read_timeout`や`set_max_timeout`によるタイムアウトも`Error::Read`になる |
|
||||
| `Error::Timeout` | `stream::Get()`や`SSEClient`で、ボディの読み取りがタイムアウトした |
|
||||
| `Error::ExceedRedirectCount` | リダイレクト回数が上限を超えた |
|
||||
| `Error::SSLConnection` | TLSハンドシェイクに失敗 |
|
||||
| `Error::SSLServerVerification` | サーバー証明書の検証に失敗 |
|
||||
| `Error::Canceled` | 進捗コールバックから`false`が返された |
|
||||
| `Error::ProxyConnection` | プロキシがCONNECTのトンネルを拒否した。ただし407は例外で、`res`はtruthyかつ`res->status == 407`になり、`res.error()`は`Error::ProxyConnection`のまま |
|
||||
| `Error::Canceled` | 進捗コールバック、`ContentReceiver`、`ResponseHandler`、アップロード用のコンテンツプロバイダのいずれかから`false`が返された |
|
||||
|
||||
## ステータスコードとの使い分け
|
||||
|
||||
|
||||
@@ -17,14 +17,17 @@ if (!res) {
|
||||
std::cerr << "error: " << httplib::to_string(err) << std::endl;
|
||||
|
||||
if (err == httplib::Error::SSLConnection ||
|
||||
err == httplib::Error::SSLServerVerification) {
|
||||
err == httplib::Error::SSLServerVerification ||
|
||||
err == httplib::Error::Read || err == httplib::Error::Write) {
|
||||
std::cerr << "ssl_error: " << res.ssl_error() << std::endl;
|
||||
std::cerr << "ssl_backend_error: " << res.ssl_backend_error() << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`ssl_error()`は、どのTLSバックエンドでも共通のエラー種別(`httplib::tls::ErrorCode`)を`int`で返します。`ssl_backend_error()`は、バックエンドが返したエラー値そのものです。OpenSSLなら、ハンドシェイクの失敗では`ERR_get_error()`の値が、証明書検証の失敗では検証結果のコード(`X509_V_ERR_*`)が入ります。
|
||||
`ssl_error()`は、どのTLSバックエンドでも共通のエラー種別(`httplib::tls::ErrorCode`)を`int`で返します。`ssl_backend_error()`は、バックエンドが返したエラー値そのものです。OpenSSLなら、ハンドシェイクやその後の読み書きの失敗では`ERR_get_error()`の値が、証明書検証の失敗では検証結果のコード(`X509_V_ERR_*`)が入ります。
|
||||
|
||||
ハンドシェイクのあとで失敗したリクエストは、`Error::Read`や`Error::Write`を返します。その場合も、`ssl_error()`と`ssl_backend_error()`には原因となったTLSのエラーが入ります。たとえば、サーバーが要求するクライアント証明書を送らなかった場合、OpenSSLのエラーは「tlsv13 alert certificate required」になります。どちらの値もリクエストごとにリセットされ、TLSのエラーが絡んでいなければ`0`です。
|
||||
|
||||
## OpenSSLのエラーを文字列化する
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ cli.set_error_logger([](const httplib::Error &err, const httplib::Request *req)
|
||||
});
|
||||
```
|
||||
|
||||
第2引数の`req`はヌルポインタのこともあります。リクエストを組み立てる前の段階で失敗した場合です。使う前に必ずヌルチェックしてください。
|
||||
第2引数の`req`はヌルポインタのこともあります。TLSハンドシェイクやCA証明書の読み込み、プロキシのトンネル確立など、接続の準備中にエラーが起きた場合です。使う前に必ずヌルチェックしてください。
|
||||
|
||||
## 両方を組み合わせる
|
||||
|
||||
|
||||
@@ -55,7 +55,9 @@ sse.on_error([](httplib::Error err) {
|
||||
});
|
||||
```
|
||||
|
||||
接続確立時やエラー発生時にもフックを挟めます。エラーハンドラが呼ばれても、`SSEClient`は内部で再接続を試みます。ただし、サーバーが204、403、404のいずれかを返したときは再接続しません。
|
||||
接続確立時やエラー発生時にもフックを挟めます。エラーハンドラが呼ばれても、`SSEClient`は内部で再接続を試みます。ただし、サーバーが204、403、404のいずれかを返したときや、`set_max_reconnect_attempts()`で決めた回数を使い切ったときは再接続しません。
|
||||
|
||||
また、1つのイベントや、まだ改行が来ていない行が`CPPHTTPLIB_SSE_MAX_EVENT_LENGTH`(デフォルトは16MB、0なら無制限)を超えたときも止まります。このときエラーハンドラには`Error::ExceedMaxPayloadSize`が渡されます。イベントストリームには終わりがないので、クライアントの`set_payload_max_length()`はストリーム全体には適用されません。
|
||||
|
||||
## 非同期で動かす
|
||||
|
||||
@@ -88,6 +90,8 @@ sse.set_max_reconnect_attempts(10); // 10回まで(0=無制限)
|
||||
|
||||
`SSEClient`は受信したイベントの`id`を内部で保持していて、再接続時に`Last-Event-ID`ヘッダーとして送ってくれます。この挙動はサーバー側で`id:`付きイベントを送っていれば自動で有効になります。
|
||||
|
||||
空の`id:`を受け取ると、保持しているIDはクリアされます。ヘッダーの値として送れない`id:`(NULなどの制御文字を含むものや、前後に空白があるもの)は無視され、それまでのIDがそのまま残ります。
|
||||
|
||||
```cpp
|
||||
std::cout << "last id: " << sse.last_event_id() << std::endl;
|
||||
```
|
||||
|
||||
@@ -44,6 +44,8 @@ svr.Get(R"(/users/(\d+))", [](const httplib::Request &req, httplib::Response &re
|
||||
|
||||
パターンに括弧を使うと、マッチした部分が`req.matches`に入ります。`req.matches[0]`はパス全体、`req.matches[1]`以降がキャプチャです。
|
||||
|
||||
正規表現のルートは、`CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH`(デフォルトは256)より長いパスには適用されず、マッチしなかったものとして扱われます。長い入力で`std::regex`がスタックを使い果たさないための制限です。もっと長いパスを扱いたい場合は、このマクロに大きな値を定義してください。
|
||||
|
||||
## どちらを使うか
|
||||
|
||||
- 単純なIDやスラッグなら`:name`でじゅうぶん。読みやすく、型が自明です
|
||||
|
||||
@@ -10,12 +10,19 @@ HTML、CSS、画像などの静的ファイルを配信したいときは、`set
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
svr.set_mount_point("/", "./public");
|
||||
if (!svr.set_mount_point("/", "./public")) {
|
||||
// ./publicが存在しない
|
||||
return 1;
|
||||
}
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
```
|
||||
|
||||
`set_mount_point()`はディレクトリが存在しないと`false`を返すので、戻り値を確認しておきましょう。
|
||||
|
||||
`./public/index.html`には`http://localhost:8080/index.html`で、`./public/css/style.css`には`http://localhost:8080/css/style.css`でアクセスできます。ディレクトリ構造がそのままURLに反映されます。
|
||||
|
||||
ディレクトリへのリクエストには、その中の`index.html`を返します。末尾のスラッシュがないリクエスト(`/docs`)は、`/docs/`へリダイレクト(301)されます。マウントしたディレクトリの外を指すシンボリックリンクには403を返します。
|
||||
|
||||
## 複数のマウントポイント
|
||||
|
||||
マウントポイントは複数登録できます。
|
||||
|
||||
@@ -48,22 +48,30 @@ svr.Post("/upload",
|
||||
const httplib::ContentReader &content_reader) {
|
||||
std::ofstream ofs;
|
||||
|
||||
content_reader(
|
||||
auto ok = content_reader(
|
||||
[&](const httplib::FormData &file) {
|
||||
if (!file.filename.empty()) {
|
||||
ofs.open("uploads/" + file.filename, std::ios::binary);
|
||||
}
|
||||
return static_cast<bool>(ofs);
|
||||
if (ofs.is_open()) { ofs.close(); } // 前のファイルを閉じる
|
||||
if (file.filename.empty()) { return true; } // 普通のフィールドは読み飛ばす
|
||||
|
||||
auto name = httplib::sanitize_filename(file.filename);
|
||||
if (name.empty()) { return false; } // 安全でない名前は拒否
|
||||
|
||||
ofs.open("uploads/" + name, std::ios::binary);
|
||||
return ofs.is_open();
|
||||
},
|
||||
[&](const char *data, size_t len) {
|
||||
if (!ofs.is_open()) { return true; } // ファイルのパートではない
|
||||
ofs.write(data, len);
|
||||
return static_cast<bool>(ofs);
|
||||
});
|
||||
|
||||
if (!ok) { return; } // ステータスは設定済み
|
||||
res.set_content("uploaded", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
ヘッダーのコールバックはパートごとに呼ばれるので、次のファイルを開く前に前のファイルを閉じています。`file.filename`はクライアントが送ってきた値そのままなので、必ず`httplib::sanitize_filename()`を通してください。空文字列が返ったら、安全でない名前です。
|
||||
|
||||
メモリには常に小さなチャンクしか載らないので、ギガバイト級のファイルでも扱えます。
|
||||
|
||||
## パート数は自分で数える
|
||||
|
||||
@@ -16,7 +16,7 @@ svr.set_pre_routing_handler(
|
||||
});
|
||||
```
|
||||
|
||||
Pre-routingハンドラは、**ルーティングよりも前**に呼ばれます。どのハンドラにもマッチしないリクエストも含めて、すべてのリクエストを捕まえられます。
|
||||
Pre-routingハンドラは、**ルーティングよりも前**に呼ばれます。どのハンドラにもマッチしないリクエストも含めて、すべてのリクエストを捕まえられます。ただし、リクエストラインやヘッダーの解析中に拒否されたリクエスト(不正なリクエストの400、長すぎるURIの414、不正な`Range`の416)には、このハンドラが呼ばれる前にサーバーが応答します。
|
||||
|
||||
戻り値の`HandlerResponse`がポイントです。
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ svr.Options("/.*", [](const auto &req, auto &res) {
|
||||
});
|
||||
```
|
||||
|
||||
`/.*`は正規表現のルートなので、`CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH`(デフォルトは256)より長いパスには適用されません。パスがそれより長くなりうるなら、pre-routingハンドラで`OPTIONS`を処理してください。
|
||||
|
||||
## セキュリティヘッダーをまとめて付ける
|
||||
|
||||
ブラウザ向けのセキュリティヘッダーを一箇所で管理できます。
|
||||
|
||||
@@ -17,6 +17,8 @@ Pre-requestハンドラなら、`req.matched_route`に「マッチしたパタ
|
||||
|
||||
Pre-requestハンドラが呼ばれる時点ではボディがまだ読まれていないので、認証に失敗したリクエストなどを、(巨大かもしれない)ボディを読み込む前に拒否できます。その代わり、`req.body`やボディから解析されるフォームフィールドはこの時点では参照できません。ヘッダ・パス・クエリパラメータ・`req.matched_route`を使って判断してください。
|
||||
|
||||
Pre-requestハンドラが呼ばれるのは、ルートにマッチしたリクエストだけです。`set_mount_point()`で配信するファイルはルートのマッチングより前に返されるので、静的ファイルを守りたいときはPre-routingハンドラでチェックしてください。
|
||||
|
||||
## ルートごとに認証を切り替える
|
||||
|
||||
```cpp
|
||||
@@ -39,6 +41,8 @@ svr.set_pre_request_handler(
|
||||
|
||||
`svr.WebSocket()`で登録したルートでも、Pre-requestハンドラは呼ばれます。呼ばれるのは`101 Switching Protocols`を返す前なので、`Handled`を返すとそのHTTPレスポンス(403など)がそのまま返り、WebSocketへのアップグレードは行われません。
|
||||
|
||||
サーバーはどの`Origin`からのハンドシェイクも受け付けます。ブラウザは別のサイトが始めたハンドシェイクにもCookieを付けて送るので、Cookieで接続を認証している場合は、`req.get_header_value("Origin")`が自分のオリジンかどうかもここで確認してください。
|
||||
|
||||
## 戻り値の意味
|
||||
|
||||
Pre-routingハンドラと同じく、`HandlerResponse`を返します。
|
||||
|
||||
@@ -4,7 +4,7 @@ order: 32
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
404や500のような**ハンドラが返したエラーレスポンス**を加工したいときは、`set_error_handler()`を使います。デフォルトの味気ないエラーページを、独自のHTMLやJSONに差し替えられます。
|
||||
404や500のような**ハンドラが返したエラーレスポンス**を加工したいときは、`set_error_handler()`を使います。エラーレスポンスのボディはデフォルトでは空ですが、エラーハンドラで独自のHTMLやJSONを入れられます。
|
||||
|
||||
## 基本の使い方
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ order: 34
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
サーバーが受け取ったリクエストと返したレスポンスをログに残したいときは、`Server::set_logger()`を使います。各リクエストの処理が完了するたびに呼ばれるので、アクセスログやメトリクス収集の土台になります。
|
||||
サーバーが受け取ったリクエストと返したレスポンスをログに残したいときは、`Server::set_logger()`を使います。リクエストごとに、レスポンスを書き出す直前に呼ばれるので、アクセスログやメトリクス収集の土台になります。
|
||||
|
||||
## 基本の使い方
|
||||
|
||||
@@ -18,6 +18,8 @@ svr.set_logger([](const httplib::Request &req, const httplib::Response &res) {
|
||||
|
||||
ログコールバックには`Request`と`Response`が渡ります。メソッド、パス、ステータスコード、クライアントIP、ヘッダー、ボディなど、好きな情報を取り出せます。
|
||||
|
||||
`req.path`はパーセントデコード済みなので、CR/LFのような制御文字を含むことがあります。ログファイルに書き出す前にエスケープしてください。そうしないと、細工したリクエストでログの行を偽造されてしまいます。
|
||||
|
||||
## アクセスログ風のフォーマット
|
||||
|
||||
Apache / Nginxのアクセスログに似た形式で残す例です。
|
||||
@@ -38,6 +40,8 @@ svr.set_logger([](const auto &req, const auto &res) {
|
||||
});
|
||||
```
|
||||
|
||||
マウントポイントから配信するファイルのように、コンテンツプロバイダで送るレスポンスでは、この時点の`res.body`は空なので、サイズは`0B`になります。
|
||||
|
||||
## 処理時間を測る
|
||||
|
||||
ログで処理時間を出したいときは、pre-routingハンドラで開始時刻を`res.user_data`に保存しておき、ロガーで差分を取ります。
|
||||
@@ -59,6 +63,8 @@ svr.set_logger([](const httplib::Request &req, const httplib::Response &res) {
|
||||
});
|
||||
```
|
||||
|
||||
ロガーはレスポンスを書き出す前に呼ばれるので、この時間にボディの送信時間は含まれません。
|
||||
|
||||
`user_data`の使い方は[S12. `res.user_data`でハンドラ間データを渡す](../s12-user-data)も参照してください。
|
||||
|
||||
> **Note:** ロガーはリクエスト処理と同じスレッドで同期的に呼ばれます。重い処理を直接入れると全体のスループットが落ちるので、必要ならキューに流して非同期で処理しましょう。
|
||||
> **Note:** ロガーはリクエスト処理と同じスレッドで同期的に呼ばれ、サーバー内のすべてのロガー呼び出しが1つのロックを共有します。重い処理を直接入れると全体のスループットが落ちるので、必要ならキューに流して非同期で処理しましょう。
|
||||
@@ -55,3 +55,5 @@ int main() {
|
||||
`stop()`を呼ぶと、新しい接続は受け付けなくなりますが、すでに動いているハンドラは**最後まで実行**されます。ただし、ストリーミングのようにコンテンツプロバイダで送っている途中のレスポンスは、その場で打ち切られます。その後、スレッドプールのワーカーが順次終了し、`listen()`から戻ってきます。これがグレースフルシャットダウンと呼ばれる理由です。
|
||||
|
||||
> **Warning:** `stop()`を呼んでから`listen()`が戻るまでには、処理中のリクエストが終わるのを待つ時間がかかります。タイムアウトを強制したい場合は、シャットダウン用のタイマーを別途用意するなど、アプリケーション側の工夫が必要です。
|
||||
|
||||
> **Note:** `stop()`はWebSocketハンドラを中断しません。`read()`をループで回しているハンドラは相手がつながっている限り動き続け、それが終わるまで`listen()`も戻りません。開いているWebSocket接続を記録しておき、シャットダウン時に`close()`(`CloseStatus::GoingAway`など)で閉じてください。
|
||||
@@ -52,6 +52,6 @@ svr.set_keep_alive_max_count(1000);
|
||||
|
||||
## スレッドプールとの関係
|
||||
|
||||
Keep-Aliveでつながりっぱなしの接続は、その間ずっとワーカースレッドを1つ占有します。接続数 × 同時リクエスト数がスレッドプールのサイズを超えると、新しいリクエストが待たされます。スレッド数の調整は[S21. マルチスレッド数を設定する](../s21-thread-pool)を参照してください。
|
||||
Keep-Aliveでつながりっぱなしの接続は、その間ずっとワーカースレッドを1つ占有します。スレッドプールは負荷に応じて最大スレッド数(デフォルトはベースの4倍)までスレッドを増やしますが、開いている接続数がそれを超えると、新しい接続はどれかが閉じるまでキューで待たされます。スレッド数の調整は[S21. マルチスレッド数を設定する](../s21-thread-pool)を参照してください。
|
||||
|
||||
> **Note:** クライアント側の挙動は[C14. 接続の再利用とKeep-Aliveの挙動を理解する](../c14-keep-alive)を参照してください。サーバーがタイムアウトで接続を切っても、クライアントは自動で再接続します。
|
||||
@@ -66,4 +66,4 @@ svr.new_task_queue = [] { return new MyTaskQueue(12); };
|
||||
#include <httplib.h>
|
||||
```
|
||||
|
||||
> **Note:** WebSocket接続はその生存期間中ずっと1スレッドを占有します。大量の同時WebSocket接続を扱うなら、動的スケーリングを有効にしておきましょう(`ThreadPool(8, 64)`のように)。
|
||||
> **Note:** WebSocket接続はその生存期間中ずっと1スレッドを占有します。大量の同時WebSocket接続を扱うなら、最大スレッド数を引き上げておきましょう(`ThreadPool(8, 64)`のように)。
|
||||
@@ -46,4 +46,4 @@ wolfSSLには商用ライセンスとサポートがあります。製品に組
|
||||
- [T03. SSL/TLSサーバーを立ち上げる](../t03-ssl-server)
|
||||
- [T05. サーバー側でピア証明書を参照する](../t05-peer-cert)
|
||||
|
||||
> **Note:** macOSでは、OpenSSL系のバックエンドを使う場合、システムのキーチェーンからルート証明書を自動で読む設定(`CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`)がデフォルトで有効です。無効にしたい場合は`CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES`を定義してください。
|
||||
> **Note:** macOSでは、3つのどのバックエンドを使う場合でも、システムのキーチェーンからルート証明書を自動で読む設定(`CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`)がデフォルトで有効です。無効にしたい場合は`CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES`を定義してください。
|
||||
@@ -19,6 +19,8 @@ auto res = cli.Get("/");
|
||||
|
||||
第1引数がCA証明書ファイル、第2引数がCA証明書ディレクトリ(省略可)です。OpenSSLバックエンドなら、`set_ca_cert_store()`で`X509_STORE*`を直接渡すこともできます。
|
||||
|
||||
独自のCAを指定すると、クライアントはそのCAだけを信頼し、OSの証明書ストアは読みません。両方を信頼したい場合は、`cli.enable_system_ca(true)`も呼んでください。CA証明書はクライアントの最初の接続で一度だけ読み込まれるので、これらの設定は最初のリクエストより前に済ませておきましょう。
|
||||
|
||||
## 証明書検証を無効にする(非推奨)
|
||||
|
||||
開発用のサーバーや自己署名証明書にアクセスしたいときは、検証を無効にできます。
|
||||
@@ -46,7 +48,7 @@ cli.enable_server_hostname_verification(false);
|
||||
|
||||
## OSの証明書ストアをそのまま使う
|
||||
|
||||
多くのLinuxディストリビューションでは、`/etc/ssl/certs/ca-certificates.crt`などにルート証明書がまとまっています。cpp-httplibは起動時にOSのデフォルトストアを自動で読みにいくので、普通のサーバーならとくに設定不要です。
|
||||
多くのLinuxディストリビューションでは、`/etc/ssl/certs/ca-certificates.crt`などにルート証明書がまとまっています。cpp-httplibはクライアントの最初の接続でOSのデフォルトストアを自動で読みにいくので、普通のサーバーならとくに設定不要です。
|
||||
|
||||
> mbedTLSやwolfSSLバックエンドでも同じAPIが使えます。バックエンドの選び方は[T01. OpenSSL・mbedTLS・wolfSSLの選択指針](../t01-tls-backends)を参照してください。
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ for (const auto &san : sans) {
|
||||
}
|
||||
```
|
||||
|
||||
ホスト名がSANに含まれるかを確認するヘルパーもあります。
|
||||
証明書がホスト名をカバーしているかを確認するヘルパーもあります。TLSのホスト名検証と同じ規則で、IPアドレスはIPアドレスのSANと、ホスト名はDNS名のSANと照合し、CNはDNS名のSANがないときだけ使います。
|
||||
|
||||
```cpp
|
||||
if (cert.check_hostname("alice.corp.example.com")) {
|
||||
@@ -81,7 +81,7 @@ Pre-requestハンドラと組み合わせれば、共通の認可ロジックを
|
||||
|
||||
## SNI(Server Name Indication)
|
||||
|
||||
クライアントが指定してきたサーバー名は、cpp-httplibが自動で処理します。同じサーバーで複数のドメインをホストする場合にSNIが使われますが、通常はハンドラ側で意識する必要はありません。
|
||||
SNIはクライアントが自動で送ります。サーバー側では、`req.sni()`でクライアントが指定したサーバー名を取得できます。ただし`SSLServer`はその名前に関係なく同じ証明書を提示するので、1つのサーバーで複数のドメインをホストするなら、すべてのドメインをカバーする証明書を使ってください。名前ごとに証明書を切り替えたい場合は、TLSバックエンドの機能を直接使います。たとえばOpenSSLなら、コンテキストのセットアップ用コールバックを受け取る`SSLServer`のコンストラクタで、servernameコールバックを設定します。
|
||||
|
||||
> **Warning:** `req.peer_cert()`は、mTLSが有効で、かつクライアントが証明書を提示した場合のみ有効な値を返します。通常のTLS接続では空の`PeerCert`が返ります。使う前に必ず`bool`チェックしてください。
|
||||
|
||||
|
||||
@@ -21,7 +21,12 @@ int main() {
|
||||
if (result == httplib::ws::ReadResult::Fail) {
|
||||
break;
|
||||
}
|
||||
ws.send(msg); // 受け取った内容をそのまま返す
|
||||
// 受け取った内容を同じフレームタイプでそのまま返す
|
||||
if (result == httplib::ws::ReadResult::Binary) {
|
||||
ws.send(msg.data(), msg.size());
|
||||
} else {
|
||||
ws.send(msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -29,7 +34,7 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
`svr.WebSocket()`でWebSocket用のハンドラを登録します。ハンドラが呼ばれた時点で、すでにWebSocketのハンドシェイクは完了しています。ループの中で`ws.read()`して`ws.send()`するだけで、エコー動作が完成します。
|
||||
`svr.WebSocket()`でWebSocket用のハンドラを登録します。ハンドラが呼ばれた時点で、すでにWebSocketのハンドシェイクは完了しています。ループの中で`ws.read()`して`ws.send()`するだけで、エコー動作が完成します。バイナリメッセージは`data()`と`size()`を渡すオーバーロードで返すので、バイナリのまま届きます(詳しくは後述)。
|
||||
|
||||
`read()`の返り値は`ReadResult`列挙値で、次の4種類です。
|
||||
|
||||
@@ -76,7 +81,7 @@ ws.send(binary_data, binary_data_size); // バイナリフレーム
|
||||
|
||||
## スレッドとの関係
|
||||
|
||||
WebSocket接続はハンドラが終わるまで生き続けるので、1接続につきワーカースレッドを1つ占有します。同時接続数が多い場合は、スレッドプールを動的スケーリングに設定しましょう。
|
||||
WebSocket接続はハンドラが終わるまで生き続けるので、1接続につきワーカースレッドを1つ占有します。デフォルトのスレッドプールも負荷に応じてベースの4倍まで増えますが、同時接続数が多い場合は、その最大スレッド数を引き上げましょう。
|
||||
|
||||
```cpp
|
||||
svr.new_task_queue = [] {
|
||||
|
||||
@@ -43,7 +43,7 @@ cli.connect();
|
||||
|
||||
## Pingへの応答(Pong)
|
||||
|
||||
WebSocketプロトコルでは、PingフレームにはPongフレームで応答することが決まっています。cpp-httplibは受信したPingに自動でPongを返すので、アプリケーションコード側で気にする必要はありません。
|
||||
WebSocketプロトコルでは、PingフレームにはPongフレームで応答することが決まっています。cpp-httplibは受信したPingに`read()`の中で自動でPongを返すので、`read()`を呼び続けている限り、アプリケーションコード側で気にする必要はありません。
|
||||
|
||||
## Pingの間隔をどう決めるか
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ cli.close(httplib::ws::CloseStatus::Normal);
|
||||
ws.close(httplib::ws::CloseStatus::GoingAway, "server restarting");
|
||||
```
|
||||
|
||||
クライアント側はこのステータスを見て、再接続を試みるかどうかを判断できます。
|
||||
クライアント側の`read()`は`Fail`を返すだけで、受け取ったステータスや理由は取り出せません。再起動による切断をほかの切断と見分けたい場合は、先にメッセージ(`ws.send("restarting")`など)を送ってから`close()`を呼びましょう。
|
||||
|
||||
## サンプル: 簡単なチャット終了
|
||||
|
||||
|
||||
@@ -85,4 +85,4 @@ if (cli.read(buf) == httplib::ws::ReadResult::Binary) {
|
||||
|
||||
テキストとバイナリを混ぜて送ることもできます。たとえば「制御メッセージはJSON、データ本体はバイナリ」といったプロトコルを組み立てると、メタデータと生データを効率よく扱えます。
|
||||
|
||||
> **Note:** WebSocketのフレームサイズには上限がないわけではありません。巨大なデータを送るときは、アプリケーション側で分割して送るのが安全です。cpp-httplibのデフォルトでは大きなフレームもそのまま処理されますが、メモリを一気に使う点は変わりません。
|
||||
> **Note:** 受信できるメッセージの大きさは`CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH`(デフォルトは16MB、0なら無制限)までです。これを超えると`read()`は`Fail`を返し、接続は終了します。上限以内でもメッセージはまるごとメモリに載るので、巨大なデータを送るときは、アプリケーション側で分割して送るのが安全です。
|
||||
@@ -26,6 +26,8 @@ if (ws.connect()) {
|
||||
|
||||
証明書検証そのものを無効にしたい場合は`enable_server_certificate_verification(false)`が使えます。挙動の詳細は[T02. SSL証明書の検証を制御する](../t02-cert-verification)を参照してください。
|
||||
|
||||
`WebSocketClient`には、独自の検証に使う`set_server_certificate_verifier()`と`set_session_verifier()`や、プロキシ用のセッター(`set_proxy()`、`set_proxy_basic_auth()`、`set_proxy_bearer_token_auth()`、`set_proxy_digest_auth()`、`set_no_proxy()`)もあります。CA証明書は最初の`connect()`で読み込まれるので、それより前に設定してください。
|
||||
|
||||
## クライアント証明書を使う(mTLS)
|
||||
|
||||
`ws::WebSocketClient`には`PemMemory`構造体を受け取るコンストラクタがあり、`wss://`接続でクライアント証明書を提示できます。
|
||||
|
||||
Reference in new issue
Block a user