1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
//! プラグインのパスインターセプト型・Upgrade 型シームヘルパー
//! (パスインターセプト型: TASK-2.1 / #18、Upgrade 型: TASK-4.1 / #22)。
//!
//! [`crate::server`] の `handle_connection` が呼ぶ固定シグネチャの委譲窓口を
//! 2 種類集約する。`#[cfg(feature = "...")]` を本モジュールの外へ一切漏らさ
//! ないことで、コアループ本体(`handle_connection`)を feature で分岐させ
//! ない設計規約(`crates/core/src/server.rs` 冒頭の doc、PoC-3)を守る:
//! - [`try_intercept`][]: リクエスト/レスポンス完結型プラグイン(WebRTC
//! シグナリングプロキシ等)へのパスインターセプト
//! - [`try_handle_upgrade`][]: 長時間接続(WebSocket 等)への完全委譲
//! (`UpgradeHandler::matches` 成立後、コア側の読み取りバッファ解放
//! (Conditional Go 条件(1))を経て呼ばれる)
//!
//! feature が 1 つも有効でない場合、両関数とも即座に `None` を返すだけの
//! 薄い関数となり、実行時コスト・依存・コード・`unsafe` を一切追加しない
//! (pay-for-what-you-use、.claude/rules/pay-for-what-you-use.md)。新しい
//! プラグインを配線する際は、本モジュールへ cfg-gated な分岐を追加する形で
//! 拡張する(`docs/design/plugin-boundary.md` のプラグイン境界パターン・
//! 適用指針を参照)。
//!
//! イシュー #305(CORS プラグイン)で 3 つ目のシーム [`finalize_response`]
//! を追加した。`Middleware::on_response` はレスポンスへの参照を持たない
//! 観測専用契約のため CORS ヘッダ付与に使えず、「レスポンス後処理型」という
//! 新パターンが必要になった(`crates/plugin-cors/src/lib.rs` の crate doc・
//! `docs/design/plugin-boundary.md` の該当節を参照)。イシュー #321
//! (圧縮プラグイン)で本シームの第 2 インスタンスを追加し、複数プラグイン
//! を逐次適用(CORS → 圧縮の順、body を確定させる圧縮を必ず最後に適用)
//! できるよう構成した(`crates/plugin-compression/src/lib.rs` の crate doc
//! を参照)。
use ;
use OwnedSemaphorePermit;
use RequestHead;
use Response;
use crateServer;
/// 登録済みプラグインへパスインターセプトを試みる。
///
/// `RequestGate` → `UpgradeHandler` の評価を通過し、既定 `Handler::handle` を
/// 呼ぶ直前に `handle_connection` が呼ぶ(`crates/core/src/server.rs` 冒頭の
/// 処理フロー doc を参照)。`Some(response)` は「このプラグインが処理を完結
/// させた」ことを意味し、呼び出し元は既定 `Handler` を呼ばずにこの応答を
/// そのまま送出する。`None` は「対象パスではない、またはプラグイン自体が
/// 無効」であり、呼び出し元は既定 `Handler::handle`(未登録時 404)へ
/// フォールスルーする。
///
/// `webrtc-proxy`・`webrtc`・`graphql`・`openapi`・`static` のいずれの feature も
/// 無効時は `server`/`head`/`body` を一切参照せず即座に `None` を返す
/// (`cargo tree` で `fandhe-backend-plugin-webrtc-proxy`・`fandhe-backend-plugin-webrtc`・
/// `fandhe-backend-plugin-graphql`・`fandhe-backend-plugin-openapi`・
/// `fandhe-backend-plugin-static` のいずれも現れないことに加え、本関数自体もコード上
/// ゼロコストであることの根拠)。
/// `graphql` feature は TASK-2.4(#21)で追加した第 2 のプラグイン境界
/// インスタンスであり、TASK-5.1(#38)で実 GraphQL 実行へ差し替えた
/// (`crates/plugin-graphql` の doc を参照)。`webrtc-proxy`・`webrtc` と
/// 同じ設定登録型パターンのため、スキーマ未登録時は feature が有効でも
/// フォールスルーする。
///
/// `webrtc-proxy`・`webrtc` が同時に有効な場合(`--all-features` CI 構成)は
/// `webrtc-proxy`(別プロセス切り出し型、REQ-8 の MVP 推奨方式)を先に評価する。
/// 両方を `Server` に登録していても `webrtc-proxy` 側が `Some` を返した時点で
/// `webrtc` 側(in-process 型、TASK-8.1 / #26)は評価しない(実運用では通常
/// どちらか片方のみ登録するため、この優先順位が問題になるのは意図的に両方
/// 登録した場合に限る。`crates/core/src/server.rs` の `Server::webrtc_proxy` /
/// `Server::webrtc` の doc を参照)。
pub async
/// `fandhe_backend_plugin_webrtc_proxy::Response`(プラグイン側の中間表現)を
/// [`fandhe_backend_http::response::Response`] へ変換する。
///
/// `content_type` は [`Response::with_content_type`] が `&'static str` のみを
/// 受け付ける制約に従う。プラグイン側の `content_type` フィールドも
/// `&'static str` に限定されているため、変換経路に外部入力由来の動的文字列が
/// 混入する余地はない(`crates/http/src/response.rs` の doc を参照)。
/// [`UpgradeHandler::matches`][crate::extension::UpgradeHandler::matches] が
/// `true` を返した接続をプラグイン側へ委譲するための、一定シグネチャの
/// Upgrade 型委譲シーム(TASK-4.1 / #22、専用タスク再 spawn は
/// TASK-4.2 / #23【条件(1)】)。
///
/// `crates/core/src/server.rs` の `handle_connection` から、読み取りバッファ
/// 解放(Conditional Go 条件(1))・残余バイト列(`leftover`)退避の直後に
/// 呼ばれる。戻り値 `Some(stream)` は「委譲されず、呼び出し元が後続処理
/// (フォールバック応答)を続けるべき」ことを意味し、`websocket` feature
/// 無効時・`server.websocket_configs()` が空、または登録済みいずれの設定にも
/// `fandhe_backend_plugin_websocket::matches` が一致しない時のいずれかで発生する
/// (呼び出し元は 501 を返す、`server.rs` の
/// doc を参照)。`None` は「完全に委譲済みで呼び出し元はこれ以上ストリームに
/// 触れない」ことを意味する。
///
/// # 委譲後の専用タスク再 spawn(TASK-4.2 / #23)
///
/// マッチ確定時、ハンドシェイク + メッセージハンドラループ(既定エコー、
/// `WebSocketConfig::with_handler` でユーザー定義ハンドラへ差し替え可能。
/// Issue #179)を提供する `fandhe_backend_plugin_websocket::handle_upgrade`
/// を呼び出し元の `handle_connection` タスク内でインラインに await せず、
/// `tokio::spawn` した専用タスクへ完全に切り離す。`handle_connection` の
/// tokio タスクは `read_request`・応答直列化等を含む大きなステートマシンで
/// あり、そのままインライン await すると WS 接続の生存中ずっとこの大きな
/// ステートマシンがメモリ上に残ってしまう(PoC-7 実測、接続あたり RSS が
/// axum 比 155.2% となり Conditional Go 条件(1) の成功基準 110% を満たさな
/// かった残差要因)。マッチ確定と同時に `handle_connection` 側は即座に
/// `return`(呼び出し元で `None` を受けて終了)し、大きな future を解放
/// する。新タスクは WS セッションのみを載せた小さな future になる
/// (`docs/design/plugin-boundary.md` 5 節「委譲後の専用タスク再 spawn」
/// パターンを参照)。
///
/// # `permit` の契約(DoS 対策の維持、TASK-4.2 / #23)
///
/// 同時接続数上限は [`BoundServer::run`][crate::server::BoundServer::run] が
/// 保持する `OwnedSemaphorePermit` で強制される。素朴に再 spawn すると、
/// 元の `handle_connection` タスクは即座に終了して permit を解放してしまい、
/// 長時間生存する WS セッションが `max_connections` の上限強制から漏れる
/// (リソース枯渇 DoS のリグレッション、`.claude/rules/security.md`)。
/// これを避けるため、呼び出し元は `permit`(`&mut Option<OwnedSemaphorePermit>`)
/// を渡し、本関数はマッチ確定時に [`Option::take`] で permit の所有権を奪って
/// 新タスクへ move する。呼び出し元に残るのは `None` であり、`handle_connection`
/// 側の通常の permit drop 経路(呼び出し元のラッパー、`server.rs` を参照)は
/// 何も解放しない。permit は新タスク内で WS セッション終了まで保持され、
/// セッション終了と同時に(タスクの戻りで)解放される。
///
/// `websocket` feature 無効時は `stream`/`head`/`leftover`/`server`/`permit`
/// を一切参照せず即座に `Some(stream)` を返し、`fandhe_backend_plugin_websocket` への
/// 依存・呼び出しコード・`tokio::spawn` 呼び出しともバイナリに含まれない
/// (`cargo tree` で確認可能、pay-for-what-you-use)。
///
/// [`fandhe_backend_plugin_websocket::handle_upgrade`] 内のエラー(ハンドシェイク検証
/// 違反・I/O・プロトコルエラー)は新タスクの境界で吸収し、panic としてコア
/// 境界を越えさせない(`.claude/rules/coding-rust.md`)。エラー発生時は
/// 接続を静かにクローズしたものとして扱う(`handle_upgrade` 自体が 400/426
/// 応答の送出、または I/O エラー時の未送出クローズを内部で行っているため、
/// 呼び出し元がさらにフォールバック応答を重ねて送る必要はない)。新タスク内
/// で panic しても `tokio::spawn` のタスク境界で隔離され、コアの accept
/// ループへは波及しない。
pub async
/// `fandhe_backend_plugin_graphql::Response`(プラグイン側の中間表現)を
/// [`fandhe_backend_http::response::Response`] へ変換する。[`from_plugin_response`] と
/// 同一の変換原則(`content_type` は `&'static str` 限定)に従う。
/// レスポンス後処理型シーム(イシュー #305、CORS プラグインで新設。
/// イシュー #321 で圧縮プラグインの第 2 インスタンスを追加)。
///
/// `handle_connection_with_permit`(`crates/core/src/server.rs`)が
/// `try_intercept` 応答・既定 `Handler` 応答のいずれかを確定させた直後、
/// keep-alive 再判定・`serialize` の前に呼ぶ(モジュール冒頭の処理フロー
/// doc・本モジュール冒頭 doc の「3 つ目のシーム」を参照)。`try_intercept`
/// 応答(graphql・openapi 等のパスインターセプト型プラグイン応答)にも
/// 既定 `Handler` 応答にも同一の後処理を適用できる、`Handler` ラッパー方式
/// にはない利点を持つ(`docs/design/plugin-boundary.md` の設計比較を参照)。
///
/// 登録済みプラグインへ**逐次適用**する(CORS → 圧縮の順固定。圧縮は
/// body を確定させる後処理のため必ず最後、`crates/plugin-compression/
/// src/lib.rs` の crate doc を参照)。`cors` / `compression` いずれの
/// feature も無効、または対応する `Server::cors` / `Server::compression`
/// が未登録の場合はそれぞれの適用をスキップし、両方スキップ時は
/// `response` を無改変で返す(他のプラグインと同じ「設定登録型」の
/// フォールスルー、pay-for-what-you-use)。
///
/// # プリフライトとの二重付与防止
///
/// CORS プリフライト(`OPTIONS` + `Origin` + `Access-Control-Request-Method`)
/// は `fandhe_backend_routes::Router::options_fallback`
/// (利用者が `fandhe_backend_plugin_cors::preflight_response` を直接配線、
/// イシュー #304)で完結済みのため、本シームは
/// `fandhe_backend_plugin_cors::is_preflight` が `true` を返すリクエストには
/// 何もしない(プリフライト応答へ実リクエスト用のヘッダを重ねて付与しない
/// ようにするための判定)。
///
/// # `RequestGate` 拒否応答・パースエラー応答を通さない設計判断
///
/// 本関数は `handle_connection_with_permit` の中で `RequestGate` 拒否応答・
/// パースエラー応答(400 等)の送出経路とは別の、`try_intercept` /
/// `Handler::handle` の結果にのみ適用される(呼び出し箇所を参照)。拒否
/// 応答は最小情報で返すフェイルクローズ方針を維持するための意図的な設計
/// (`docs/design/plugin-boundary.md` の該当節・`.claude/rules/security.md`
/// を参照)。
///
/// 同期・`.await` なし(ヘッダ検査と `with_header` 呼び出しのみ)で
/// `Middleware` の非同期 I/O 禁止規約(`.claude/rules/coding-rust.md`)とは
/// 独立にコストを抑える。
pub