JBossのサポートエンジニアをしている三浦です。
赤帽エンジニアAdvent Calendar 2018の19日目の記事です。今日は Undertow の HttpHandler について書いてみたいと思います。
Undertow とは
Undertow とは、OSS の Java EE アプリケーションサーバである WildFly (商用サポートのあるEnterprise 版は JBoss EAP 7) に組み込まれている Web サーバー実装です。 なお、以前の JBoss AS7/JBoss EAP 6 までは、JBossWeb という Tomcat をベースにした実装が組み込まれていましたが、WildFly 8/JBoss EAP 7以降から、この Undertow に置き換わっています。
公式サイト にある Undertow の特徴をいくつか抜粋します。
- Java NIOベースのWebサーバー実装で、ブロッキングI/OとノンブロッキングI/Oの両方をサポートする
- Builder API を利用することで Java アプリケーションに簡単に組み込むことができる
- HttpHandler を組み合わせることで、簡単なWebサーバーから Java EE Servlet 4 対応のWebコンテナまで実装できる柔軟性がある
- HTTP/2 対応 (JDK 8 環境でも jetty ALPN jar の boot class path への追加なしでOK)
- Servlet 4.0 対応
HttpHandler とは
Undertow でのリクエスト処理は HttpHandler(以下、ハンドラー)) というもので実装することができ、上の特徴で述べられているように複数のハンドラーを組み合わせるすることでリクエスト処理の機能拡張ができるようになっています。Undertow が提供する多くの機能もハンドラーとして実装されています。
公式ドキュメントの Undertow Handler Authors Guide などに実装方法が解説されています。ただ、こちらは Undertow のドキュメントなので、主に Java アプリケーションへの組み込む場合についての例となっており、WildFly 上で動かしてみる具体的な例はありません。
Enterprise版である JBoss EAP 7 の Development Guide に少し記載はありますし、Undertow 内の実装を参考にすることはできるのですが、まずは、シンプルな実装のハンドラーを作成して、WildFly 15 に設定して動かしてみるまでをやってみましょう。
Undertow HttpHandler を作ってみる
まずは、pom.xml に必要な maven の依存定義です。
シンプルな HttpHandler を実装するだけであれば、必要になるのは io.undertow:undertow-core
のみです。今回の例ではロギングライブラリとして WildFly に組み込まれている jboss-logging
を利用したので、その依存定義も追加していますが、java.util.logging を使うなら不要ですし、他のロギングライブラリを使うのであれば適宜変更してください。どちらも WildFly に含まれているので <scope>
は provided
でOKです。
<dependencies> <dependency> <groupId>io.undertow</groupId> <artifactId>undertow-core</artifactId> <version>${version.io.undertow}</version> <!-- 2.0.15.Final が WildFly 15 に同梱 --> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.logging</groupId> <artifactId>jboss-logging</artifactId> <version>${version.org.jboss.logging}</version> <!-- 3.3.2.Final が WildFly 15 に同梱 --> <scope>provided</scope> </dependency> </dependencies>
ハンドラー実装クラスとして実装する必要があるのは、io.undertow.server.HttpHandler のみです。
public interface HttpHandler { void handleRequest(HttpServerExchange exchange) throws Exception; }
通常、ハンドラーは次のハンドラーに処理をチェインしていくので、以下のように処理を記載した最後に next.handleRequest(exchange)
のように書き、次のハンドラーに処理を渡します。
package org.jboss.example.undertow; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import org.jboss.logging.Logger; public class HelloHandler implements HttpHandler { private final Logger log = Logger.getLogger(HelloHandler.class); private HttpHandler next; public HelloHandler(HttpHandler next) { this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { log.info("handleRequest() is invoked."); // ここにリクエスト処理を記載する next.handleRequest(exchange); } }
リクエスト処理は、HttpServerExchange というリクエスト/レスポンスの情報を格納するオブジェクト経由で処理を行うことができます。
HttpServerExchange には多くの API があるので、すべては紹介できませんが、たとえば、リクエスト・レスポンスのヘッダー情報は HttpServerExchange#getRequestHeaders() / HttpServerExchange#getResponseHeaders() から HeaderMap として取得・操作ができ、HttpServerExchange#getResponseSender() からレスポンスを書き込む Sender が取得できます。
もし、次のハンドラーに処理を渡さず、このハンドラーでレスポンスを返してリクエスト処理を終了するのであれば、以下のように next.handleRequest(exchange)
を呼ばずに終了することもできます。
import ...(上と同じなので省略)... import io.undertow.util.Headers; // 追加 ...(省略)... public void handleRequest(final HttpServerExchange exchange) throws Exception { log.info("handleRequest() is invoked."); log.info("requestPath = " + exchange.getRequestPath()); exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); exchange.getResponseSender().send("Hello, World"); }
ここまでの実装で WildFly で動かしてみましょう。
ハンドラーを WildFly に追加して動かしてみる
アプリケーション内にパッケージングして WEB-INF/jboss-web.xml
に定義して利用する方法もありますが、今回はまだアプリケーションは特にデプロイしていないので、WildFly のカスタムモジュールとして配置して利用したいと思います。
WildFly をダウンロードして、起動します。
$ curl -L https://download.jboss.org/wildfly/15.0.0.Final/wildfly-15.0.0.Final.tar.gz | tar xz $ cd wildfly-15.0.0.Final/ $ ./bin/standalone.sh
wildfly-maven-plugin を使うと、 pom.xml に以下のような定義を追加することで <commands>
に指定したモジュール登録のCLIコマンドが wildfly:execute-commands
で実行できるようになるので追加します。
<build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-maven-plugin</artifactId> <version>${version.wildfly.maven.plugin}</version> <!-- 現時点の最新は 2.0.0.Final --> <executions> </executions> <configuration> <!-- Tells plugin to start in offline mode, to not try to connect to server or start it--> <offline>true</offline> <fork>true</fork> <jboss-home>${wildfly.home.dir}</jboss-home> <!-- 実行するCLIを外だしにすることも可能 --> <!-- <scripts> --> <!-- <script>config.cli</script> --> <!-- </scripts> --> <commands> <command>module add --name=org.jboss.example.undertow --resources=${project.build.directory}${file.separator}${project.artifactId}.jar --dependencies=io.undertow.core,org.jboss.logging,javaee.api,javax.api</command> </commands> </configuration> </plugin> </plugins> </build>
それが終わったら、以下の mvn コマンドで、作成したハンドラーをビルドしてモジュールとして登録します。
$ mvn clean package wildfly:execute-commands -Dwildfly.home.dir=/path/to/wildfly-15.0.0.Final
もちろん、 wildfly:execute-commands
を使わずに、jboss-cli.sh
から直接CLIを実行して登録してもよいです。
$ cd wildfly-15.0.0.Final/ $ ./bin/jboss-cli.sh -c [standalone@localhost:9990 /] module add --name=org.jboss.example.undertow --slot=main --resources=/path/to/undertow-example-handler.jar --dependencies=io.undertow.core,org.jboss.logging,javaee.api,javax.api
以下の2つのCLIコマンドでを実行して、配置したハンドラーを undertow サブシステムの filter として有効化します。
/subsystem=undertow/configuration=filter/custom-filter=hello-handler:add(class-name=org.jboss.example.undertow.HelloHandler, module=org.jboss.example.undertow) /subsystem=undertow/server=default-server/host=default-host/filter-ref=hello-handler:add()
standalone/configuration/standalone.xml
には以下のような定義が追加されます。
<subsystem xmlns="urn:jboss:domain:undertow:8.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other"> <buffer-cache name="default"/> <server name="default-server"> <http-listener name="default" socket-binding="http" redirect-socket="https" enable-http2="true"/> <https-listener name="https" socket-binding="https" security-realm="ApplicationRealm" enable-http2="true"/> <host name="default-host" alias="localhost"> <location name="/" handler="welcome-content"/> <filter-ref name="hello-handler"/> <!-- 追加 --> ... </host> </server> ... <filters> <filter name="hello-handler" class-name="org.jboss.example.undertow.HelloHandler" module="org.jboss.example.undertow"/> <!-- 追加 --> </filters> </subsystem>
これでハンドラーが利用可能になり、リクエストを投げてみると、ハンドラーからレスポンスが帰ってくることが確認できます。
$ curl -v http://localhost:8080 ... > GET / HTTP/1.1 > User-Agent: curl/7.29.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK < Connection: keep-alive < Content-Type: text/plain < Content-Length: 13 < Date: Wed, 19 Dec 2018 10:17:06 GMT < Hello, World!
ハンドラーが動くURIパスを設定で指定する
上の設定例では <filter-ref name="hello-handler"/>
と特にパスなどを指定していないため、どんなパスでリクエストしてもハンドラーがレスポンスを返してきます。
たとえば、URIのパスが /hello
以下でのみ動くようにするには、<filter-ref>
に predicate
属性で path-prefix(/hello)
などのようにフィルターが適用される条件を指定します。predicate
属性に指定できる記法の詳細については Undertow のドキュメントの 「Textual Representation of Predicates」のセクション にありますが、path-prefix
(パスの前方マッチ) 以外にも、path-suffix
(パスの後方マッチ) や regex
(正規表現での指定) や method
(リクエストのメソッド) などを指定できます。and
、or
や not
で複数の条件を指定することもできます。
既存 <filter-ref>
定義の更新は、以下のようなCLIを実行することで反映できます。(新規追加時は :reload
不要ですが、 既存定義の更新なので :reload
が必要です。)
/subsystem=undertow/server=default-server/host=default-host/filter-ref=hello-handler:write-attribute(name=predicate,value="path-prefix(/hello)") :reload
standalone.xml
の undertow サブシステムの設定は以下のように変わります。
<host name="default-host" alias="localhost"> ... <filter-ref name="hello-handler" predicate="path-prefix(/hello)"/> <!-- 変更される --> ... </host>
これで http://localhost:8080/hello
以外ではハンドラーがレスポンスを返すことがなくなりました。
ハンドラーの処理を task スレッド上で動かす
ここで standalone/log/server.log
に出力されるログをみてみると、リクエスト処理は I/O スレッドで動いていることがわかります。
INFO [org.jboss.example.undertow.HelloHandler] (default I/O-5) handleRequest() is invoked. INFO [org.jboss.example.undertow.HelloHandler] (default I/O-5) requestPath = /hello
ノンブロッキングな処理であれば、特に問題ありませんが、時間がかかったりブロックするような重い処理を実行している場合には、I/Oスレッドではなく、task スレッドという別なワーカースレッドプールで処理を行うようにすべきであると Undertow のドキュメントにも記載されています。
task スレッドにディスパッチするのは簡単で、ハンドラー内で以下のような実装を追加すればよいです。
@Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (exchange.isInIoThread()) { exchange.dispatch(this); return; } // ブロッキングな処理を行うコード }
今回の処理は特にブロッキングな処理はないのですが、HelloHandler#handleRequest()
を以下のように書き換えてみます。
@Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (exchange.isInIoThread()) { exchange.dispatch(this); log.info("Running on I/O thread. Let's dispatch to task thread"); return; } log.info("handleRequest() is invoked."); log.info("requestPath = " + exchange.getRequestPath()); exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); exchange.getResponseSender().send("Hello, World"); exchange.endExchange(); }
配置したモジュールは一旦削除してから、新しいモジュールを改めて追加して、再起動します。
$ ./bin/jboss-cli.sh -c [standalone@localhost:9990 /] module remove --name=org.jboss.example.undertow [standalone@localhost:9990 /] module add --name=org.jboss.example.undertow --slot=main --resources=/path/to/undertow-example-handler.jar --dependencies=io.undertow.core,org.jboss.logging,javaee.api,javax.api
これで http://localhost:8080/hello
にアクセスして、ログから HelloHandler が task スレッドで実行されていることがわかります。
INFO [org.jboss.example.undertow.HelloHandler] (default I/O-3) Running on I/O thread. Let's dispatch to task thread INFO [org.jboss.example.undertow.HelloHandler] (default task-1) handleRequest() is invoked. INFO [org.jboss.example.undertow.HelloHandler] (default task-1) requestPath = /hello
ハンドラーに設定パラメータを渡す
また、WildFly 上でハンドラーが定義されているとき、ハンドラーに対応するアクセサーメソッドを用意することで、<filter>
定義のパラメータを渡すこともできます。
設定で message
というパラメータを渡して、それをレスポンスとして返すようにしてみます。HelloHandler を以下のように更新します。
public class HelloHandler implements HttpHandler { private static final String DEFAULT_MESSAGE = "Hello, World!"; private final Logger log = Logger.getLogger(HelloHandler.class); private HttpHandler next; private String message; public HelloHandler(HttpHandler next) { this.message = DEFAULT_MESSAGE; this.next = next; } public HelloHandler(String message, HttpHandler next) { this.message = message; this.next = next; } @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { if (exchange.isInIoThread()) { exchange.dispatch(this); log.info("Running on I/O thread. Let's dispatch to task thread"); return; } log.info("handleRequest() is invoked."); log.info("requestPath = " + exchange.getRequestPath()); exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); exchange.getResponseSender().send(message); exchange.endExchange(); } public void setMessage(String message) { this.message = message; } }
先ほどと同様に、配置したモジュールは一旦削除してから、新しいモジュールを改めて追加して、再起動します。
そして、以下のような CLI で <filter name="hello-handler" ...>
に message
パラメータをセットします。
/subsystem=undertow/configuration=filter/custom-filter=hello-handler:write-attribute(name=parameters.message,value="Hello, WildFly")
standalone.xml
の undertow サブシステムの設定は以下のように変わります。
<filter name="example-handler" class-name="org.jboss.example.undertow.HelloHandler" module="org.jboss.example.undertow"> <param name="message" value="Hello, WildFly"/> <!-- 追加される --> </filter>
これで http://localhost:8080/hello
にアクセスしてハンドラーが返すレスポンスも変わったことが確認できます。
$ curl -v http://localhost:8080/hello ... > GET /hello HTTP/1.1 > User-Agent: curl/7.29.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK < Connection: keep-alive < Content-Type: text/plain < Content-Length: 14 < Date: Wed, 19 Dec 2018 11:30:43 GMT < * Connection #0 to host localhost left intact Hello, WildFly
細かく説明したのですこし長くなりましたが、シンプルな Undertow HttpHandler の作り方と WildFly 15 での設定方法でした。WildFly 15 / Undertow 2.0.x を例に書きましたが、ここで書いた内容は EAP 7.0.x/Undertow 1.3.x や EAP 7.1.x/Undertow 1.4.x でも変わらないと思います。
作ったコード:
Undertow の built-in handler を紹介する
独自 HttpHandler の作り方と設定の仕方はわかったけど、この例だとシンプル過ぎて実用性がないし、あまりうれしさがないですね。
Undertow には built-in Handlers に記載されているように多くのハンドラーが同梱されており、WildFly 上で設定して簡単に利用できるようになっています。
これらの built-in handler は <expression-filter>
に指定可能な短縮名が定義されています。<expression-filter>
の expression
属性で predicates と組み合わせて指定して利用することもできるようになっています。
というわけで、最後に、知っておくと有用そうな built-in handler をいくつか紹介して終わりたいと思います。
Request Dumping Handler
実装クラス | io.undertow.server.handlers.RequestDumpingHandler |
---|---|
短縮名 | dump-request |
指定可能パラメータ | なし |
デフォルトパラメータ | なし |
リクエストとレスポンスのヘッダー情報やリクエストパラメータなどをログにダンプするハンドラーです。ログ出力量が多くなるので、predicate
を指定して出力対象を限定するなどをすることが多いかもしれません。
- 設定するCLIコマンドの例:
/subsystem=undertow/configuration=filter/expression-filter=dump-request-handler:add(expression="dump-request") /subsystem=undertow/server=default-server/host=default-host/filter-ref=dump-request-handler:add
standalone.xml
での設定例:
<subsystem xmlns="urn:jboss:domain:undertow:8.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other"> ... <server name="default-server"> ... <host name="default-host" alias="localhost"> ... <filter-ref name="dump-request-handler"/> <!-- 追加 --> <!-- <filter-ref name="dump-request-handler" predicate="path-prefix(/test)"/> --> ... </host> </server> ... <filters> <expression-filter name="dump-request-handler" expression="dump-request"/> <!-- 追加 --> </filters> </subsystem>
例えば、curl http://localhost:8080/test/?foo=bar -H "TestHeader: test"
のようなリクエストを投げると、以下のようなログが出力されます。
INFO [io.undertow.request.dump] (default task-1) ----------------------------REQUEST--------------------------- URI=/test/ characterEncoding=null contentLength=-1 contentType=null header=Accept=*/* header=TestHeader=test header=User-Agent=curl/7.29.0 header=Host=localhost:8080 locale=[] method=GET parameter=foo=bar protocol=HTTP/1.1 queryString=foo=bar remoteAddr=/127.0.0.1:35140 remoteHost=localhost scheme=http host=localhost:8080 serverPort=8080 isSecure=false --------------------------RESPONSE-------------------------- contentLength=874 contentType=text/html;charset=UTF-8 cookie=JSESSIONID=LMovDERaWI4EtB2MvkmHj6yfXYoLXwADm3m2szaS.node1; domain=null; path=/test header=Connection=keep-alive header=X-Powered-By=JSP/2.3 header=Set-Cookie=JSESSIONID=LMovDERaWI4EtB2MvkmHj6yfXYoLXwADm3m2szaS.node1; path=/test header=Content-Type=text/html;charset=UTF-8 header=Content-Length=874 header=Date=Wed, 19 Dec 2018 12:46:40 GMT status=200 ==============================================================
Blocking Handler
実装クラス | io.undertow.server.handlers.BlockingHandler |
---|---|
短縮名 | blocking |
指定可能パラメータ | なし |
デフォルトパラメータ | なし |
HttpServerExchange をブロッキングモードして、I/Oスレッド上で動いている場合には task スレッドに処理をディスパッチするハンドラーです。
<filters>
に指定されたハンドラーはI/Oスレッド上で動くので、<expression-filter>
にて、この BlockingHandler を各種ハンドラーと組み合わせて利用することで、後続のハンドラーを task スレッド上で稼働させることができます。このハンドラーを単体で利用するユースケースはあまりないと思います。
Stuck Thread Detection Handler
実装クラス | io.undertow.server.handlers.StuckThreadDetectionHandler |
---|---|
短縮名 | stuck-thread-detector |
指定可能パラメータ | threshhold: int (単位は秒) |
デフォルトパラメータ | threshhold |
threshhold
に指定した値(単位は秒)以上に処理に時間がかかっているスレッドをログに出力するハンドラーです。threshhold
のデフォルトは 600 (600秒つまり10分) です。
ServletやEJBアプリケーションなどは task ワーカースレッド上で動くため、このハンドラーで検知するには、前述した BlockingHandler と組み合わせて利用する必要があります。
なお、threshhold
はデフォルトパラメータなので、stuck-thread-detector(5)
と指定しても、stuck-thread-detector(threshhold=5)
と指定しても同じです。
- 設定するCLIコマンドの例:
/subsystem=undertow/configuration=filter/expression-filter=stuck-thread-detector-handler:add(expression="blocking; stuck-thread-detector(5)") /subsystem=undertow/server=default-server/host=default-host/filter-ref=stuck-thread-detector-handler:add
standalone.xml
での設定例:
<subsystem xmlns="urn:jboss:domain:undertow:8.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other"> ... <server name="default-server"> ... <host name="default-host" alias="localhost"> ... <filter-ref name="stuck-thread-detector-handler"/> <!-- 追加 --> <!-- <filter-ref name="dump-request-handler" predicate="path-prefix(/test)"/> --> ... </host> </server> ... <filters> <expression-filter name="stuck-thread-detector-handler" expression="blocking; stuck-thread-detector(5)"/> <!-- 追加 --> </filters> </subsystem>
上の設定例では、threshhold
を5秒に指定しているので5秒以上かかる処理、例えば、10秒スリープするだけのJSPにアクセスすると、5秒超過時に以下のように時間がかかっている処理スタックトレースとともに WARN ログが出力されます。
22:05:27,070 WARN [io.undertow.request] (default I/O-11) UT005072: Thread default task-1 (id=148) has been active for 5007 milliseconds (since Wed Dec 19 22:05:22 JST 2018) to serve the same request for /test/sleep.jsp and may be stuck (configured threshold for this StuckThreadDetectionValve is 5 seconds). There is/are 1 thread(s) in total that are monitored by this Valve and may be stuck.: java.lang.Throwable at java.lang.Thread.sleep(Native Method) at org.apache.jsp.sleep_jsp._jspService(sleep_jsp.java:102) at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70) at javax.servlet.http.HttpServlet.service(HttpServlet.java:791) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:433) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:403) at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:347) at javax.servlet.http.HttpServlet.service(HttpServlet.java:791) at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74) at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) at io.undertow.jsp.JspFileHandler.handleRequest(JspFileHandler.java:32) at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132) at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50) at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292) at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81) at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138) at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135) at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) at org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105) at org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction$$Lambda$661/801187413.call(Unknown Source) at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502) at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction$$Lambda$662/1452439737.call(Unknown Source) at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502) at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction$$Lambda$662/1452439737.call(Unknown Source) at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502) at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction$$Lambda$662/1452439737.call(Unknown Source) at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502) at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction$$Lambda$662/1452439737.call(Unknown Source) at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272) at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:197) at io.undertow.server.handlers.HttpContinueReadHandler.handleRequest(HttpContinueReadHandler.java:65) at io.undertow.server.handlers.PathHandler.handleRequest(PathHandler.java:94) at org.wildfly.extension.undertow.Host$OptionsHandler.handleRequest(Host.java:386) at io.undertow.server.handlers.HttpContinueReadHandler.handleRequest(HttpContinueReadHandler.java:65) at io.undertow.predicate.PredicatesHandler.handleRequest(PredicatesHandler.java:110) at io.undertow.server.handlers.RequestDumpingHandler.handleRequest(RequestDumpingHandler.java:162) at io.undertow.predicate.PredicatesHandler.handleRequest(PredicatesHandler.java:93) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.predicate.PredicatesHandler.handleRequest(PredicatesHandler.java:110) at io.undertow.server.handlers.StuckThreadDetectionHandler.handleRequest(StuckThreadDetectionHandler.java:168) at io.undertow.predicate.PredicatesHandler.handleRequest(PredicatesHandler.java:93) at io.undertow.server.Connectors.executeRootHandler(Connectors.java:360) at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830) at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35) at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1985) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1487) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1349) at java.lang.Thread.run(Thread.java:748)
そして、時間がかかっていた処理が完了した際に、以下のような WARN ログが出力されます。
22:05:33,080 WARN [io.undertow.request] (default I/O-11) UT005073: Thread default task-1 (id=148) was previously reported to be stuck but has completed. It was active for approximately 10694 milliseconds. There is/are still 0 thread(s) that are monitored by this Valve and may be stuck.
Set Attribute Handler
実装クラス | io.undertow.server.handlers.SetAttributeHandler |
---|---|
短縮名 | set |
指定可能パラメータ | attribute: ExchangeAttribute (必須) |
value: ExchangeAttribute (必須) | |
デフォルトパラメータ | なし |
リクエスト/レスポンスの Exchange Attributes の値を変更するハンドラーです。変更できるのは変更可能な Exchange Attributes に限ります。
ドキュメントの「Exchange Attributes」のセクション に記載があるように、リクエストヘッダーが %{i,request_header_name}
、レスポンスヘッダーが %{o,response_header_name}
、クエリパラメータが %{q,query_param_name}
、 Cookie が %{c,cookie_name}
のように指定できます。
リクエストヘッダーの情報は、Servlet filter を利用しても書き換えるといったことはできませんが、このハンドラーを利用することで変更することができます。あまりよい例は浮かびませんが、、問題のある特定のヘッダーやCookieなどをアドホックに書き換えて対応したいという場合などに使えます。
- 設定するCLIコマンドの例:
/subsystem=undertow/configuration=filter/expression-filter=set-attr-filter:add(expression="set(attribute='%{i,ExampleHeader}', value='newvalue')") /subsystem=undertow/server=default-server/host=default-host/filter-ref=set-attr-filter:add
standalone.xml
での設定例:
<subsystem xmlns="urn:jboss:domain:undertow:8.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other"> ... <server name="default-server"> ... <host name="default-host" alias="localhost"> ... <filter-ref name="set-attr-filter"/> <!-- 追加 --> <!-- <filter-ref name="set-attr-filter" predicate="path-prefix(/test)"/> --> ... </host> </server> ... <filters> <expression-filter name="set-attr-filter" expression="set(attribute='%{i,ExampleHeader}', value='newvalue')"/> <!-- 追加 --> </filters> </subsystem>
Clear Handler
実装クラス | io.undertow.server.handlers.SetAttributeHandler |
---|---|
短縮名 | clear |
指定可能パラメータ | attribute: ExchangeAttribute (必須) |
デフォルトパラメータ | attribute |
こちらは実装としては上の SetAttributeHandler
と同じですが、違いは attribute の変更ではなく削除を行うものです。
たとえば、これも極端な例ですが、GET リクエストにおいて通常は必要のない Content-Type ヘッダーが付与されており、その結果としてアプリケーション処理に問題が発生しているというケースに、リクエストの Content-Type ヘッダーを削除したいといったケースなどで利用できます。
なお、attribute
はデフォルトパラメータなので、 clear(attribute='%{i,Content-Type}')
と書いても clear(%{i,Content-Type})
と書いても同じです。
- 設定するCLIコマンドの例:
/subsystem=undertow/configuration=filter/expression-filter=clear-content-type-header-on-get-filter:add(expression="method(GET) and regex(pattern='text/plain.*', value='%{i,Content-Type}') -> clear(attribute='%{i,Content-Type}')") /subsystem=undertow/server=default-server/host=default-host/filter-ref=clear-content-type-header-on-get-filter:add
standalone.xml
での設定例:
<subsystem xmlns="urn:jboss:domain:undertow:8.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other"> ... <server name="default-server"> ... <host name="default-host" alias="localhost"> ... <filter-ref name="clear-content-type-header-on-get-filter"/> <!-- 追加 --> <!-- <filter-ref name="clear-content-type-header-on-get-filter" predicate="path-prefix(/test)"/> --> ... </host> </server> ... <filters> <expression-filter name="clear-content-type-header-on-get-filter" expression="method(GET) and regex(pattern='text/plain.*', value='%{i,Content-Type}') -> clear(attribute='%{i,Content-Type}')"/> <!-- 追加 --> </filters> </subsystem>
Proxy Peer Address Handler
実装クラス | io.undertow.server.handlers.ProxyPeerAddressHandler |
---|---|
短縮名 | proxy-peer-address |
指定可能パラメータ | なし |
デフォルトパラメータ | なし |
前段のロードバランサーやプロキシがリクエストに付与した X-Forwarded-*
ヘッダー (X-Forwarded-For
、X-Forwarded-Proto
、X-Forwarded-Host
、X-Forwarded-Port
) の情報に基づいて、リクエストの情報(Hostヘッダー、スキーム、受付ポートなど)を更新するハンドラーです。
これによってリダイレクト時の Location ヘッダーが送られてきた X-Forwarded-*
ヘッダーに基づいて組み立てられるようになり、適切なURLにリダイレクトされるようになるというものです。 ただ、
設定例などは省略します。
Forwarded Handler
実装クラス | io.undertow.server.handlers.ForwardedHandler |
---|---|
短縮名 | proxy-peer-address |
指定可能パラメータ | なし |
デフォルトパラメータ | なし |
やっていることは ProxyPeerAddressHandler と同じですが、 RFC7239 で定義された Forwarded
ヘッダーに基づいてリクエストの情報(Hostヘッダー、スキーム、受付ポートなど)を更新するハンドラーです。
ロードバランサーやプロキシーが RFC7239 に準拠したヘッダーを送ってくるかどうか次第です。また、通常は ProxyPeerAddressHandler と ForwardedHandler の両方を利用するのではなく、どちらかを利用すればよいでしょう。
ただ、 X-Forwarded-*
ヘッダーおよびForwarded
ヘッダーを意図的に改竄して送ることは簡単にできてしまうので、どちらも前段にロードバランサーやプロキシーがいる場合にのみ利用すべきものです、
設定例などは省略します。
Redirect Handler
実装クラス | io.undertow.server.handlers.RedirectHandler |
---|---|
短縮名 | redirect |
指定可能パラメータ | value: ExchangeAttribute (必須) |
デフォルトパラメータ | value |
指定したURLに 302 Redirect レスポンスを返すハンドラーです。ステータスコードは 302 で固定です。
以下の設定例だと、/test
以外のURIパスへのアクセスだった場合に、http://localhost/test/index.html
に 302 リダイレクトを返すことができます。
- 設定するCLIコマンドの例:
/subsystem=undertow/configuration=filter/expression-filter=redirect-handler:add(expression="redirect(http://localhost:8080/test/)") /subsystem=undertow/server=default-server/host=default-host/filter-ref=redirect-handler:add(predicate="not path-prefix(/test)")
standalone.xml
での設定例:
<subsystem xmlns="urn:jboss:domain:undertow:8.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other"> ... <server name="default-server"> ... <host name="default-host" alias="localhost"> ... <filter-ref name="redirect-handler" predicate="not path-prefix(/test)"/> <!-- 追加 --> ... </host> </server> ... <filters> <expression-filter name="redirect-handler" expression="redirect(http://localhost/test/index.html)"/> <!-- 追加 --> </filters> </subsystem>
Response Code Handler
実装クラス | io.undertow.server.handlers.ResponseCodeHandler |
---|---|
短縮名 | response-code |
指定可能パラメータ | value: int (必須) |
デフォルトパラメータ | value |
指定したレスポンスコードを返すハンドラーです。
例えば、 response-code(403)
と指定すれば 403 Forbidden とともにデフォルトのエラーページが返却されます。
また、RedirectHandler では 302 固定でしたが、この ResponseCodeHandler と前述の SetAttributeHandler を組み合わせて、Location
ヘッダーをセットしてから response-code(301)
と指定することで、301 Moved Permanently
でリダイレクトさせることもできたりします。
CLIは省略しますが、standalone.xml
での設定例です。
<expression-filter name="301-redirect-filter" expression="set(attribute='%{o,Location}', value='http://www.example.com'); response-code(301)"/>
Disable Cache Handler
実装クラス | io.undertow.server.handlers.DisableCacheHandler |
---|---|
短縮名 | disable-cache |
指定可能パラメータ | なし |
デフォルトパラメータ | なし |
レスポンスに以下のヘッダーを付与するハンドラーです。設定例などは省略します。
Cache-Control: no-cache, no-store, must-revalidate Pragma: no-cache Expires: 0