最近、ちょっと組み込みのJettyを触っている時間が増えたので、ちょっとまとめておこうかなと思います。
Jettyを組み込みの形で使って、以下のことを試してみたいと思います。
- Servletを使う
- Webアプリケーション(Servlet+アノテーション / web.xmlレス)を使う
- Webアプリケーション(Servlet+web.xml)を使う
- JSPを使う
- JSTLを使う
利用するJettyのバージョンは「9.4.8.v20171121」とし、参考にするドキュメントおよびリソースはこちらを。
https://www.eclipse.org/jetty/documentation/9.4.8.v20171121/embedded-examples.html
確認はテストコードで行うので、各プロジェクトのMaven依存関係にはいつも以下が入っているものとします。
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.0.3</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.9.0</version> <scope>test</scope> </dependency>
Servletを使う
まずは、単純にServletだけを使う方法。
Embedded Examples / Minimal Servlet
必要なMaven依存関係は、こちら。
<dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> <version>9.4.8.v20171121</version> </dependency>
簡単なServletを用意しましょう。
src/test/java/org/littlewings/embedded/jetty/HelloServlet.java
package org.littlewings.embedded.jetty; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.getWriter().write("Hello Servlet!!"); } }
Jettyを使った確認コードは、こちら。
src/test/java/org/littlewings/embedded/jetty/SimpleJettyServletTest.java
package org.littlewings.embedded.jetty; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URI; import java.nio.charset.StandardCharsets; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; public class SimpleJettyServletTest { @Test public void test() throws Exception { Server server = new Server(8080); ServletHandler handler = new ServletHandler(); handler.addServletWithMapping(HelloServlet.class, "/hello"); server.setHandler(handler); server.start(); HttpURLConnection conn = (HttpURLConnection) URI.create("http://localhost:8080/hello").toURL().openConnection(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { assertThat(reader.readLine()).isEqualTo("Hello Servlet!!"); } conn.disconnect(); server.stop(); } }
まず、Serverのインスタンスを作成。この時、リッスンポートを指定します。
Server server = new Server(8080);
ServletHandlerを生成して、マッピングするURLとともにSerlvetのクラスを登録し、Serverへ設定します。
ServletHandler handler = new ServletHandler(); handler.addServletWithMapping(HelloServlet.class, "/hello"); server.setHandler(handler);
あとは、Server#start、stopで起動/停止です。
server.start();
/////
server.stop();
簡単ですね。
確認。
HttpURLConnection conn = (HttpURLConnection) URI.create("http://localhost:8080/hello").toURL().openConnection(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { assertThat(reader.readLine()).isEqualTo("Hello Servlet!!"); } conn.disconnect();
これで、Servletが動かせましたよ、と。
Webアプリケーション(Servlet+アノテーション / web.xmlレス)を使う
続いて、Webアプリケーション(アノテーション駆動のServletのみ)を動かしてみます。
必要なMaven依存関係は、こちら。
<dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-webapp</artifactId> <version>9.4.8.v20171121</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-annotations</artifactId> <version>9.4.8.v20171121</version> </dependency>
「jetty-webapp」は、「jetty-annotations」に実は含まれていたりするのですが…。
@WebServletアノテーションを付与したServletを作成します。
src/test/java/org/littlewings/embedded/jetty/HelloServlet.java
package org.littlewings.embedded.jetty; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.getWriter().write("Hello Servlet!!"); } }
Webアプリケーションのルートディレクトリが必要なので、今回は「src/test/webapp」とします。
$ mkdir -p src/test/webapp/WEB-INF
@WebServletなServletを認識させるには、クラスファイルをWEB-INF/classesに配置する必要があるようなので、コンパイル結果を
「src/test/webapp/WEB-INF/classes」に出力するように設定しておきます。
<build> <testOutputDirectory>src/test/webapp/WEB-INF/classes</testOutputDirectory> </build>
なんとなく、テキストファイルも置いてみます。
src/test/webapp/text-file.txt
Hello Jetty!!
src/test/java/org/littlewings/embedded/jetty/SimpleJettyWebappTest.java
package org.littlewings.embedded.jetty; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URI; import java.nio.charset.StandardCharsets; import org.eclipse.jetty.annotations.AnnotationConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.Configuration; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebInfConfiguration; import org.eclipse.jetty.webapp.WebXmlConfiguration; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; public class SimpleJettyWebappTest { @Test public void test() throws Exception { Server server = new Server(8080); WebAppContext webapp = new WebAppContext(); webapp.setContextPath("/"); webapp.setWar("src/test/webapp"); webapp.setConfigurations(new Configuration[]{ new WebXmlConfiguration(), new AnnotationConfiguration(), new WebInfConfiguration() }); server.setHandler(webapp); server.start(); HttpURLConnection conn = (HttpURLConnection) URI.create("http://localhost:8080/hello").toURL().openConnection(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { assertThat(reader.readLine()).isEqualTo("Hello Servlet!!"); } conn.disconnect(); HttpURLConnection connPlainText = (HttpURLConnection) URI.create("http://localhost:8080/text-file.txt").toURL().openConnection(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(connPlainText.getInputStream(), StandardCharsets.UTF_8))) { assertThat(reader.readLine()).isEqualTo("Hello Jetty!!"); } connPlainText.disconnect(); server.stop(); } }
先ほどの最小構成のServletの時から変わったことは、WebAppContextを使用するようになったことですね。
WebAppContext webapp = new WebAppContext(); webapp.setContextPath("/"); webapp.setWar("src/test/webapp"); webapp.setConfigurations(new Configuration[]{ new WebXmlConfiguration(), new AnnotationConfiguration(), new WebInfConfiguration() }); server.setHandler(webapp);
コンテキストパス、WARの位置(今回はディレクトリですが)を指定して、Server#setHandlerで登録します。
あと、Servletを認識させるには、このあたりの設定が必要です。
webapp.setConfigurations(new Configuration[]{ new WebXmlConfiguration(), new AnnotationConfiguration(), new WebInfConfiguration() });
確認。
HttpURLConnection conn = (HttpURLConnection) URI.create("http://localhost:8080/hello").toURL().openConnection(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { assertThat(reader.readLine()).isEqualTo("Hello Servlet!!"); } conn.disconnect(); HttpURLConnection connPlainText = (HttpURLConnection) URI.create("http://localhost:8080/text-file.txt").toURL().openConnection(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(connPlainText.getInputStream(), StandardCharsets.UTF_8))) { assertThat(reader.readLine()).isEqualTo("Hello Jetty!!"); } connPlainText.disconnect();
動いていますよ、と。
Webアプリケーション(Servlet+web.xml)を使う
今度は、web.xmlを使ってちょっとレガシー気味なWebアプリケーション…Servletのみですが…を書いてみます。
Maven依存関係は、「jetty-webapp」のみがあればOKです。
<dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-webapp</artifactId> <version>9.4.8.v20171121</version> </dependency>
また、Webアプリケーションのルートディレクトリは先ほどと変更しませんが、「WEB-INF/classes」へのコンパイル結果出力設定は不要です。
Servletは、シンプルなものに戻します。
src/test/java/org/littlewings/embedded/jetty/HelloServlet.java
package org.littlewings.embedded.jetty; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.getWriter().write("Hello Servlet!!"); } }
これをマッピングするweb.xmlを作成。
src/test/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>hello</servlet-name> <servlet-class>org.littlewings.embedded.jetty.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app>
テキストファイルも置いていますが、内容一緒なので省略…。
src/test/java/org/littlewings/embedded/jetty/SimpleJettyWebappLegacyTest.java
package org.littlewings.embedded.jetty; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URI; import java.nio.charset.StandardCharsets; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppContext; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; public class SimpleJettyWebappLegacyTest { @Test public void test() throws Exception { Server server = new Server(8080); WebAppContext webapp = new WebAppContext(); webapp.setContextPath("/"); webapp.setWar("src/test/webapp"); // webapp.addServlet(HelloServlet.class, "/hello"); server.setHandler(webapp); server.start(); HttpURLConnection conn = (HttpURLConnection) URI.create("http://localhost:8080/hello").toURL().openConnection(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { assertThat(reader.readLine()).isEqualTo("Hello Servlet!!"); } conn.disconnect(); HttpURLConnection connPlainText = (HttpURLConnection) URI.create("http://localhost:8080/text-file.txt").toURL().openConnection(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(connPlainText.getInputStream(), StandardCharsets.UTF_8))) { assertThat(reader.readLine()).isEqualTo("Hello Jetty!!"); } connPlainText.disconnect(); server.stop(); } }
今回は、グッとシンプルになります。Configurationは不要です。
WebAppContext webapp = new WebAppContext(); webapp.setContextPath("/"); webapp.setWar("src/test/webapp"); // webapp.addServlet(HelloServlet.class, "/hello"); server.setHandler(webapp);
web.xmlでServletのマッピングを書かない場合は、コメントアウトしてある箇所のようにServletを個別にWebAppContext#addServletで登録すると良いでしょう。
確認結果は同じなので、割愛。
JSPを使う
JSPを使ってみます。
参考にしたドキュメントは、こちら。これは、WARファイルをデプロイする例ではありますが…。
Embedded Examples / Web Application with JSP
必要なMaven依存関係は、こちら。
<dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-webapp</artifactId> <version>9.4.8.v20171121</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-annotations</artifactId> <version>9.4.8.v20171121</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>apache-jsp</artifactId> <version>9.4.8.v20171121</version> </dependency>
単純なJSPと
src/test/webapp/hello.jsp
Hello JSP!!
EL式を使ったJSPを用意してみます。
src/test/webapp/el.jsp
${5 + 3}
WEB-INFディレクトリは用意しません。
コードは、こちら。
src/test/java/org/littlewings/embedded/jetty/SimpleJettyJspTest.java
package org.littlewings.embedded.jetty; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URI; import java.nio.charset.StandardCharsets; import org.eclipse.jetty.annotations.AnnotationConfiguration; import org.eclipse.jetty.plus.webapp.EnvConfiguration; import org.eclipse.jetty.plus.webapp.PlusConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.webapp.Configuration; import org.eclipse.jetty.webapp.FragmentConfiguration; import org.eclipse.jetty.webapp.JettyWebXmlConfiguration; import org.eclipse.jetty.webapp.MetaInfConfiguration; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebInfConfiguration; import org.eclipse.jetty.webapp.WebXmlConfiguration; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; public class SimpleJettyJspTest { @Test public void test() throws Exception { Server server = new Server(8080); WebAppContext webapp = new WebAppContext(); webapp.setContextPath("/"); webapp.setWar("src/test/webapp"); webapp.setConfigurations(new Configuration[] { new WebXmlConfiguration(), new AnnotationConfiguration(), new WebInfConfiguration() }); server.setHandler(webapp); server.start(); HttpURLConnection connJsp = (HttpURLConnection) URI.create("http://localhost:8080/hello.jsp").toURL().openConnection(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(connJsp.getInputStream(), StandardCharsets.UTF_8))) { assertThat(reader.readLine()).isEqualTo("Hello JSP!!"); } connJsp.disconnect(); HttpURLConnection connEl = (HttpURLConnection) URI.create("http://localhost:8080/el.jsp").toURL().openConnection(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(connEl.getInputStream(), StandardCharsets.UTF_8))) { assertThat(reader.readLine()).isEqualTo("8"); } connEl.disconnect(); server.stop(); } }
Jettyの設定は、web.xmlを省略した場合のコードと同じですね。
このあたりの設定を削ると、今回の構成ではJSPが動かなくなります…。
webapp.setConfigurations(new Configuration[] { new WebXmlConfiguration(), new AnnotationConfiguration(), new WebInfConfiguration() });
別解としては、これでも動くようですが。
webapp.setConfigurations(new Configuration[] { new AnnotationConfiguration(), new WebInfConfiguration() }); ServletHolder jspHolder = new ServletHolder("jsp", JettyJspServlet.class); webapp.addServlet(jspHolder, "*.jsp");
また、JSPはServletにコンパイルされるわけですが、その時に使うディレクトリはデフォルトでは「java.io.tmpdir」配下が使われます。
こんな感じに。
$ ll -d /tmp/jetty* drwxrwxr-x 3 xxxxx xxxxx 4096 2月 17 19:30 /tmp/jetty-0.0.0.0-8080-webapp-_-any-2499022055629159289.dir/ drwxrwxr-x 3 xxxxx xxxxx 4096 2月 17 19:09 /tmp/jetty-0.0.0.0-8080-webapp-_-any-4228262766737794196.dir/ drwxrwxr-x 3 xxxxx xxxxx 4096 2月 17 19:05 /tmp/jetty-0.0.0.0-8080-webapp-_-any-456663689543055411.dir/ drwxrwxr-x 3 xxxxx xxxxx 4096 2月 17 19:29 /tmp/jetty-0.0.0.0-8080-webapp-_-any-7594933357262438255.dir/
このディレクトリは、Server#stop時には削除されます。
ディレクトリを変更する場合は、以下のように設定すればOKです。
webapp.setAttribute("javax.servlet.context.tempdir", new File("..."));
確認。
HttpURLConnection connJsp = (HttpURLConnection) URI.create("http://localhost:8080/hello.jsp").toURL().openConnection(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(connJsp.getInputStream(), StandardCharsets.UTF_8))) { assertThat(reader.readLine()).isEqualTo("Hello JSP!!"); } connJsp.disconnect(); HttpURLConnection connEl = (HttpURLConnection) URI.create("http://localhost:8080/el.jsp").toURL().openConnection(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(connEl.getInputStream(), StandardCharsets.UTF_8))) { assertThat(reader.readLine()).isEqualTo("8"); } connEl.disconnect();
JSP、EL式も含めて動きましたよ、と。
JSTLを使う
最後に、JSTLを使ってみましょう。
必要なMaven依存関係は、こちらになります。
<dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-webapp</artifactId> <version>9.4.8.v20171121</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-annotations</artifactId> <version>9.4.8.v20171121</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>apache-jsp</artifactId> <version>9.4.8.v20171121</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>apache-jstl</artifactId> <version>9.4.8.v20171121</version> </dependency>
簡単なJSTLのカスタムタグを使ったJSPを用意。
src/test/webapp/jstl.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><c:out value="Hello JSTL!!"/>
Java側のコードは、JSPを使う時と変わりません。
src/test/java/org/littlewings/embedded/jetty/SimpleJettyJstlTest.java
package org.littlewings.embedded.jetty; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URI; import java.nio.charset.StandardCharsets; import org.eclipse.jetty.annotations.AnnotationConfiguration; import org.eclipse.jetty.plus.webapp.EnvConfiguration; import org.eclipse.jetty.plus.webapp.PlusConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.webapp.Configuration; import org.eclipse.jetty.webapp.FragmentConfiguration; import org.eclipse.jetty.webapp.JettyWebXmlConfiguration; import org.eclipse.jetty.webapp.MetaInfConfiguration; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebInfConfiguration; import org.eclipse.jetty.webapp.WebXmlConfiguration; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; public class SimpleJettyJstlTest { @Test public void test() throws Exception { Server server = new Server(8080); WebAppContext webapp = new WebAppContext(); webapp.setContextPath("/"); webapp.setWar("src/test/webapp"); webapp.setConfigurations(new Configuration[] { new WebXmlConfiguration(), new AnnotationConfiguration(), new WebInfConfiguration() }); server.setHandler(webapp); server.start(); HttpURLConnection conn = (HttpURLConnection) URI.create("http://localhost:8080/jstl.jsp").toURL().openConnection(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { assertThat(reader.readLine()).isEqualTo("Hello JSTL!!"); } conn.disconnect(); server.stop(); } }
実行時に、JSTLのURIが解決できないとかいうトラブルに遭った場合は、次のような調性を行うことになります。
// Set Classloader of Context to be sane (needed for JSTL) // JSP requires a non-System classloader, this simply wraps the // embedded System classloader in a way that makes it suitable // for JSP to use ClassLoader jspClassLoader = new URLClassLoader(new URL[0], this.getClass().getClassLoader()); webapp.setClassLoader(jspClassLoader); // Set the ContainerIncludeJarPattern so that jetty examines these // container-path jars for tlds, web-fragments etc. // If you omit the jar that contains the jstl .tlds, the jsp engine will // scan for them instead. webapp.setAttribute( "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", ".*/[^/]*servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/[^/]*taglibs.*\\.jar$" );
参考)
Embedded Examples / Web Application with JSP
https://github.com/jetty-project/embedded-jetty-jsp/blob/master/src/main/java/org/eclipse/jetty/demo/Main.java#L194-L199
とりあえず、こんなところで。