sgcWebSockets 2026.8: QUIC、HTTP/3、静的 OpenSSL、そして TLS の全面刷新 | eSeGeCe ブログ

sgcWebSockets 2026.8: QUIC、HTTP/3、静的 OpenSSL、そして TLS の全面刷新

· リリース

sgcWebSockets 2026.8 は今年最大のリリースです。新しいトランスポートコンポーネントが 4 つ追加され、QUIC と HTTP/3 がクライアント側とサーバー側の両方で利用できるようになりました。OpenSSL を実行ファイル内にリンクできるようになったため、TLS アプリケーションを DLL なしで配布できます。さらに sgcHTML は 30 を超えるコンポーネントが加わり、WebBroker と DataSnap のディスパッチャを備え、スマートフォンの画面にも適応するようになりました。

内部では、SChannel TLS レイヤーを分解して作り直しました。Windows の TLS スタックを使っているなら、このリリースはインストールすべきものです。TLSOptions.Version は黙って無視され、失効した証明書が受け入れられ、TLS 1.3 接続が完了せず、再ネゴシエーションでは証明書がまったく検証されないまま差し替えられていました。これらはすべて修正され、失効チェック、バージョンの下限、サーバー側でのクライアント証明書検証が利用できるようになりました。この記事の残りはガイドツアーです。各セクションから、その機能を詳しく解説した記事へリンクしています。

QUIC と HTTP/3

4 つの新しいコンポーネントが、QUIC (RFC 9000) と HTTP/3 (RFC 9114) を Delphi と C++ Builder にもたらします。TsgcQUICClientTsgcQUICServerTsgcHTTP3ClientTsgcHTTP3Server です。これらはネイティブの OpenSSL 3.5 QUIC エンジン上で動作するため、サードパーティのスタックを配布する必要はありません。ハンドシェイクに統合された TLS 1.3、ヘッドオブラインブロッキングのない多重化ストリーム、0-RTT による再開、クライアントのネットワークが変わったときのコネクションマイグレーションが手に入ります。

uses
  sgcQUIC_Client;

var
  oClient: TsgcQUICClient;
begin
  oClient := TsgcQUICClient.Create(nil);
  oClient.Host := 'www.example.com';
  oClient.Port := 443;
  oClient.OnQUICConnect    := OnQUICConnect;
  oClient.OnQUICStreamData := OnQUICStreamData;

  oClient.Active := True;          // TLS 1.3 handshake in a single flight
  oClient.WriteData('ping');       // send bytes on a QUIC stream
end;

HTTP/3 クライアントはあらゆる HTTP メソッドに対応し、QPACK によるヘッダー圧縮を行い、ブロッキングでも非同期でも動作し、Alt-Svc を通じて HTTP/3 エンドポイントを検出し、サーバープッシュも扱えます。

uses
  sgcHTTP3_Client;

var
  oClient: TsgcHTTP3Client;
  vBody: string;
begin
  oClient := TsgcHTTP3Client.Create(nil);
  oClient.OnResponse := OnResponse;
  oClient.OnAltSvc   := OnAltSvc;

  oClient.Connect('www.example.com', 443);
  vBody := oClient.Get('https://www.example.com/');
end;

どちらも sgcWebSockets Enterprise の sgcQUIC パッケージが必要です。詳しくは QUIC クライアントおよびサーバーコンポーネントHTTP/3 クライアントおよびサーバーコンポーネント をご覧ください。

実行ファイル内の OpenSSL

アプリケーションの横に libcryptolibssl を配置することは、TLS クライアントを配布するうえで常に最も気の重い作業でした。2026.8 からは OpenSSL 3.5.7 を静的にリンクできます。プロジェクトの uses 節にユニットを 1 つ追加するだけで、DLL は不要になります。そのユニットを外せば、これまでとまったく同じように DLL が読み込まれます。つまり、どちらの方向にも 1 行の変更で切り替えられます。

uses
  sgcWebSocket, sgcWebSocket_Classes,
  sgcIdSSLOpenSSL_Static;

クライアントとサーバー、32 ビットと 64 ビット、Delphi XE2 以降に対応しています。詳しくは 静的 OpenSSL リンク、DLL はもう不要 をご覧ください。

sgcHTML: WebBroker、DataSnap、そして 30 の新コンポーネント

sgcHTML のページは、もはや sgcWebSockets サーバーに縛られません。新しいディスパッチャコンポーネントが、スタンドアロン、ISAPI、Apache、CGI のいずれの WebBroker アプリケーションからでもページを配信します。DataSnap サーバーからも配信でき、そこではブリッジが REST エンドポイントと同じポートにリアルタイムの WebSocket 更新を追加します。

uses
  Web.HTTPApp, sgcHTMX_Engine_Server_WebBroker, sgcHTMX_Router;

// Any WebBroker host: the engine's Owner is the web module,
// so the standard WebBroker dispatcher calls it automatically
FEngine := TsgcHTMX_Engine_Server_WebBroker.Create(WebModule1);
FEngine.Router := FRouter;   // your htmx routes and the page

このリリースでコンポーネントセットは大きく成長しましたが、すべて従来どおりサーバー側で描画され、読み込むクライアント側ライブラリはありません。

ページは画面サイズにも適応するようになりました。スマートフォンではサイドメニューがボタンの後ろに折りたたまれ、コンテンツが全幅を使います。デスクトップでは何も変わりません。従来の固定レイアウトに戻すには、新しい Responsive プロパティを False に設定してください。詳しくは WebBroker と DataSnap 上の sgcHTML をご覧ください。

SChannel TLS の全面刷新

SChannel は Windows の TLS スタックで、OpenSSL をまったく配布せずに使えるものです。ここには外からは見えない問題がいくつもありました。これは最悪の種類の問題です。TLSOptions.Version は Windows 11 と Windows Server 2022 ではまったく読まれず、Windows 10 で TLS 1.3 を要求しても黙って TLS 1.2 になり、バージョンを未指定のままにすると SSL 3.0、TLS 1.0、TLS 1.1 が再び有効になっていました。VerifyCertificate を True にしていても失効したサーバー証明書が受け入れられていました。失効状態をまったく問い合わせずにチェーンを構築していたためです。再ネゴシエーション後は、チェーンもホスト名も検証せずに新しい証明書が受け入れられていました。Indy が裏で開く接続、たとえば別のホストへの HTTP リダイレクトや FTP のデータチャネルは、設定が空のまま返ってきていたため、検証がオフの状態で動作し、それを報告するものもありませんでした。

これらはすべて修正され、ハンドシェイク完了時にネゴシエートされたバージョンが検証されるようになりました。またこの作業に伴い、3 つの新しいオプショングループが加わりました。失効チェック、バージョンの下限、そして Windows の強力な暗号スイートです。

// client
oClient.TLSOptions.Version     := tls1_3;   // highest version to use
oClient.TLSOptions.SChannel_Options.VersionMin := tls1_2;   // lowest accepted
oClient.TLSOptions.SChannel_Options.UseStrongCrypto := True;
oClient.TLSOptions.SChannel_Options.Revocation.Check   := scrcChainExcludeRoot;
oClient.TLSOptions.SChannel_Options.Revocation.Timeout := 5000;  // ms

// or all of it in one line
oClient.TLSOptions.Preset := tlspSecureDefaults;

失効に関する既定値は意図的に寛容にしてあります。IgnoreRevocationOfflineIgnoreNoRevocationCheck は True なので、チェックを有効にしても、それまで動いていた接続が壊れることはありません。また Timeout が CRL と OCSP の取得を制限するため、到達できないレスポンダーがハンドシェイクを止めてしまうこともありません。実際に失効している証明書は常に拒否されます。

サーバー側では、SChannel がクライアントに証明書を要求できるようになりました。これは以前はできなかったことです。クライアント証明書は、クライアントがサーバー証明書に対して行うのと同じチェック、つまりチェーン、有効期限、OnSChannelVerifyPeer イベントを通過します。ホスト名チェックは行いません。

oServer.SSLOptions.VerifyCertificate := True;
oServer.SSLOptions.VerifyCertificate_Options.FailIfNoCertificate := True;

ここにはさらに 2 つの修正が含まれます。SChannel を使うクライアントは、プロキシが再起動したときのように相手側がクローズ通知なしに接続を切った場合、それに気づきませんでした。そのため OnDisconnect は発火せず、WatchDog も動きませんでした。また TLS 1.3 接続はまったく完了しませんでした。ハンドシェイク直後にサーバーが送るセッションチケットにより、サーバーがすでに送信済みのデータをクライアントが待ち続けてしまっていたためです。ハンドシェイクは TCP 接続だけをカバーするのではなく、ConnectTimeout を尊重するようになりました。そしてクローズ前にクローズ通知を送るようになりました。これは SChannel、Apple、Android の接続すべてに共通です。

サーバーの制限と IOCP / EPOLL エンジン

これまで無制限だった項目に、サーバーが一連の制限を持つようになりました。WebSocket サーバーは既定で無制限ではなく同時 10,000 接続を受け入れ、クライアントが 1 秒間に送信できる制御フレーム数 (100) にも上限を設けます。これにより ping で溢れさせられることがなくなります。HTTP/2 サーバーには MaxRequestSize が、DTLS コンポーネントには MaxConnectionsHandshakeTimeoutIdleTimeout が追加され、偽装アドレスからの単発パケットの洪水がサーバーのメモリを埋め尽くすことはなくなりました。いずれも値を引き上げたり、0 に設定して従来の動作に戻したりできます。

高性能エンジンには長い修正リストがあります。いくつかのクリーンアップ経路でのメモリリーク、ハンドルリーク、クラッシュ、accept 直後に切断するクライアント、サーバー停止のクリーンアップ途中で二重に解放される接続、ワーカースレッド有効時の use after free、処理するメッセージごとに失われるバッファなどです。HTTP キープアライブはまったく機能しておらず、リクエストごとに接続が閉じられ、サーバーは TIME_WAIT のソケットで埋まっていました。Linux では、HTTP リクエストごとに解析済みリクエストの一部がリークし、さらにすべてのワーカーが 1 つの接続キューを共有していたため、1 つか 2 つのスレッドがほとんどすべての処理を担っていました。

これを補う 2 つの新しいオプションが追加されました。どちらも既定ではオフです。キープアライブプローブとアイドルタイムアウトにより、ハーフオープンのソケットが積み上がらなくなります。もう 1 つは、新規接続をワーカースレッドにラウンドロビンで分配する機能です。

Server.IOHandlerOptions.IOHandlerType := iohEPOLL;
Server.IOHandlerOptions.EPOLL.TCPKeepAlive.Enabled  := True;
Server.IOHandlerOptions.EPOLL.TCPKeepAlive.Time     := 60;  // seconds idle before probing
Server.IOHandlerOptions.EPOLL.TCPKeepAlive.Interval := 10;  // seconds between probes
Server.IOHandlerOptions.KeepAliveTimeout            := 120; // seconds

詳しくは IOCP および EPOLL サーバーの TCPKeepAlive と KeepAliveTimeout をご覧ください。エンジンの修正のいくつかの土台となったパッチを提供してくださった Andrea さんに感謝します。

プロトコル: STOMP、MQTT、AMQP、HTTP/2

最も多く手を入れたのは STOMP です。正常な Disconnect は、ブローカーがレシートで確認するまで待つようになったため、クローズ時に何も失われません。メッセージはブローカーが送信したとおりに配信されます。ボディの読み取りに content-length を使うため、改行やバイナリのゼロを含めることができます。1 つの WebSocket メッセージにまとめられた複数のフレームはすべて処理され、2 つのメッセージにまたがって分割されたフレームは再構成され、バイナリフレームが無視されることもなくなりました。ACK と NACK はネゴシエートされたバージョンが要求するヘッダーを送信し、ハートビートはサーバーが接続を確認してから開始されます。

oSTOMP.DisconnectTimeout := 10000;   // ms to wait for the receipt, 0 = return at once
oSTOMP.ACKEx(vMessageId, vSubscriptionId);
ShowMessage('STOMP ' + oSTOMP.NegotiatedVersion);

MQTT はブローカーが拒否するパケット、たとえばユーザー名だけでパスワードのない CONNECT や、より大きなプロパティブロックを運ぶ MQTT 5 パケットを組み立てなくなりました。また MQTT 5 クライアントはパケットの終端を越えて読み取ることをやめました。以前は、切り詰められたパケットがあると、メモリ上でその後ろにあった内容をプロパティ値としてアプリケーションに渡してしまっていました。現在はブローカーの指示、たとえば CONNACK で返される Keep Alive から、Topic Alias だけを伴って到着するメッセージまで、正しく尊重します。QoS 2 のパブリッシュ経路は正しい後続の確認を送信し、ブローカーが拒否したメッセージは永久に再試行せず破棄し、適切なスケジュールで再試行します。

AMQP 1.0 クライアントは、不正なブローカーに対して保護されるようになりました。小さく分割されて到着するフレーム、不正なヘッダーサイズ、深くネストされすぎたメッセージ、展開すると膨大なメモリを消費するよう細工された小さなメッセージなどです。AMQP 0.9.1 と 1.0 の既定の最大フレームサイズは、事実上無制限だったものが 1 MB になりました。HTTP/2 のヘッダー圧縮は細工されたヘッダーから保護され、接続は開かれていないストリーム宛の DATA フレーム、PRIORITY フレームの洪水、ストリーム番号の再利用、空の CONTINUATION の洪水を拒否します。サーバーは改行や NULL を含むヘッダー名と値を拒否し、リクエストスマグリングを防ぎます。リセットされたストリームによるメモリ増加と、約 100 リクエスト後に発生する「CONCURRENT STREAM limit has been exceeded」は、どちらも修正されました。

MCP サーバー

ツールは入力に対して完全な JSON Schema を公開できるようになりました。items を伴う配列、列挙型、整数型と nullable 型、ネストされたオブジェクト、additionalProperties を宣言できます。これらを要求し、なければ拒否する MCP クライアントもあります。さらにサーバーは 2025-11-25 プロトコルのメタデータも公開します。readOnlyHint アノテーションとアノテーションのタイトル、ツールのアイコン、サーバーのタイトル、ウェブサイトとアイコン、そして tools/list のタスクサポート宣言です。

すでに運用している方には 2 つの修正が重要です。サーバーは、クライアントが GET で開くイベントストリームに、内部の接続 ID をメッセージとして書き込んでいました。VS Code GitHub Copilot などのクライアントはこれを「Failed to parse message」として報告していました。またオリジンチェックは、ブラウザが決して送らないヘッダーがリクエストに含まれる場合にのみ実行されていたため、本来防ぐべきケースでは一度も実行されていませんでした。つまり、ユーザーが訪れたウェブページが自分のマシン上の MCP サーバーに到達し、そのツール一覧を取得し、実行し、結果を読み取ることができたのです。オリジンは、ブラウザの事前チェックを含め、すべてのリクエストで検証されるようになりました。

MCPServer.MCPOptions.HTTPStreamable.AllowedOrigins.Add('https://*.example.com');

暗号資産取引所

この領域の大半は 2 件のお客様からの報告がきっかけです。HTTP エラーステータスで失敗したリクエストは、サーバーが返したヘッダーも報告するようになりました。これにより、429 に対する Retry-After や、取引所が返すレート制限カウンターをようやく読み取れます。既製の API クライアントは EsgcHTTPAPIProtocolException を送出します。これは従来送出していた例外を継承しているため、既存のハンドラーはそのまま動作します。

try
  vResponse := oBinance.GetAggregateTrades('BTCUSDT');
except
  on E: EsgcHTTPAPIProtocolException do
  begin
    if E.ErrorCode = 429 then
      Sleep(E.RetryAfterMs);
    vWeight := E.GetHeader('X-MBX-USED-WEIGHT-1M');
    // E.ResponseHeaders holds the complete list
  end;
end;

これまで、どの取引所クライアントも再接続後に購読リスト全体を一気に送り直していました。しかも接続してわずか数ミリ秒しか経っていない状態でです。そのため、1 秒あたりのメッセージ数を制限する取引所は即座に接続を閉じ、同じことが繰り返されていました。現在は再送がペース配分され、Binance では結合フレームとして送信されます。さらに、送信するメッセージに対するクライアント側のスロットルも新しく追加されました。

oBinance.Throttle.Enabled     := True;
oBinance.Throttle.MaxMessages := 4;      // per window
oBinance.Throttle.IntervalMs  := 1000;
// paced reconnect replay is on by default:
// oBinance.Throttle.PaceResubscribe := False;  restores the old burst

// subscribe a whole watchlist with ONE frame
oBinance.SubscribeStreams(['btcusdt@trade', 'ethusdt@trade', 'bnbusdt@trade']);

Binance は Spot のユーザーデータストリームの基盤だった listenKey エンドポイントを廃止しました。そのためアカウント、注文、残高の更新は Binance WebSocket API から届くようになり、コンポーネントが自ら開いて再接続後に更新する 2 本目の接続を経由します。イベントの形は同じなのでハンドラーはそのまま動作しますが、新しい購読は署名されます。Binance.ApiKey に加えて Binance.ApiSecret の設定が必要になりました。Binance.us と Futures は引き続き listenKey を使用します。USD-M Futures のマーケットデータは 2 つのアドレスから配信されるようになり、新しい Binance.FuturesStreamEndpoint プロパティで選択します (既定は bfsePublic、集約約定、マーク価格、kline、清算のストリームには bfseMarket)。

その他の修正として、OKX の価格と数量は送信前に小数点以下 5 桁に丸められていたため、より細かいティックの銘柄では指定したとおりの注文にならず、0.00001 未満の値はゼロとして送信されていました。OKX と KuCoin のキープアライブは、WebSocket の ping ではなく各取引所が要求するテキストの ping を送信し、pong が返らない場合は再接続するようになりました。詳しくは Binance のレート制限: バッチ化、ペース配分、読み取れる 429 をご覧ください。

zlib 1.3.1 とソフトウェア部品表

同梱の zlib が 1.2.12 から 1.3.1 になり、inflate のヒープオーバーリードである CVE-2022-37434 が修正されます。リンクされるオブジェクトは Delphi 7 から Delphi 13、32 ビットと 64 ビット向けに再ビルドされ、すべてのコンパイラで検証されました。付随する修正として、Delphi 10 Seattle の 64 ビットでは zlib オブジェクトがまったくリンクされておらず、32 ビットのオブジェクトが選ばれてコンパイルに失敗していた問題も直しました。

すべてのセットアップが sbom.cdx.json もインストールするようになりました。これは CycloneDX 形式のソフトウェア部品表で、ライブラリを構成するコンポーネントをバージョンとライセンスとともに列挙します。Enterprise と All-Access はカスタマイズされた Indy とその zlib のバージョンを記載し、Core、Standard、Professional は Delphi または C++ Builder に付属する Indy であることを記録します。エディションごとにインストーラーのビルド時に生成されるため、インストールした内容と常に一致します。詳しくは sgcWebSockets と sgcOpenAPI の zlib 1.3.1sgcWebSockets が独自の SBOM をインストールするようになりました をご覧ください。

セキュリティ修正

TLS 以外にも、このリリースは多くの穴をふさぎます。以下のリストをお読みいただき、該当するコンポーネントをアプリケーションで使っている場合はアップグレードしてください。

もう 1 つ、見落としやすいものがあります。あるコンポーネントから別のコンポーネントへコピーする際、ほとんどの TLS オプションが失われていました。コピーされていたのは IO ハンドラー、ALPN プロトコル、OpenSSL オプションだけだったため、証明書ファイル、パスワード、ルート証明書、TLS バージョン、VerifyCertificate は空のままとなり、そのように設定されたコンポーネントは結局何も検証しない状態になっていました。

細かな追加

このリリース前後の新着

ライブラリに同梱される 2 つの要素について、今月それぞれ記事を公開しました。1 つは REST サーバーコンポーネントで、CORS、メトリクス、ヘルスエンドポイント、マルチテナンシー、そして 1 つの仕様ファイルからルーティング、検証、セキュリティ、Swagger UI を生成する OpenAPI プラグインを備えた、REST API のための HTTP サーバーです。もう 1 つは sgcProtoBuf で、.proto ファイルを Delphi のユニットに変換するコードジェネレーターです。詳しくは TsgcHTTPRESTServer: 新しい REST サーバーコンポーネントREST サーバー + OpenAPIユーザー、マルチテナンシー、メトリクスsgcProtoBuf: .proto ファイルから Delphi ユニットへ をご覧ください。

.NET エディション

sgcWebSockets .NET 2026.8 は、このリリースの共通部分を引き継いでいます。SChannel 関連の作業はすべて含まれています。無視されていたバージョン、複製された接続で失われていた設定、検証されない再ネゴシエーション、完了しなかった TLS 1.3 接続、気づかれなかった切断、無制限のハンドシェイク、起動時の競合状態、さらにクローズ前のクローズ通知とサーバー側のクライアント証明書検証です。IOCP と EPOLL の修正、WebSocket のハンドシェイクとフレーム検証、MQTT、STOMP、AMQP、HTTP/2 の堅牢化、OAuth2、WebAuthn、MCP、Files プロトコルのセキュリティ修正、そして取引所関連の変更 (Binance Spot ユーザーデータストリーム、ペース配分された再接続時の再送、OKX と KuCoin のキープアライブ) も含まれます。レシートを伴う STOMP の正常な Disconnect と、WebSocket サーバーの制御フレーム制限も、こちらでは新規追加です。

アップグレード

2026.8 は既存の 2026.x プロジェクトにそのまま適用できるアップグレードですが、ビルド前に知っておくべきことが 2 つあります。Binance Spot のユーザーデータストリームは購読に署名するようになったため、Binance.ApiKey と併せて Binance.ApiSecret を設定する必要があります。また WebSocket サーバーは既定で無制限ではなく同時 10,000 接続を受け入れるようになったため、それを超える運用をしている場合は MaxConnections を引き上げるか 0 に設定してください。

それ以外はすべて、明示的に有効にするまでオフです。失効チェック、バージョンの下限、強力な暗号スイート、クライアント証明書検証、IOCP および EPOLL エンジンのキープアライブ、メッセージスロットル、MCP の許可オリジンリストは、いずれも従来の動作が既定になっています。

有効なサブスクリプションをお持ちのお客様は、カスタマーエリアまたは esegece.com/products/websockets/download から新しいビルドをダウンロードできます。

ご質問、ご意見、移行のサポートが必要ですか。お問い合わせください。コードを書いた本人から返信が届きます。