これは、なにをしたくて書いたもの?
- Undertowには、ResourceManagerというリソースを扱うインターフェースがある
- ResourceManagerの実装を使うと、静的ファイルをUndertowで公開できるようだ
というわけで、試してみました。
ResourceManager?
このインターフェースですね。
ドキュメント上でも、少しだけ登場します。
The resource handler is used to serve static resources such as files. This handler takes a ResourceManager instance, that is basically a file system abstraction. Undertow provides file system and class path based resource mangers, as well as a caching resource manager that wraps an existing resource manager to provide in memory caching support.
Handlerとしては、ResourceHandlerを使います。
ResourceManagerには実装がいくつかあり、
- 指定したjava.io.Fileを起点に、ファイルシステムからリソースを取得するもの
- 指定したjava.nio.file.Pathを起点に、ファイルシステムからリソースを取得するもの
- クラスパスからリソースを取得するもの
- ResourceManagerをラップし、キャッシュの機能を提供するもの
といったものが存在します。
お題
今回は、簡単なServlet+ResourceManager(ResourceHandler)を使って、動的コンテンツの公開と静的コンテンツの公開の
両方をやってみましょう。
環境
今回の環境は、こちらです。
$ java -version openjdk version "1.8.0_181" OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-0ubuntu0.18.04.1-b13) OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode) $ mvn -version Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-18T03:33:14+09:00) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 1.8.0_181, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-8-openjdk-amd64/jre Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "4.15.0-34-generic", arch: "amd64", family: "unix"
準備
Maven依存関係は、こちら。
<dependency> <groupId>io.undertow</groupId> <artifactId>undertow-servlet</artifactId> <version>2.0.13.Final</version> </dependency>
undertow-servletのみでいきます。
あと、静的ファイルは用意するのが面倒だったので、Bootstrapをダウンロードしてきて展開。
JavaScritやCSSは、Bootstrapのzipを展開後、ディレクトリをひとつ上に移しています。
$ find src/main/resources -type f | perl -wp -e 's!(.+)/(.+)!$1 $2!' | sort src/main/resources/static bootstrap-4.1.3-dist.zip src/main/resources/static index.html src/main/resources/static/css .DS_Store src/main/resources/static/css bootstrap-grid.css src/main/resources/static/css bootstrap-grid.css.map src/main/resources/static/css bootstrap-grid.min.css src/main/resources/static/css bootstrap-grid.min.css.map src/main/resources/static/css bootstrap-reboot.css src/main/resources/static/css bootstrap-reboot.css.map src/main/resources/static/css bootstrap-reboot.min.css src/main/resources/static/css bootstrap-reboot.min.css.map src/main/resources/static/css bootstrap.css src/main/resources/static/css bootstrap.css.map src/main/resources/static/css bootstrap.min.css src/main/resources/static/css bootstrap.min.css.map src/main/resources/static/js bootstrap.bundle.js src/main/resources/static/js bootstrap.bundle.js.map src/main/resources/static/js bootstrap.bundle.min.js src/main/resources/static/js bootstrap.bundle.min.js.map src/main/resources/static/js bootstrap.js src/main/resources/static/js bootstrap.js.map src/main/resources/static/js bootstrap.min.js src/main/resources/static/js bootstrap.min.js.map
index.htmlは、Getting startedをマネて。
Introduction / Starter template
Bootstrapが提供するJavaScriptやCSSは、ローカルファイルを参照するように記述しています。 src/main/resources/static/index.html
<!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="/static/css/bootstrap.min.css"> <title>Hello, world!</title> </head> <body> <h1>Hello, world!</h1> <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script> <script src="/static/js/bootstrap.min.js"></script> </body> </html>
Servletを用意
動的コンテンツの配信役として、簡単なServletを作成します。 src/main/java/org/littlewings/undertow/HelloServlet.java
package org.littlewings.undertow; 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 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.getWriter().write("Hello Undertow Servlet!!"); } }
本当に、至ってシンプル。
Undertowを起動する
では、このServletとResourceManagerを使って、Undertowを使った起動クラスを作成していきます。
結果は、こちら。 src/main/java/org/littlewings/undertow/Server.java
package org.littlewings.undertow; import java.nio.file.Paths; import javax.servlet.ServletException; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.resource.ClassPathResourceManager; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.handlers.resource.ResourceManager; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; public class Server { public static void main(String... args) throws ServletException { DeploymentInfo deploymentInfo = Servlets .deployment() .setClassLoader(Server.class.getClassLoader()) .setContextPath("/") .setDeploymentName("app") .addServlets( Servlets.servlet(HelloServlet.class).addMapping("/servlet/hello") ); DeploymentManager deploymentManager = Servlets.defaultContainer().addDeployment(deploymentInfo); deploymentManager.deploy(); ResourceManager resourceManager = new PathResourceManager(Paths.get("src/main/resources/static")); HttpHandler handler = Handlers .path(Handlers.redirect("/static")) .addPrefixPath("/static", Handlers.resource(resourceManager)) .addPrefixPath("/app", deploymentManager.start()); Undertow server = Undertow .builder() .addHttpListener(8080, "localhost") .setHandler(handler) .build(); server.start(); } }
Servletの登録と、デプロイ。
DeploymentInfo deploymentInfo = Servlets .deployment() .setClassLoader(Server.class.getClassLoader()) .setContextPath("/") .setDeploymentName("app") .addServlets( Servlets.servlet(HelloServlet.class).addMapping("/servlet/hello") ); DeploymentManager deploymentManager = Servlets.defaultContainer().addDeployment(deploymentInfo); deploymentManager.deploy();
静的ファイルの取得。今回は、java.nio.file.Pathを起点に書いています。
ResourceManager resourceManager = new PathResourceManager(Paths.get("src/main/resources/static"));
で、これらを使ってHttpHandlerを作成。
HttpHandler handler = Handlers .path(Handlers.redirect("/static")) .addPrefixPath("/static", Handlers.resource(resourceManager)) .addPrefixPath("/app", deploymentManager.start());
Handlers.pathではデフォルトのHttpHandlerを「/」以下に割り当てるのですが、これは「/static」へリダイレクトするように設定。
静的コンテンツは「/static」配下、Servletは「/app」配下というパスの割り当てで公開します。
.addPrefixPath("/static", Handlers.resource(resourceManager)) .addPrefixPath("/app", deploymentManager.start());
あとは、このHttpHandlerを使ってUndertowを起動。
Undertow server = Undertow .builder() .addHttpListener(8080, "localhost") .setHandler(handler) .build(); server.start();
確認。
$ curl -i localhost:8080/app/servlet/hello HTTP/1.1 200 OK Connection: keep-alive Content-Length: 24 Date: Sat, 15 Sep 2018 08:26:47 GMT Hello Undertow Servlet!!
静的コンテンツ。
$ curl -i localhost:8080/static/index.html HTTP/1.1 200 OK Connection: keep-alive Last-Modified: Sat, 15 Sep 2018 08:26:45 GMT Content-Length: 928 Content-Type: text/html Accept-Ranges: bytes Date: Sat, 15 Sep 2018 08:27:11 GMT <!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="/static/css/bootstrap.min.css"> <title>Hello, world!</title> </head> <body> <h1>Hello, world!</h1> <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script> <script src="/static/js/bootstrap.min.js"></script> </body> </html> $ curl -i -I localhost:8080/static/js/bootstrap.min.js HTTP/1.1 200 OK Connection: keep-alive Last-Modified: Tue, 24 Jul 2018 01:37:42 GMT Content-Length: 51039 Content-Type: application/javascript Accept-Ranges: bytes Date: Sat, 15 Sep 2018 08:28:14 GMT $ curl -i -I localhost:8080/static/css/bootstrap.min.css HTTP/1.1 200 OK Connection: keep-alive Last-Modified: Tue, 24 Jul 2018 01:37:28 GMT Content-Length: 140936 Content-Type: text/css Accept-Ranges: bytes Date: Sat, 15 Sep 2018 08:28:36 GMT
JavaScriptとCSSは結果が長いので、HEADだけ。
動作していますね。
今回はjava.nio.file.Pathを起点にしたResourceManagerを使いましたが、クラスパスから取得する場合は、こんな感じ。
ResourceManager resourceManager = new ClassPathResourceManager(Server.class.getClassLoader(), "static");
クラスパス上の、あるパスを起点にリソースを取得します。
実行結果は、先ほどと同じなので割愛。
やりたいことは確認できたので、OKです。
DeploymentInfoにResourceManagerを設定する
今回は、HttpHandlerとして個別にResourceHandlerを設定しましたが、ResourceManagerをDeploymentInfoに
含めて設定することも可能なようです。
DeploymentInfo deploymentInfo = Servlets .deployment() .setClassLoader(Server.class.getClassLoader()) .setContextPath("/") .setDeploymentName("app") .setResourceManager(new PathResourceManager(Paths.get("src/main/resources/static"))) .addServlets( Servlets.servlet(HelloServlet.class).addMapping("/servlet/hello") );
Weldのドキュメントを見ていて、気づきました…。
Content-Type?
ResourceManagerを使うにあたり、Content-Typeがちょっと気になるところだったのですが、割としっかり入って
いました。
これ、どうなっているのかなーと思ったのですが、デフォルトである程度登録されているようです。