Javaの組み込みWebサーバ３つ、Tomcat、Jetty、Undertowのうち、GraalVMで動作したのは？

GraalVMをご存知ですか？Javaをネイティブコンパイルし、ケースによってはメモリ消費を[1/16に押さえてくれる](https://qiita.com/ukiuni@github/items/2999acf26fdc0c3f8353)ツールです。
ただし、GraalVMがコンパイルできるアプリケーションには制約があり、すべてのJavaアプリケーションがネイティブコンパイルできるわけではありません。

今回はネイティブなWebアプリケーションを作成するために、Webサーバを組み込んだJavaアプリケーションがGraalVMでコンパイル、実行できるかを検証してみました。

# 施行
gradle shadowを使ってFat Jarを作成、GraalVMのnative-imageコマンドにて-jarオプションでFat Jarを指定してコンパイルします。

# 環境

```
$ native-image --version
GraalVM Version 1.0.0-rc13
$ java -version
openjdk version "1.8.0_202"
OpenJDK Runtime Environment (build 1.8.0_202-20190206132754.buildslave.jdk8u-src-tar--b08)
OpenJDK GraalVM CE 1.0.0-rc13 (build 25.202-b08-jvmci-0.55, mixed mode)
```

# Tomcat(tomcat-embed-core v9.0.17)
だめでした。

ソース

```java:ソース抜粋
public static void main(String[] args) throws Exception {
	Tomcat tomcat = new Tomcat();
	tomcat.setPort(8080);
	tomcat.getConnector();
	File base = new File("src/main/static/");
	Context context = tomcat.addContext("/app", base.getAbsolutePath());
	Tomcat.addServlet(context, "default", new DefaultServlet()).addMapping("/");

	Tomcat.addServlet(context, "hello", new HttpServlet() {
		protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
			Writer w = resp.getWriter();
			w.write("Hello, World!");
			w.flush();
		}
	}).addMapping("/hello");
	tomcat.start();
	tomcat.getServer().await();
}
```

コンパイルに失敗。

```sh
$ native-image  -jar build/libs/app-all.jar
Build on Server(pid: 2099, port: 50704)
[app-all:2099]    classlist:   1,259.46 ms
[app-all:2099]        (cap):   1,512.61 ms
[app-all:2099]        setup:   7,054.04 ms
[app-all:2099]     analysis:  10,298.20 ms
Error: unsupported features in 2 methods
Detailed message:
Error: Class initialization failed: com.sun.naming.internal.ResourceManager$AppletParameter
Original exception that caused the problem: java.lang.ExceptionInInitializerError
...(省略)
```

残念。

# Jetty(jetty-server v9.4.15.v20190215)
行けました！

```java:ソース抜粋
public static void main(String[] args) throws Exception {
	Server server = new Server(8080);

	server.setHandler(new AbstractHandler() {

		@Override
		public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
			response.setContentType("text/plain");
			response.setStatus(HttpServletResponse.SC_OK);
			PrintWriter out = response.getWriter();
			out.println("Hello World!");
			baseRequest.setHandled(true);
		}
	});

	server.start();
	server.join();
}
```

コンパイル & 実行

```sh
$ native-image -jar build/libs/app-all.jar
Build on Server(pid: 2523, port: 51183)*
[app-all:2523]    classlist:   3,325.81 ms
[app-all:2523]        (cap):   1,309.95 ms
[app-all:2523]        setup:   8,400.08 ms
2019-04-04 22:16:54.214:INFO::ForkJoinPool-2-worker-3: Logging initialized @22713ms to org.eclipse.jetty.util.log.StdErrLog
[app-all:2523]   (typeflow):  11,805.43 ms
[app-all:2523]    (objects):   9,466.98 ms
[app-all:2523]   (features):     354.09 ms
[app-all:2523]     analysis:  37,080.95 ms
[app-all:2523]     universe:     650.05 ms
[app-all:2523]      (parse):   3,276.85 ms
[app-all:2523]     (inline):   6,226.93 ms
[app-all:2523]    (compile):  31,668.53 ms
[app-all:2523]      compile:  57,307.38 ms
[app-all:2523]        image:   2,385.43 ms
[app-all:2523]        write:     859.86 ms
[app-all:2523]      [total]: 145,307.12 ms
$ ./app-all
2019-04-04 22:19:55.501:INFO:oejs.Server:main: jetty-9.4.z-SNAPSHOT; built: 2019-02-15T16:53:49.381Z; git: eb70b240169fcf1abbd86af36482d1c49826fa0b; jvm 1.8.0_202
2019-04-04 22:19:55.502:INFO:oejs.AbstractConnector:main: Started ServerConnector@6b64b5db{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2019-04-04 22:19:55.502:INFO:oejs.Server:main: Started @2ms
```

無事起動！ブラウザでアクセスもできました！
すばらしい。

# Undertow(undertow-core v2.0.19.Final)
だめでした。。。

```java:ソース抜粋
public static void helloWorldHandler(HttpServerExchange exchange) {
	exchange.getResponseHeaders().add(Headers.CONTENT_TYPE, "text/plain");
	exchange.getResponseSender().send("Hello World!");
}

public static void main(String[] args) {
	Undertow server = Undertow.builder().addHttpListener(8080, "0.0.0.0", App::helloWorldHandler).build();
	server.start();
}
```

コンパイル。

```sh
$ native-image -jar build/libs/app-all.jar
Build on Server(pid: 2523, port: 51183)
[app-all:2523]    classlist:   2,361.03 ms
[app-all:2523]        (cap):   1,720.81 ms
[app-all:2523]        setup:   7,458.13 ms
[app-all:2523]     analysis:   6,485.28 ms
Error: com.oracle.graal.pointsto.constraints.UnresolvedElementException: Discovered unresolved type during parsing: org.osgi.framework.FrameworkUtil. To diagnose the issue you can use the --allow-incomplete-classpath option. The missing type is then reported at run time when it is accessed the first time.
```

その後、--allow-incomplete-classpathオプションを付けたりなんだりしたのですが、どうしてもコンパイルできませんでした。残念。

# というわけで正解はJettyでした。
一つでもコンパイルできたものがあったということで、成果ありです。1/16のメモリリソースでのWebアプリ運用の光が見えてきました！
あとは、JPAが動けば、簡単なWebアプリケーションは作れそうですね。QuarkusでJPAが動いているようなので、コンパイルできることを期待しています。