Java内蔵のJettyサーバ

1.概要

この記事では、http://www.eclipse.org/jetty/[ Jetty ]ライブラリーを見ていきます。 Jettyは、埋め込みコンテナとして実行できるWebサーバーを提供し、 javax.servlet ライブラリと簡単に統合できます。

2 Mavenの依存関係

始めるには、Mavenの依存関係をhttps://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.eclipse.jetty%22%20AND%20a%3A%22jetty-serverに追加します。 %22[jetty-server]およびhttps://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.eclipse.jetty%22%20AND%20a%3A%22jetty-servlet%22[jetty-servlet]ライブラリ

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-server</artifactId>
    <version>9.4.3.v20170317</version>
</dependency>
<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-servlet</artifactId>
    <version>9.4.3.v20170317</version>
</dependency>

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でそのようなロジックを提供するには、 startAsync()を呼び出して http://docs.oracle.com/javaee/6/api/javax/servlet/AsyncContext.html クラスを使用するサーブレットを作成します。 HttpServletRequestの メソッドこのコードは実行中のスレッドをブロックしませんが、 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();
            }
        });
    }
}

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

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

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桟橋の設定

本番環境でWebアプリケーションを実行するときは、Jettyサーバーが要求を処理する方法を調整したいと思うかもしれません。これはスレッドプールを定義してそれをJettyサーバーに適用することによって行われます。

これを行うために、設定できる3つの構成設定があります。

  • maxThreads - Jettyが可能なスレッドの最大数を指定する

プールで作成して使用する ** minThreads – プール内の初期スレッド数を設定します。

桟橋が使用します ** 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アプリケーションをテストしました。