Java内蔵のJettyサーバ

Javaの組み込みJettyサーバー

1. 概要

この記事では、Jettyライブラリについて説明します。 Jettyは、組み込みコンテナとして実行でき、javax.servletライブラリと簡単に統合できるWebサーバーを提供します。

2. Mavenの依存関係

開始するには、Mavenの依存関係をjetty-serverおよびjetty-servletライブラリに追加します。


    org.eclipse.jetty
    jetty-server
    9.4.3.v20170317


    org.eclipse.jetty
    jetty-servlet
    9.4.3.v20170317

3. サーブレットを使用したJettyサーバーの起動

Jetty組み込みコンテナーの開始は簡単です。 新しいServerオブジェクトをインスタンス化し、指定されたポートで開始するように設定する必要があります。

public class JettyServer {
    private Server server;

    public void start() throws Exception {
        server = new Server();
        ServerConnector connector = new ServerConnector(server);
        connector.setPort(8090);
        server.setConnectors(new Connector[] {connector});
}

すべてがうまくいけば、HTTPステータスコード200と単純なJSONペイロードで応答するエンドポイントを作成するとします。

このようなリクエストを処理するためにHttpServletクラスを拡張するクラスを作成します。このクラスはシングルスレッドで、完了するまでブロックされます。

public class BlockingServlet extends HttpServlet {

    protected void doGet(
      HttpServletRequest request,
      HttpServletResponse response)
      throws ServletException, IOException {

        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_OK);
        response.getWriter().println("{ \"status\": \"ok\"}");
    }
}

次に、addServletWithMapping()メソッドを使用してBlockingServletクラスをServletHandlerオブジェクトに登録し、サーバーを起動する必要があります。

servletHandler.addServletWithMapping(BlockingServlet.class, "/status");
server.start();

サーブレットロジックをテストする場合は、テストセットアップ内の実際のJettyサーバーインスタンスのラッパーである、以前に作成したJettyServerクラスを使用してサーバーを起動する必要があります。

@Before
public void setup() throws Exception {
    jettyServer = new JettyServer();
    jettyServer.start();
}

開始したら、テストHTTPリクエストを/statusエンドポイントに送信します。

String url = "http://localhost:8090/status";
HttpClient client = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(url);

HttpResponse response = client.execute(request);

assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);

4. ノンブロッキングサーブレット

Jettyは、非同期要求処理を適切にサポートしています。

実行中のスレッドをかなりの時間ロードブロッキングするのに長い時間がかかるI / O集約型の巨大なリソースがあるとしましょう。 I / Oリソースを待つのではなく、その間に他のリクエストを処理するためにそのスレッドを解放できるとよいでしょう。

このようなロジックをJettyに提供するために、HttpServletRequest.startAsync()メソッドを呼び出すことにより、AsyncContextクラスを使用するサーブレットを作成できます。このコードは実行中のスレッドをブロックしませんが、 AsyncContext.complete()メソッドを使用して準備ができたときに結果を返す別のスレッドでのI / O操作:

public class AsyncServlet extends HttpServlet {
    private static String HEAVY_RESOURCE
      = "This is some heavy resource that will be served in an async way";

    protected void doGet(
      HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

        ByteBuffer content = ByteBuffer.wrap(
          HEAVY_RESOURCE.getBytes(StandardCharsets.UTF_8));

        AsyncContext async = request.startAsync();
        ServletOutputStream out = response.getOutputStream();
        out.setWriteListener(new WriteListener() {
            @Override
            public void onWritePossible() throws IOException {
                while (out.isReady()) {
                    if (!content.hasRemaining()) {
                        response.setStatus(200);
                        async.complete();
                        return;
                    }
                    out.write(content.get());
                }
            }

            @Override
            public void onError(Throwable t) {
                getServletContext().log("Async Error", t);
                async.complete();
            }
        });
    }
}

ByteBufferOutputStreamに書き込んでおり、バッファー全体が書き込まれると、complete()メソッドを呼び出して、結果をクライアントに返す準備ができていることを通知します。

次に、AsyncServletをJettyサーブレットマッピングとして追加する必要があります。

servletHandler.addServletWithMapping(
  AsyncServlet.class, "/heavy/async");

これで、/heavy/asyncエンドポイントにリクエストを送信できます。そのリクエストはJettyによって非同期的に処理されます。

String url = "http://localhost:8090/heavy/async";
HttpClient client = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(url);
HttpResponse response = client.execute(request);

assertThat(response.getStatusLine().getStatusCode())
  .isEqualTo(200);
String responseContent = IOUtils.toString(r
  esponse.getEntity().getContent(), StandardCharsets.UTF_8);
assertThat(responseContent).isEqualTo(
  "This is some heavy resource that will be served in an async way");

アプリケーションが非同期でリクエストを処理している場合、スレッドプールを明示的に構成する必要があります。 次のセクションでは、カスタムスレッドプールを使用するようにJettyを構成します。

5. Jetty構成

本番環境でWebアプリケーションを実行するとき、Jettyサーバーがリクエストを処理する方法を調整したい場合があります。 これは、スレッドプールを定義し、Jettyサーバーに適用することで実行されます。

これを行うには、次の3つの設定を設定できます。

  • maxThreads –Jettyがプールで作成および使用できるスレッドの最大数を指定します

  • minThreads –Jettyが使用するプール内のスレッドの初期数を設定します

  • idleTimeout –ミリ秒単位のこの値は、スレッドが停止してスレッドプールから削除されるまでにアイドル状態にできる時間を定義します。 プール内の残りのスレッドの数がminThreads設定を下回ることはありません

これらを使用して、構成済みのスレッドプールをServerコンストラクターに渡すことにより、組み込みJettyサーバーをプログラムで構成できます。

int maxThreads = 100;
int minThreads = 10;
int idleTimeout = 120;

QueuedThreadPool threadPool = new QueuedThreadPool(maxThreads, minThreads, idleTimeout);

server = new Server(threadPool);

次に、サーバーを起動すると、特定のスレッドプールのスレッドが使用されます。

6. 結論

このクイックチュートリアルでは、組み込みサーバーをJettyに統合する方法を確認し、Webアプリケーションをテストしました。

いつものように、コードはover on GitHubで利用できます。