クライアントが送信を終えると、プロキシは EOF を検出し、応答は消えてしまいます。プロキシの両方のソケットを直ちに閉じると、まさにその順序が起こり得ます。TCP リレーでは、一方向の整った終了は、もう一方の方向が終わったことを意味しません。

リクエストを送り、送信側を閉じて応答を待つクライアントを考えます。

client -- request, then FIN --> proxy --> service
client <-------- reply ------- proxy <-- service

プロキシには 2 つの TCP 接続と、独立に動く 2 つのバイトストリームがあります。先行するバイトを転送したうえで、入力の終わりという合図もさらに先へ運ぶ必要があります。

EOF を転送する前にキューを排出する

TCP ストリームでサイズがゼロでない受信において、ゼロの戻り値はその受信ストリームの整った終了を示します。その読み取り方向を終了済みとして記録します。それ以上その方向の入力を待たずに済ませますが、反対側のソケット向けにすでにキューに入っているバイトは保持します。

クライアントからサービスへのキューが排出されたら、POSIX ソケットで shutdown(service_socket, SHUT_WR) を呼び出します。これによりサービス方向への送信を終了し、サービスからクライアント方向は利用可能なまま残ります。サービスが応答を終えたら、応答キューを排出した後に鏡像の操作を適用します。

ディスクリプタを閉じるのは、両方向とその保留中のバッファが終わった時点、またはエラーや取り消しによって終了が必要な時点です。残りの半分を決して完了しない相手に対しては、上限付きのタイムアウトを保ちます。

読み取り可能は読み取りの招待

読み取り可能なソケットには、データがあるか EOF 状態があります。recv を呼び出して結果を解釈します。正の値は転送するバイト数、ゼロは方向の終端です。非ブロッキングソケットでは、EAGAIN や EWOULDBLOCK は現時点でこれ以上の入力がないことを意味し、EINTR は再試行が必要な場合があります。どちらも EOF と同じイベントではありません。

返されたバイトバッファ内で FIN フラグを探してはいけません。TCP はこの API に順序どおりのバイトストリームを渡します。パケット境界や FIN セグメントは通常のアプリケーションのペイロードではありません。

これは生のバイトトンネルのライフサイクルです。メッセージを解析する HTTP プロキシは依然として HTTP のフレーミングを守る必要があり、TLS を終端するプロキシにも TLS のシャットダウンプロトコルがあります。それらのプロトコル上の判断は、方向ごとの TCP リレーの上位に置いてください。

関連記事