読者です 読者をやめる 読者になる 読者になる

M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

Jetty の ProxyServlet でプロキシサーバを実現する(HTTPS対応)

Java

JettyでHTTPプロキシを実現する方法は、Web上で検索すれば日本語でもいくつかの記事がある様子。しかし中継するHTTP/HTTPSリクエストに何らかの操作を加える方法については日本語の記事が見つからなかった。今後のため、今回調べてわかった内容をメモしておく。

Jettyのバイナリを取得

Jettyのバイナリはプロジェクトの公式サイトから入手可能。マニュアルJavadocも同サイトにてオンラインで参照できる。

ただし、プロキシAPIについてはたいしたことは記載されていないためあまり役に立たない。またソースコードが必要な場合は、プロジェクトのGitリポジトリから取得する必要がある。筆者の作業環境(Windows8、64bit、Cygwin上でGitを使用)では、gitプロトコルでコード取得しようとすると途中でエラーになってしまい、時間はかかるがHTTPプロトコルで取得する必要があった。

Proxyサーバ起動用コードを記述

公式サイトからダウンロードしたZIPファイルを解凍して、JARファイル(バージョン9.1.1ではlibフォルダ配下にある)をすべてJavaプロジェクトのビルド・パスに追加する。不要なモジュールが存在する可能性が高いけれど、見極めは難しそうなのでとにかくすべて登録してしまう。

そしてHTTP/HTTPS接続をプロキシする(そしてプロキシする接続に干渉する)コードを記述する。といっても、上記配布物にも含まれているProxyServerの実装をそのまま使えばよい。あとは必要に応じてロジックを追加するだけである。

HTTP接続のプロキシについては、ProxyServletを使って実現できる。本当に単なるプロキシを作りたいのであれば、同クラスをそのまま使用すれば良い。もしリクエスト/レスポンスに何らかの干渉をしたいのであれば、serviceメソッドやrewriteURIメソッドの実装を上書きする。

HTTPS接続のプロキシについては、ConnectHandlerを使う。クライアントはプロキシに対して、最終接続先のホストを指定してCONNECTメソッドのリクエストを行う。プロキシはこれに応えて、クライアントと最終接続先ホストの間のパケットのやりとりを仲介するように動く。こちらも単なるプロキシ目的であれば、同クラスをそのまま使用すれば良い。もし干渉をしたいのであれば、handleConnectメソッドなどの実装を上書きすることになる。

        Server server = new Server();
        ServerConnector connector = new ServerConnector(server);

        // リクエストを待ち受けるポート番号を設定
        connector.setPort(8888);
        server.addConnector(connector);

        // HTTPS接続をプロキシするため、CONNECTメソッドを処理するハンドラーを設定
        ConnectHandler proxy = new ConnectHandler() {
        	@Override
        	protected void handleConnect(Request jettyRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress) {
        		// ここに任意のロジックを記載すればリクエスト/レスポンスに干渉できる 
        		super.handleConnect(jettyRequest, request, response, serverAddress);
        	}
        };
        server.setHandler(proxy);

        // HTTP接続をプロキシするため、ProxyServletを設定
        ServletContextHandler context = new ServletContextHandler(proxy, "/", ServletContextHandler.SESSIONS);
        ServletHolder proxyServlet = new ServletHolder(new ProxyServlet(){
        	@Override
        	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        		// ここに任意のロジックを記載すればリクエスト/レスポンスに干渉できる 
        		super.service(request, response);
        	}
        });
        proxyServlet.setInitParameter("blackList", "www.eclipse.org");
        context.addServlet(proxyServlet, "/*");

        server.start();