Edited at

【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というサーブレットを追加する

// ServletContextHandlerはサーブレットをハンドリングする

ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);

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


●フォームのアップロードサイズを指定する

大きなサイズのデータをアップロードしようとすると、java.lang.IllegalStateException: Form too largeをお見舞いされることがあります。

そういうときには、setMaxFormContentSizeで必要なサイズに指定します。


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


●静的コンテンツ(static file)の置き場所を指定する

setResourceBaseでresource baseを指定します。

// ResourceHandlerは(ざっくりいうと)静的コンテンツをハンドリングする

final ResourceHandler resourceHandler = new ResourceHandler();

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


●静的コンテンツのファイル一覧をみせたくない

Apache等古くから伝わるwebサーバー伝統のおせっかい機能です。

ファイル一覧(リスティング)表示をしたくない場合はこうします

// 静的コンテンツのファイル一覧(リスティング)表示しない

resourceHandler.setDirectoriesListed(false);


●デフォルトのhtmlをindex.htmlから変更したい

setWelcomeFilesで変更可能です

// 初期表示するファイルを指定

resourceHandler.setWelcomeFiles(new String[] { "index.html" });


●キャッシュを無効にする。キャッシュさせない。

HTMLやJavaScript開発者を悩ます静的コンテンツのキャッシュをオフする方法です。

setCacheControlによって、焼け石に水のF5連打から解放されます。

// キャッシュさせない

resourceHandler.setCacheControl("no-store,no-cache,must-revalidate");


●サーバーのバージョン情報をヘッダー等で送出させない

jettyのバージョン番号等情報がHTTPヘッダや、デフォルトのエラー画面等に表示したくない場合に使います

HttpConfig#setSendServerVersionで設定可能です。

2つの方法を示します

(やりたいことはシンプルなのに、コードがやや長くなります。ここは、jetty9リファクタでjetty8より複雑化しました)

【方法その1】

http connectorを自前でつくり、httpConfig をセットする

// デフォルトコンストラクタでサーバーを初期化する

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 });

【方法その2】

(既存の)http connectorをひっぱりだしてきて、httpConfig をセットする

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 のようにアクセスします。

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

<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サーバーを作る!」をモチーフにこちらの記事にまとめています。