2

HTTP 要求のいくつかの側面を変更できる Java アプリケーションでプロキシを作成しようとしています。

これを行うには、ポート 8080 で ServerSocket を開き、そのポートで Mozilla Firefox のプロキシを構成し、接続ごとに別のスレッドで ServerSocket の accept() メソッドを実行します。これまでのところ、すべて正常です。

ブラウザーから対応する Web サイトに要求を送信するには、Java 11 に含まれている HttpClient ライブラリを使用します。これは、そのライブラリを使用するコードの一部です。

private void obtainResponse(Socket socket, IHttpRequest req, String uri) {

        HttpClient client = null;
        if (req.isSSL()) {
            SSLContext sslContext = ((SecureConnectionHandler)connHandler).createSSLContext( req.getHost() );
            client = HttpClient.newBuilder()
                    .connectTimeout(Duration.ofSeconds(30))
                    .priority(1)
                    .version(HttpClient.Version.HTTP_2)
                    .followRedirects(Redirect.NORMAL)
                    .sslContext( sslContext )
                    .build();
        }
        else
            client = HttpClient.newBuilder()
                    .connectTimeout(Duration.ofSeconds(30))
                    .priority(1)
                    .version(HttpClient.Version.HTTP_2)
                    .followRedirects(Redirect.NORMAL)
                    .build();

        String protocolAndHost = ((req.isSSL()) ? "https://" : "http://") + req.getHost();

        if (uri == null)
            uri = protocolAndHost + req.getRequestedResource();
        else {
            if (uri.startsWith("/"))
                uri = protocolAndHost + uri;
            System.out.println("Aqui:" + uri);
        }

        HttpRequest.Builder preRequest=null;
        if (req.getMethod().equalsIgnoreCase("GET")) {
            preRequest = HttpRequest.newBuilder()  // GET request!
                .uri(URI.create( uri ))
                .GET();
        }
        else if (req.getMethod().equalsIgnoreCase("POST")) {
            preRequest = HttpRequest.newBuilder()  // POST request!
            .uri(URI.create( uri ))
            .POST(BodyPublishers.ofString(req.getBody()));
        }

        for (Header header : req.getHeaders()) {
            if (!header.getKey().equalsIgnoreCase("Host") &&
                !header.getKey().equalsIgnoreCase("Connection") &&
                !header.getKey().equalsIgnoreCase("Content-Length") &&
                !header.getKey().equalsIgnoreCase("Upgrade") ) 
            {
                preRequest.setHeader(header.getKey(), header.getValues());
            }
        }
        HttpRequest request = preRequest.build();

        System.err.println("Request to: " + uri);

        HttpResponse<byte[]> response;
        try {
            response = client.sendAsync(request, BodyHandlers.ofByteArray())
                             .join();   
        } catch (CompletionException ce) {
            System.err.println("Address " + uri + " is unreachable!");
            return ;
        }

        HttpHeaders httpHeaders = response.headers();

        Optional<String> locationHeader = httpHeaders.firstValue("Location"); // When resource has been permanently moved

        if ( !locationHeader.isEmpty() ) {
            System.out.println("Moved permanently to " + locationHeader.get());
            obtainResponse( socket, req, locationHeader.get() );
        }
        else {
            Map<String, List<String>> headers = httpHeaders.map();

            String protocol = response.version().toString().replace("_", ".").replaceFirst("\\.", "/");

            int code = response.statusCode();

            String reasonPhrase = HttpStatus.getStatusText( code );

            var crlf = "\r\n";

            var responseString = protocol + " " + code + " " + reasonPhrase + crlf;

            for (String key : headers.keySet()) {
                responseString += key + ":";
                for (String valor : headers.get(key)) {
                    responseString += " " + valor;
                }
                responseString += crlf;
            }

            responseString += crlf; // espacio cabeceras y cuerpo

            writeResponse(socket, response.body(), responseString);
        }
    }


private void writeResponse(Socket socket, byte[] streamResponse, String responseHeaders) {
        OutputStream outputStream = null;
        try {
            outputStream = socket.getOutputStream(); 

            outputStream.write(responseHeaders.getBytes());
            outputStream.write(streamResponse);
            outputStream.flush();

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                if (!socket.isOutputShutdown()) {
                    socket.shutdownOutput();
                }
                outputStream.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
    }

注: IHttpRequest は、ソケットから収集されたすべての情報 (ターゲット ホスト、ヘッダー、存在する場合は本文など) を含む、私が作成したクラスです。

これまでのところ、HTTP Web サイトに向けられたすべてのリクエストを傍受することに成功しています。ただし、たとえば、HTTP / 2 プロトコルを実装し、TLS を使用するhttps://www.google.com/には問題があります。Java アプリケーションを実行して前の Google ページにアクセスすると、Web サイトが表示される代わりに、ブラウザーに次のように表示されます (ヘッダー間の空白は無視してください)。

HTTP/2 200 OK

:ステータス: 200

alt-svc: quic = ":443"; 馬 = 2592000; v = "46,43,39"

キャッシュ制御: プライベート

コンテンツエンコーディング: gzip

コンテンツの長さ: 46058

コンテンツ タイプ: テキスト/html; 文字セット = UTF-8

日付: 2019 年 9 月 3 日 (火) 09:47:34 GMT

有効期限: 2019 年 9 月 3 日 (火) 09:47:34 GMT

p3p: CP = "これは P3P ポリシーではありません! 詳しくは g.co/p3phelp をご覧ください。"

サーバー: gws

セット Cookie: 1P_JAR = 2019-09-03-09; 有効期限 = 2019 年 10 月 3 日木曜日 09:47:34 GMT; パス = /; ドメイン = .google.com; SameSite = none NID = 188 = XOJkffugf5G8rxNLov_iqqxo-Cq5RCvhwJPNu9tvtzLesZ4q8CE0IDVt9VgCEHZsw-AV0EYaaL8D4d_2Qwb6jXCcss7RydfV9PqQFemN_Ezz0kUjyseDDbJXfrHpmqPR6GIQCnR7bjukfasxg883K9fjnhAaqz6IpUYxoguZx-vazWc; 有効期限 = 水、2020 年 3 月 4 日 09:47:34 GMT; パス = /; ドメイン = .google.com; HttpOnly 同意 = WP.27dd1a; 有効期限 = 金曜日、2038 年 1 月 1 日 00:00:00 GMT; パス = /; ドメイン = .google.com

x-frame-options: SAMEORIGIN

x-xss-保護: 0

(↓↓本文↓↓)

‹����� ÿÔ½ézâȲ (ú¿Ÿ‚ ¢ örÁ²À または PªÚ ° çyÜÞ¾ © $ Æ.ÞåžG¸ßýwþ® »™ š ¶« »× ùöíê ¢ ¤T '' '' ™ 'ß¿ († l / Æj ¢ または õ ßñ7¡ “QOLªV ÞU ¢ üø> Tm' ûÄ´T [L ^] îd * I7Õ Ùê Rçšb ÷ EE i²š¡ / ÜP iÃé0cÉDWE! ËsCò I ™ ZªI_ ‰) ## ™ '* & gš: ¦½ÒŽê¸ oŒ ‚™» † 9 $ vFQmU¶5c´Rˆ (Š © ZÖï 1L§ Üì ¦ÚUMS5W² Û $ # K'¶êæí FOWWrniCÒS- ò + Ú¨ · Åòõ¶ „÷ ñɲá 1 • 'ÙÐ

<< バイト形式のより多くの情報 >>

私のコードに奇妙な点はありますか? HTTP/2 がフレーム内のヘッダーを圧縮することは知っていますが、HTTPClient が内部で圧縮していると想定していました...

さらに情報が必要な場合は、お知らせください:)

前もって感謝します。

4

1 に答える 1