独自の TCP プロトコルは、そのプロキシが宛先への CONNECT を許可していれば HTTP プロキシを通り抜けられます。アプリケーションのプロトコルが HTTP になる必要はありません。HTTP がトンネルを確立し、その後アプリケーションがその中で独自のバイトを交換します。

OpenJDK 25 は Socket(Proxy) に HTTP プロキシ経路を提供します。その実装はCONNECT の交換を行い、アプリケーションが使う接続済み socket を返します。

Proxy route = new Proxy(
    Proxy.Type.HTTP,
    new InetSocketAddress("proxy.example", 8080));

try (Socket socket = new Socket(route)) {
    socket.setSoTimeout(10000);
    socket.connect(
        InetSocketAddress.createUnresolved("tcp-service.example", 8443),
        5000);
    InputStream input = socket.getInputStream();
    OutputStream output = socket.getOutputStream();
}

この断片は java.net と java.io のクラスを使います。2 つの端点を置き換え、try ブロックを抜ける前にプロトコルの交換を行ってください。許可された公開 TCP ターゲットについては、そのターゲットのポートに対する CONNECT 権限を確認したうえで、HTTP プロキシ一覧から HTTP 候補を選びます。プロキシのアドレスは Java が最初に接続する場所であり、connect に渡すアドレスはプロキシが到達すべきサービスを識別します。

未解決の宛先は、この実装の CONNECT 要求のためにホスト名を保持します。一方、解決済みの InetSocketAddress は、その要求に解決済みの IP アドレスを渡します。この違いは、プロキシがサービスの名前を解決する必要がある場合や、ホスト名に基づくポリシーを適用する場合に重要です。

接続が成功した後に残るもの

得られるストリームはトンネル化されたサービスのデータを運びます。アプリケーションが読んで捨てるべき別個の HTTP 応答は現れません。2 回目の CONNECT 交換を追加すると、JDK がすでにトンネルを確立した後に HTTP のテキストをサービスへ送ってしまいます。

この例は接続タイムアウトと socket の読み取りタイムアウトを設定します。これらは別々の制御であり、アプリケーションのセッション全体に対する 1 つの期限ではありません。

CONNECT の権限は依然としてプロキシに属します。通常の Web 要求を転送する端点は、サービスのポートを拒否したり、トンネルを開く前に認証を要求したりすることがあります。これらの失敗は、トンネルを受け入れた後にアプリケーションのプロトコルを拒否するサーバーとは区別してください。

そのプロトコルが TLS を必要とする場合は、接続済みの socket を通じて、サービスの意図されたホスト名と通常の証明書検証を用いて TLS をネゴシエートします。CONNECT だけではストリームは暗号化されません。実際の HTTP API では、HTTP メッセージも扱える HTTP クライアントのほうが便利なインターフェースであり続けます。

関連記事