【2018/12月更新】Jetty9でつくる組み込みwebサーバーとservletのTips

ご存知Jetty9は軽量で高機能なwebサーバー、servletコンテナです。

Jetty9をJavaアプリ組み込みで使うときに、良く使う設定TIPSと、servletまで含めたサンプルコードを掲載します。

動作するソースコード一式(maven project)は https://github.com/riversun/jetty9-quick-start にあげています。

Jetty9でHTTPS(SSL/TLS)する記事は https://qiita.com/riversun/items/2909019123b28471ea79 にまとめました。

# Jetty9 組み込み設定Tips (v.9.4.12で動作確認)

### ●Servletを追加する

"/api" というパスで受け付けるExampleServlet.javaというサーブレットを追加する

```java
// ServletContextHandlerはサーブレットをハンドリングする
ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);

// サーブレットを追加
servletHandler.addServlet(new ServletHolder(new ExampleServlet()), "/api");
```

### ●フォームのアップロードサイズを指定する
大きなサイズのデータをアップロードしようとすると、**java.lang.IllegalStateException: Form too large**をお見舞いされることがあります。
そういうときには、**setMaxFormContentSize**で必要なサイズに指定します。

```java

// フォームのアップロードサイズを指定
servletHandler.setMaxFormContentSize(1024 * 1024 * 1024);
```

### ●静的コンテンツ(static file)の置き場所を指定する
**setResourceBase**でresource baseを指定します。

```java
// ResourceHandlerは（ざっくりいうと）静的コンテンツをハンドリングする
final ResourceHandler resourceHandler = new ResourceHandler();

// 静的コンテンツの置き場所を指定
resourceHandler.setResourceBase(System.getProperty("user.dir") + "/htdocs");
```

### ●静的コンテンツのファイル一覧をみせたくない
Apache等古くから伝わるwebサーバー伝統の~~おせっかい~~機能です。
ファイル一覧（リスティング）表示をしたくない場合はこうします

```java
// 静的コンテンツのファイル一覧（リスティング）表示しない
resourceHandler.setDirectoriesListed(false);
```

### ●デフォルトのhtmlをindex.htmlから変更したい
**setWelcomeFiles**で変更可能です

```java
// 初期表示するファイルを指定
resourceHandler.setWelcomeFiles(new String[] { "index.html" });

```

### ●キャッシュを無効にする。キャッシュさせない。
HTMLやJavaScript開発者を悩ます静的コンテンツのキャッシュをオフする方法です。
**setCacheControl**によって、焼け石に水のF5連打から解放されます。

```java
// キャッシュさせない
resourceHandler.setCacheControl("no-store,no-cache,must-revalidate");
```

### ●サーバーのバージョン情報をヘッダー等で送出させない
jettyのバージョン番号等情報がHTTPヘッダや、デフォルトのエラー画面等に表示したくない場合に使います
**HttpConfig#setSendServerVersion**で設定可能です。

２つの方法を示します
（やりたいことはシンプルなのに、コードがやや長くなります。ここは、jetty9リファクタでjetty8より複雑化しました）

【方法その１】
http connectorを自前でつくり、httpConfig をセットする

```java
// デフォルトコンストラクタでサーバーを初期化する
final Server jettyServer = new Server();
jettyServer.setHandler(handlerList);

final int PORT = 8080;

// httpの設定クラス
final HttpConfiguration httpConfig = new HttpConfiguration();

// サーバーのバージョン情報をヘッダにのせない
httpConfig.setSendServerVersion(false);
final HttpConnectionFactory httpConnFactory = new HttpConnectionFactory(httpConfig);
final ServerConnector httpConnector = new ServerConnector(jettyServer, httpConnFactory);
httpConnector.setPort(PORT);
jettyServer.setConnectors(new Connector[] { httpConnector });
```

【方法その２】
(既存の)http connectorをひっぱりだしてきて、httpConfig をセットする

```java
final int PORT = 8080;

final Server jettyServer = new Server(PORT );

for (Connector conn : jettyServer.getConnectors()) {
  for (ConnectionFactory connFactory : conn.getConnectionFactories()) {
    if (connFactory instanceof HttpConnectionFactory) {
      ((HttpConnectionFactory) connFactory).getHttpConfiguration().setSendServerVersion(false);
    }
  }
}
```

# Servlet on Jetty9 サンプルコード全文

上で説明したコードを含むJSONを返すCORS(クロスドメイン)アクセス対応Web API風servletサンプルです。
Mavenのdependencyをセットして、コピペ、実行して http://localhost:8080/api?message=hello のようにアクセスします。


```java
package com.example.jetty;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Jetty9 Quick Start Example
 */
public class ServletApp
{
	public static void main(String[] args) {

		// ServletContextHandlerはサーブレットをハンドリングする
		ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);

		// フォームのアップロードサイズを指定
		servletHandler.setMaxFormContentSize(1024 * 1024 * 1024);

		// サーブレットを追加
		servletHandler.addServlet(new ServletHolder(new ExampleServlet()), "/api");

		// ResourceHandlerは（ざっくりいうと）静的コンテンツをハンドリングする
		final ResourceHandler resourceHandler = new ResourceHandler();

		// 静的コンテンツの置き場所を指定
		resourceHandler.setResourceBase(System.getProperty("user.dir") + "/htdocs");

		// 静的コンテンツのファイル一覧（リスティング）表示しない
		resourceHandler.setDirectoriesListed(false);

		// 初期表示するファイルを指定
		resourceHandler.setWelcomeFiles(new String[] { "index.html" });

		// キャッシュさせない
		resourceHandler.setCacheControl("no-store,no-cache,must-revalidate");

		HandlerList handlerList = new HandlerList();

		// resourceHandlerが先にくるように指定する（逆にすると静的コンテンツは永遠によばれない。。)
		handlerList.addHandler(resourceHandler);
		handlerList.addHandler(servletHandler);

		// デフォルトコンストラクタでサーバーを初期化する
		final Server jettyServer = new Server();
		jettyServer.setHandler(handlerList);

		final int PORT = 8080;

		// httpの設定クラス
		final HttpConfiguration httpConfig = new HttpConfiguration();

		// サーバーのバージョン情報をヘッダにのせない
		httpConfig.setSendServerVersion(false);
		final HttpConnectionFactory httpConnFactory = new HttpConnectionFactory(httpConfig);
		final ServerConnector httpConnector = new ServerConnector(jettyServer, httpConnFactory);
		httpConnector.setPort(PORT);
		jettyServer.setConnectors(new Connector[] { httpConnector });

		try {
			jettyServer.start();
			jettyServer.join();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	@SuppressWarnings("serial")
	public static class ExampleServlet extends HttpServlet {

		final ObjectMapper mObjectMapper = new ObjectMapper();

		final class Result {
			public boolean success;
			public String message;
		}

		@Override
		protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

			// queryパラメータを取得
			final String paramMessage = req.getParameter("message");

			// レスポンス格納用POJO(あとでJSONに変換する)
			final Result result = new Result();
			result.success = true;
			result.message = "You say '" + paramMessage + "'";

			// CORS(Cross-Origin Resource Sharing)を有効にする
			resp.addHeader("Access-Control-Allow-Origin", "*");
			resp.addHeader("Access-Control-Allow-Headers", "Content-Type");

			// JSONを返すのContent-TypeをJSONにする
			final String CONTENT_TYPE = "application/json; charset=UTF-8";
			resp.setContentType(CONTENT_TYPE);

			final PrintWriter out = resp.getWriter();
			// JacksonでPOJOをJSONに変換する
			final String json = mObjectMapper.writeValueAsString(result);

			// レスポンスを生成
			out.println(json);
			out.close();

		}

		@Override
		protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
			resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Sorry, POST is not supported");
		}

		@Override
		protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
			resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Sorry, PUT is not supported");
		}

		@Override
		protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
			resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Sorry, DELETE is not supported");
		}

		@Override
		protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

			// For PreFlight Access
			// CORS(Cross-Origin Resource Sharing)を有効にする
			resp.addHeader("Access-Control-Allow-Origin", "*");
			resp.addHeader("Access-Control-Allow-Headers", "Content-Type");

		}

	}

		
}

```

### Maven

```xml
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.4.12.v20180830</version>
</dependency>

<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>9.4.12.v20180830</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.8.1</version>
</dependency>
```

### ソースコード
ソースコードはこちらです
https://github.com/riversun/jetty9-quick-start

# おわりに

**Jetty**の詳しい動作については、「[20行でHTTPSサーバーを作る！](https://qiita.com/riversun/items/2909019123b28471ea79)」をモチーフに[こちらの記事](https://qiita.com/riversun/items/2909019123b28471ea79)にまとめています。