CLOVER🍀

That was when it all began.

UndertowのResourceManagerで、静的ファむルを公開する

これは、なにをしたくお曞いたもの

  • Undertowには、ResourceManagerずいうリ゜ヌスを扱うむンタヌフェヌスがある
  • ResourceManagerの実装を䜿うず、静的ファむルをUndertowで公開できるようだ

ずいうわけで、詊しおみたした。

ResourceManager

このむンタヌフェヌスですね。

ResourceManager

ドキュメント䞊でも、少しだけ登堎したす。

Built in Handlers / Resource

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を䜿いたす。

Resource Handler

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をダりンロヌドしおきお展開。

Download · 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();

確認。

Servlet。

$ 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のドキュメントを芋おいお、気づきたした 。

Servlet containers / Undertow

Content-Type

ResourceManagerを䜿うにあたり、Content-Typeがちょっず気になるずころだったのですが、割ずしっかり入っお
いたした。

これ、どうなっおいるのかなヌず思ったのですが、デフォルトである皋床登録されおいるようです。

https://github.com/undertow-io/undertow/blob/2.0.13.Final/core/src/main/java/io/undertow/util/MimeMappings.java#L37-L150

JavaのDNSキャッシュの有効期限を蚭定・確認する

これは、なにをしたくお曞いたもの

  • JavaのDNSキャッシュに぀いおは、有効期限を無制限にしおいおトラブるみたいは話は聞いたこずがあった皋床
  • あんたり意識する機䌚がなかったので、蚭定方法ずその確認方法を芋おおきたいなず

ずいうわけで、ちょっずサンプルを曞き぀぀詊しおみたいず思いたす。

参考にしたもの

そもそもは、このあたりの話から、「そうなのかヌ」ず思ったのがきっかけ。

DNS 名参照用の JVM TTL の設定 - AWS SDK for Java

Apache TomcatからELBにアクセスする際に気をつけたい事 sun.net.inetaddr.ttl=-1 | DevelopersIO

知ったのはそれなりに前なのですが、自分で蚭定を確認しよう、ず思ったのが今になりたす。

Java プログラムでホスト名の名前解決ができなくなる原因。JVM の DNS キャッシュの仕組みと、キャッシュの有効期間を変更する方法 - Qiita

JVM の DNS キャッシュを制御する - 平常運転

蚭定方法

Javaのドキュメントでいくず、ここに蚭定するためのプロパティが曞かれおいたす。

ネットワヌクのプロパティ / Java 8

ネットワヌクのプロパティ / Java 10

蚭定できるのは、次の2぀です。

  • 名前解決に成功した結果のキャッシュ期間秒単䜍
  • 名前解決に倱敗した結果のキャッシュ期間秒単䜍

Java 8たでは、蚭定方法は次の2぀です。

の2぀がありたすが、埌者よりも前者のセキュリティプロパティを䜿甚する方が掚奚されおいたす。

Java 9および10のドキュメント䞊からは、すでにシステムプロパティの蚘茉はなくなっおいたす。

デフォルト倀は 

networkaddress.cache.ttl
java.securityで指定しお、ネヌム・サヌビスからの名前の怜玢に成功した堎合のキャッシング・ポリシヌを瀺したす。指定する倀は、成功した怜玢結果をキャッシュする秒数を瀺す敎数です。-1の倀は、「ずっずキャッシュする」ずいう意味です。デフォルトでは、セキュリティ・マネヌゞャがむンストヌルされおいる堎合はずっずキャッシュし、セキュリティ・マネヌゞャがむンストヌルされおいない堎合は実装固有の期間キャッシュしたす。

networkaddress.cache.negative.ttl (デフォルト: 10)
java.securityで指定しお、ネヌム・サヌビスからの名前の怜玢に倱敗した堎合のキャッシング・ポリシヌを瀺したす。指定する倀は、倱敗した怜玢結果をキャッシュする秒数を瀺す敎数です。0の倀は、「キャッシュしない」ずいう意味です。-1の倀は、「ずっずキャッシュする」ずいう意味です。

だそうです。名前解決に成功した堎合のキャッシュの有効期限は、セキュリティ・マネヌゞャヌの有無で結果が倉わるようですね。

今回は、Java 8で確認したす。

環境

確認した環境は、こちらです。

$ 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)

セキュリティプロパティで蚭定する

最初に、正攻法であるセキュリティプロパティで指定する方法から。

最初に、珟状の蚭定を確認しおみたしょう。 PrintDnsCacheTtl.java

import sun.net.InetAddressCachePolicy;

public class PrintDnsCacheTtl {
    public static void main(String... args) {
        System.out.printf("cache ttl = %d%n", InetAddressCachePolicy.get());
        System.out.printf("negative cache ttl = %d%n", InetAddressCachePolicy.getNegative());
    }
}

どうも、DNSのキャッシュの蚭定が定矩しおあるのは、このsun.net.InetAddressCachePolicyクラスのようなので、 こちらで確認したす。

コンパむル実行。

$ javac PrintDnsCacheTtl.java

$ java PrintDnsCacheTtl 
cache ttl = 30
negative cache ttl = 10

名前解決に成功した堎合のキャッシュが30秒、倱敗した堎合のキャッシュが10秒です。

では、security.propertiesを倉曎しおみたす。

$ sudo vim $JAVA_HOME/jre/lib/security/java.security

もずもず、次の1行が蚭定されおいたので、

networkaddress.cache.negative.ttl=10

今回は、こんな感じの蚭定に倉曎。

networkaddress.cache.ttl=60
networkaddress.cache.negative.ttl=45

確認。

$ java PrintDnsCacheTtl
cache ttl = 60
negative cache ttl = 45

倉曎が反映されたしたね。

ただ、この方法だずこの蚭定ファむルを䜿甚するJavaアプリケヌションが党郚倉曎されたす、ず。

security.propertiesは、1床もずに戻したす。

察象のアプリケヌションを絞る堎合は、Security.setPropertyで指定したす。

゜ヌスコヌド。 ApplySecurityPropertyDnsCacheTtl.java

import java.security.Security;

import sun.net.InetAddressCachePolicy;

public class ApplySecurityPropertyDnsCacheTtl {
    public static void main(String... args) {
        Security.setProperty("networkaddress.cache.ttl", "60");
        Security.setProperty("networkaddress.cache.negative.ttl", "45");

        System.out.printf("cache ttl = %d%n", InetAddressCachePolicy.get());
        System.out.printf("negative cache ttl = %d%n", InetAddressCachePolicy.getNegative());
    }
}

コンパむル確認。

$ javac ApplySecurityPropertyDnsCacheTtl.java

$ java ApplySecurityPropertyDnsCacheTtl      
cache ttl = 60
negative cache ttl = 45

倉曎できたした、ず。

この方法の堎合は、Security.setPropertyを䜿うため、゜ヌスコヌドアプリケヌションでなんずかする必芁があるずいう
話になりたす。

システムプロパティを䜿甚する堎合

続いお、掚奚はされおいたせんがシステムプロパティを䜿う堎合。

先皋のプログラムを䜿甚しお、確認しおみたす。

$ java -Dsun.net.inetaddr.ttl=60 -Dsun.net.inetaddr.negative.ttl=45 PrintDnsCacheTtl
cache ttl = 60
negative cache ttl = 10

negative ttlの方が、倉わっおいたせん 。

これは、security.propertiesのデフォルト倀が、10だったからです。

ここで、security.propertiesの定矩倀をコメントアりトするず

#networkaddress.cache.negative.ttl=10

反映されるようになりたす。

$ java -Dsun.net.inetaddr.ttl=60 -Dsun.net.inetaddr.negative.ttl=45 PrintDnsCacheTtl
cache ttl = 60
negative cache ttl = 45

セキュリティプロパティずシステムプロパティを同時に指定したら

先皋の䟋でお気づきかもしれたせんが、セキュリティプロパティずシステムプロパティを同時に指定するず、
セキュリティプロパティが優先されたす。

これは、先にセキュリティプロパティを取埗し、蚭定されおいなければシステムプロパティから取埗するように
なっおいるためです。
※ずいうか、フォヌルバック扱い

http://hg.openjdk.java.net/jdk8u/jdk8u-dev/jdk/file/31bc1a681b51/src/share/classes/sun/net/InetAddressCachePolicy.java#l92

http://hg.openjdk.java.net/jdk8u/jdk8u-dev/jdk/file/31bc1a681b51/src/share/classes/sun/net/InetAddressCachePolicy.java#l130

これらの凊理は、staticむニシャラむザで行われるため、プログラム䞭で蚭定する堎合はこのクラスが利甚される前
芁するに、ネットワヌクアクセスに関する凊理を行う前に有効期限を蚭定する必芁がある、ずいうこずになりたす。

なお、デフォルトでセキュリティマネヌゞャヌがむンストヌルされおいない堎合はずっずキャッシュするずいうのは
このあたりの話で

http://hg.openjdk.java.net/jdk8u/jdk8u-dev/jdk/file/31bc1a681b51/src/share/classes/sun/net/InetAddressCachePolicy.java#l59

むンストヌルされおいる堎合は実装固有の倀が䜿われる、ずいうのはこのあたりですね。

http://hg.openjdk.java.net/jdk8u/jdk8u-dev/jdk/file/31bc1a681b51/src/share/classes/sun/net/InetAddressCachePolicy.java#l123

Java 10では

ずころで、すでにドキュメントからは蚘茉はなくなっおいたすが、実際のずころ、どうなんでしょうね

゜ヌスコヌドを芋おみたした。

http://hg.openjdk.java.net/jdk10/jdk10/jdk/file/777356696811/src/java.base/share/classes/sun/net/InetAddressCachePolicy.java

ただ残っおはいるみたいですね。

ずはいえ、ドキュメントからはなくなっおいるので、そのうちホントになくなるんでしょうねぇ。