HTTP/1.1 プロキシは CONNECT の中で宛先の HTTP/2 フレームを運べます。プロキシがその内側のフレームを実装する必要はありませんが、クライアントはプロトコルの受け渡しを順番に行う必要があります。

完全な CONNECT 応答を検証し tunnelSocket を保持したら、宛先 TLS を追加し、確立したストリームを HTTP/2 に渡します。

import tls from 'node:tls';
import http2 from 'node:http2';

const secureSocket = tls.connect({
  socket: tunnelSocket,
  servername: 'api.example',
  ALPNProtocols: ['h2'],
});
secureSocket.once('error', failRoute);
secureSocket.once('secureConnect', () => {
  if (secureSocket.alpnProtocol !== 'h2') {
    secureSocket.destroy();
    failRoute(new Error('Destination did not negotiate h2'));
    return;
  }
  const session = http2.connect('https://api.example', {
    createConnection: () => secureSocket,
  });
  session.on('error', failRoute);
  useSession(session);
});

createConnection は別の直接ソケットを開く代わりに、プロキシがすでに運んでいる TLS ストリームを返します。宛先の servername は SNI と証明書の同一性を提供し、検証は有効なままです。ALPN チェックは h2 をネゴシエートしなかった宛先を拒否します。

failRoute はルートを後始末する必要があり、useSession がリクエストとセッションの終了を所有します。この断片の周囲に接続と操作の期限を設定してください。

外側のコネクタは依然として host:port の CONNECT ターゲティング、プロキシ認証、拒否の処理、ヘッダー後にバッファされたバイトを所有します。ソケットを TLS に渡す前にそれらを終えてください。HTTP/1.1 プロキシのリスナーへ直接送った HTTP/2 のプリアンブルは間違った層から始まります。

関連記事