ããã¯ããªã«ãããããŠæžãããã®ïŒ
åã«ãJakarta RESTful Web Servicesã§Server-Sent Eventsã詊ããŠã¿ãŸããã
WildFly 35(RESTEasy)でServer-Sent Events(SSE)を試す - CLOVER🍀
ãã®æã«RESTEasyã®å®è£ ãèŠãŠã¿ããšãJakarta ServletïŒä»¥éServletïŒã®éåæåŠçã䜿ã£ãŠããããšãããããŸããã
ããããã°Servletã®éåæåŠçã䜿ã£ãããšããªãã£ããªãšæã£ãã®ã§ã詊ããŠã¿ãããšã«ããŸããã
ä»åã¯WildFly 35.0.1.FinalãšApache Tomcat 10.1.36ã§ç¢ºèªããŠã¿ãããšæããŸãã
Jakarta Servletã®éåæåŠç
Jakarta Servletã®éåæåŠçã«é¢ããå 容ã¯ããã®ãããã«æžãããŠããŸãã
- Jakarta Servlet Specification / The Servlet Interface / Servlet Life Cycle / Request Handling / Asynchronous processing
- Jakarta Servlet Specification / The Request / Lifetime of the Request Object
- Jakarta Servlet Specification / The Response / Lifetime of the Response Object
- Jakarta Servlet Specification / Filtering / Main Concepts / Filters and the RequestDispatcher
- Jakarta Servlet Specification / Dispatching Requests
- Jakarta Servlet Specification / Web Applications / Error Handling
- Jakarta Servlet Specification / Application Lifecycle Events
å°ããã€èŠãŠãããŸãããã
ãŸãã¯ãã¡ããããJakarta Servletã®ä»æ§æžã§ã¯ãããã§åããŠéåæåŠçãç»å ŽããŸããæèã¯ãªã¯ãšã¹ãã®åŠçã§ããã
éåæåŠçã䜿ããšããªã¯ãšã¹ããæ±ã£ãã¹ã¬ãããã³ã³ããã«æ»ããå¥ã®ã¹ã¬ããã§ã¬ã¹ãã³ã¹ãçæããããšãã§ããŸãã
éåæåŠçã§ã¯ãAsyncContext#completeãåŒã³åºããAsyncContext#dispatchã䜿ã£ãŠãã£ã¹ãããããŸãã
The asynchronous processing of requests is introduced to allow the thread to return to the container and perform other tasks. When asynchronous processing begins on the request, another thread or callback may either generate the response and call complete or dispatch the request so that it may run in the context of the container using the AsyncContext.dispatch method.
éåæåŠçã§ã®ã·ãŒã±ã³ã¹ã¯ä»¥äžã«ãªããŸãã
- ãªã¯ãšã¹ããåãä»ãããã£ã«ã¿ãŒãªã©ãéããåŸã«Servletã«æž¡ããã
- Servletã¯ãªã¯ãšã¹ãã®ãã©ã¡ãŒã¿ãŒãã³ã³ãã³ããåŠçããŠãªã¯ãšã¹ãã®æ§è³ªã倿ãã
- Servletã¯ãªãœãŒã¹ãŸãã¯ããŒã¿ãèŠæ±ãã
- ããšãã°ãªã¢ãŒãã®WebãµãŒãã¹ã®åŒã³åºããJDBCæ¥ç¶ãåŸ æ©ãããã¥ãŒãªã©
- Servletã¯ã¬ã¹ãã³ã¹ãçæããã«ã¡ãœãããçµäºãã
- 3.ã§èŠæ±ããããªãœãŒã¹ãå©çšå¯èœã«ãªããšããã®ã€ãã³ããåŠçããã¹ã¬ããã¯åãã¹ã¬ããããŸãã¯
AsyncContextã䜿ã£ãŠã³ã³ãããžãã£ã¹ããããã
éåæåŠçãæ±ãã«ã¯ããã£ã«ã¿ãŒãServletã®ã¢ãããŒã·ã§ã³ã®asyncSupported屿§ãŸãã¯web.xmlã®async-supportedã
trueã«ãªã£ãŠããå¿
èŠããããŸããããã©ã«ãå€ã¯falseã§ããtrueã«ãªã£ãŠããªãå Žåã¯ãéåæåŠçãéå§ã§ããŸããã
asyncSupported=trueã®ServletããasyncSupported=falseã®Servletãžã®ãã£ã¹ãããã¯èš±å¯ãããŸããããã®å Žåã¯
éåæããµããŒãããªãServletã®ã¡ãœãããçµäºããæç¹ã§ã¬ã¹ãã³ã¹ã¯ã³ããããããããšã«ãªããŸãã
éåæåŠçã®ã©ã€ããµã€ã¯ã«ã¯ãã¡ãã®å³ã«æžãããŠããŸãã
ServletRequest#startAsyncãåŒã³åºãããšã§ãéåæåŠçãéå§ãç¶æ ã¯AsyncStartedã«ãªãAsyncContext#dispatchãåŒã³åºãããšã§ãå¥ã®Servletã«ãã£ã¹ãããã§ãã- ãã ãã1åã®éåæãµã€ã¯ã«ïŒ
ServletRequest#startAsyncã®åŒã³åºãïŒããšã«ãã£ã¹ãããã§ããåæ°ã¯æå€§1åã®æš¡æ§
- ãã ãã1åã®éåæãµã€ã¯ã«ïŒ
AsyncContext#completeãåŒã³åºãããšã§ãç¶æ ã¯CompletedïŒãŸãã¯CompletingâCompletedïŒã«ãªãã¬ã¹ãã³ã¹ã¯ã³ãããããã

éåæåŠçã¯ãServletRequest#startAsyncãåŒã³åºãããšã§ååŸã§ããAsyncContextãäžå¿ã«æäœããŸãã
AsyncContext (Jakarta Servlet API documentation)
ãŸããéåæåŠçã®ã€ãã³ãã«å¯ŸããŠãªã¹ããŒïŒAsyncListenerïŒãèšå®ããããšãã§ããŸãã
AsyncListener (Jakarta Servlet API documentation)
éåæåŠçãéå§ãããã©ããã¯ãServletRequest#isAsyncStartedã§å€æã§ããããã§ãã
ServletRequest (Jakarta Servlet API documentation)
ãã¡ãã¯ããªã¯ãšã¹ãã衚ããªããžã§ã¯ãã®ã©ã€ããµã€ã¯ã«ã«ã€ããŠæžãããŠããŸãã
Jakarta Servlet Specification / The Request / Lifetime of the Request Object
ãªã¯ãšã¹ããªããžã§ã¯ãã¯éåžžServletã®serviceã¡ãœããããŸãã¯ãã£ã«ã¿ãŒã®doFilterã®éã§æå¹ã§ãã
ServletRequest#startAsyncã¡ãœãããåŒã³åºãéåæåŠçãéå§ããå Žåã¯ãAsyncContext#comleteãåŒã³åºããŸã§
æå¹ã«ãªããŸãã
ãã¡ãã«ã¯ã¬ã¹ãã³ã¹ã衚ããªããžã§ã¯ãã®ã©ã€ããµã€ã¯ã«ã«ã€ããŠæžãããŠããŸããã¬ã¹ãã³ã¹ã¯ä»¥äžã®å Žåã«
ã¯ããŒãºãããŸãã
- Servletã®
serviceã¡ãœããã®çµäº setContentLengthãŸãã¯setContentLengthLongã§æå®ããããŒããã倧ããã³ã³ãã³ããã¬ã¹ãã³ã¹ã«æžã蟌ãŸããsendErrorã¡ãœãããåŒã³åºãããsendRedirectã¡ãœãããåŒã³åºãããAsyncContext#completeãåŒã³åºããã
Jakarta Servlet Specification / The Response / Lifetime of the Response Object
éåæåŠçãæ±ã£ãŠããå Žåã¯ãAsyncContext#completeãåŒã³åºããšãªã¯ãšã¹ããã¬ã¹ãã³ã¹ãã¯ããŒãºãããããš
èŠããŠããã°ããããã§ããã
Jakarta Servlet Specification / Filtering / Main Concepts / Filters and the RequestDispatcher
ãã¡ãã¯ãªã¯ãšã¹ãã®ãã£ã¹ãããã®è©±ã«ã€ããŠãéåæåŠçã䜿ã£ãŠããå Žåã¯ãAsycContext#dispatchã§ãªã¯ãšã¹ãã
ãã£ã¹ãããã§ããŸãã
Jakarta Servlet Specification / Dispatching Requests
AsyncContext#dispatchã«ã¯åŒæ°ã«ãã¹ãåããã®ãšåããªããã®ããããŸãããã¹ãæå®ããå Žåã¯ServletContextããã®
çžå¯Ÿãã¹ã«ãªã/ããæå®ããŸãããã¹ãæå®ããªãå Žåã¯ãããšããšã®ãªã¯ãšã¹ããš
åããã¹ïŒHttpServletRequest#getRequestURIïŒã«å¯ŸããŠãã£ã¹ããããããŸãã
AsyncContext#completeãåŒã³åºããåŸã¯ãAsyncContext#dispatchãåŒã³åºãããšã¯ã§ããŸãããIllegalStateExceptionã
ã¹ããŒãããŸãã
AsyncContext#dispatchã䜿çšããŠåŒã³åºããããµãŒãã¬ããã¯ãAsyncContextã®è»¢éå
ã«ãªã£ããã©ã¡ãŒã¿ãŒã«ä»¥äžã®
ãªã¯ãšã¹ãã®å±æ§ïŒServletRequest#getAttributeïŒã§ã¢ã¯ã»ã¹ã§ããŸãã
- jakarta.servlet.async.mapping
- jakarta.servlet.async.request_uri
- jakarta.servlet.async.context_path
- jakarta.servlet.async.servlet_path
- jakarta.servlet.async.path_info
- jakarta.servlet.async.query_string
ããã¯AsyncContextã®å®æ°ãšããŠå®çŸ©ãããŠããŸããã
AsyncContext (Jakarta Servlet API documentation)
ãšã©ãŒãã³ããªã³ã°ã«ã€ããŠã
Jakarta Servlet Specification / Web Applications / Error Handling
Jakarta Servletã§ã¯ãšã©ãŒãçºçããæã«ã©ã®ãããªããŒãžã衚瀺ããããšãããšã©ãŒããŒãžãšããä»çµã¿ããããŸããã
éåæåŠçãæ±ã£ãŠããå Žåã¯ã¢ããªã±ãŒã·ã§ã³ã®è²¬ä»»ã§ãšã©ãŒããã³ããªã³ã°ããªããã°ãªããªãããšãæèšãããŠããŸãã
If the application is using asynchronous operations as described in Section 2.3.3.3, âAsynchronous processingâ, it is the applicationâs responsibility to handle all errors in application created threads. The container MAY take care of the errors from the thread issued via AsyncContext.start.
ãªã¯ãšã¹ãããã£ã¹ãããããå Žåã¯è©±ãå€ããããã§ãã
For handling errors that occur during AsyncContext.dispatch see dispatch error handling.
ã©ããåç §ããããšãããšãéåæåŠçãç»å Žããæåã®ã»ã¯ã·ã§ã³ã«æ»ã£ãŠããŸãã
Any errors or exceptions that may occur during the execution of the dispatch methods MUST be caught and handled by the container as follows:
éåæåŠçããã£ããããããå ã§ãšã©ãŒãçºçããå Žåã¯ãã³ã³ããã¯ä»¥äžã®åŠçãè¡ãããã§ãã
- ç»é²ããã
AsyncListenerã®onErrorã¡ãœãããåŒã³åºã AsyncContext#completeãAsyncContext#dispatchãåŒã³åºãããªãã£ãå Žåã¯ãã¹ããŒã¿ã¹ã³ãŒããHttpServletResponse.SC_INTERNAL_SERVER_ERRORãšãªããšã©ãŒãã£ã¹ããããå®è¡ããçºçããThrowableããªã¯ãšã¹ãã®å±æ§ã«RequestDispatcher.ERROR_EXCEPTIONãšããŠèšå®ãã- ã¹ããŒã¿ã¹ã³ãŒããäŸå€ã«å¯Ÿå¿ãããšã©ãŒããŒãžãèŠã€ãããªãå ŽåããŸãã¯
AsynContext#completeãAsyncContext#dispatchãåŒã³åºãããŠããªãå Žåã¯ãAsyncContext#completeãåŒã³åºã
æåŸã¯ã¢ããªã±ãŒã·ã§ã³ã©ã€ããµã€ã¯ã«ã€ãã³ãã§ãããªã¹ããŒã«é¢ãã話ã§ããã
Jakarta Servlet Specification / Application Lifecycle Events
ããã§ã¯AsyncListenerã®ç޹ä»ã«çããããŠããŸãã
AsyncListener (Jakarta Servlet API documentation)
ãã¡ããèŠããšãAsyncListenerã¯éåæåŠçã®éå§æããšã©ãŒæãã¿ã€ã ã¢ãŠãæãå®äºæã®4ã€ã®ã€ãã³ããæ±ããããã§ãã
Jakarta Servletã®ä»æ§æžã«æžãããŠããéåæåŠçã«é¢ããå 容ã¯ããããªãšããã§ããã
ããšã¯Jakarta EEã®ãã¥ãŒããªã¢ã«ãèªããšãããããããŸããã
Jakarta Servlet / Asynchronous Processing
ãšããããã§ãããã¥ã¡ã³ããèªãã®ã¯ãããããã«ããŠãŸãã¯ç°¡åã«äœ¿ã£ãŠã¿ãŸãããã
ç°å¢
ä»åã®ç°å¢ã¯ãã¡ãã
$ java --version openjdk 21.0.6 2025-01-21 OpenJDK Runtime Environment (build 21.0.6+7-Ubuntu-124.04.1) OpenJDK 64-Bit Server VM (build 21.0.6+7-Ubuntu-124.04.1, mixed mode, sharing) $ mvn --version Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 21.0.6, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "6.8.0-53-generic", arch: "amd64", family: "unix"
WildFlyã¯35.0.1.FinalãApache Tomcatã¯10.1.36ã䜿ããŸãã
æºå
ãŸãã¯Mavenãããžã§ã¯ãã®æºåãããŸãã
WildFlyãšApache Tomcatã®äž¡æ¹ã§åããã®ã§ãpom.xmlã¯ãããªæãã«ããŸããã
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.littlewings</groupId> <artifactId>servlet-async-example</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.release>21</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>jakarta.platform</groupId> <artifactId>jakarta.jakartaee-bom</artifactId> <version>10.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.16</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>2.0.16</version> <scope>runtime</scope> </dependency> </dependencies> <build> <finalName>ROOT</finalName> </build> <profiles> <profile> <id>wildfly</id> <activation> <activeByDefault>true</activeByDefault> </activation> <build> <plugins> <plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-maven-plugin</artifactId> <version>5.1.2.Final</version> <executions> <execution> <id>package</id> <goals> <goal>package</goal> </goals> </execution> </executions> <configuration> <overwrite-provisioned-server>true</overwrite-provisioned-server> <discover-provisioning-info> <version>35.0.1.Final</version> <layers-for-jndi> <layer>ee-concurrency</layer> </layers-for-jndi> </discover-provisioning-info> </configuration> </plugin> </plugins> </build> </profile> <profile> <id>tomcat</id> <build> <plugins> <plugin> <groupId>org.codehaus.cargo</groupId> <artifactId>cargo-maven3-plugin</artifactId> <version>1.10.17</version> <configuration> <container> <containerId>tomcat10x</containerId> <zipUrlInstaller> <url>https://archive.apache.org/dist/tomcat/tomcat-10/v10.1.36/bin/apache-tomcat-10.1.36.tar.gz</url> </zipUrlInstaller> </container> </configuration> </plugin> </plugins> </build> </profile> </profiles> </project>
WildFlyã¯WildFly Maven Pluginã䜿ã£ãŠããããžã§ãã³ã°ãApache Tomcatã¯Codehaus Cargo Maven 3 Pluginã䜿ã£ãŠ
ããŠã³ããŒãããããšã«ããŸãã
ããã©ã«ãã¯WildFlyã§ãApache Tomcatã䜿ãæã¯-P tomcatã§ãããã¡ã€ã«ãåãæ¿ããŸãã
WildFlyã¯ä»¥äžã®ã³ãã³ãã§ããããžã§ãã³ã°ãšèµ·åãè¡ããŸãã
$ mvn wildfly:run
Apache Tomcatã¯ä»¥äžã®ã³ãã³ãã§ããã±ãŒãžã³ã°ãšèµ·åãè¡ããŸãã
$ mvn -P tomcat package cargo:run
Jakarta Servletã®éåæåŠçã䜿ã£ãŠã¿ã
ããããå ã¯ãJakarta Servletã®éåæåŠçãåºæ¬çãªäœ¿ãæ¹ãã€ããã€ãã®ããªãšãŒã·ã§ã³ã§è©ŠããŠã¿ãããšæããŸãã
ããªãšãŒã·ã§ã³ãšããã®ã¯ããéåæåŠçãæå¹ã«ããªããšåäœããªãããå«ã¿ãŸãã
éåžžã®Servletã§éåæåŠçã®APIãåŒã³åºã
ãŸãã¯ãéåžžã®Servletã§éåæåŠçã䜿ã£ãŠã¿ãŸãã
src/main/java/org/littlewings/servlet/async/SimpleServlet.java
package org.littlewings.servlet.async; import java.io.IOException; import java.io.PrintWriter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.TimeUnit; import jakarta.servlet.AsyncContext; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // asyncSupported = true ããªãã®ã§ããã®Servlet㯠ServletRequest#startAsync ã®åŒã³åºãã§å€±æãã @WebServlet(urlPatterns = "/sync/simple") public class SimpleServlet extends HttpServlet { private Logger logger = LoggerFactory.getLogger(SimpleServlet.class); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { AsyncContext asyncContext = request.startAsync(request, response); asyncContext.start(() -> { try { TimeUnit.SECONDS.sleep(1L); HttpServletResponse res = (HttpServletResponse) asyncContext.getResponse(); PrintWriter writer = res.getWriter(); for (int i = 0; i < 5; i++) { int counter = i + 1; logger.info("[{}] in async{}", Thread.currentThread().getName(), counter); writer.printf( "%s [%s] in async%d%n", LocalDateTime.now().format(DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")), Thread.currentThread().getName(), counter ); writer.flush(); TimeUnit.SECONDS.sleep(1L); } } catch (IOException | InterruptedException e) { throw new RuntimeException(e); } finally { logger.info("[{}] complete async", Thread.currentThread().getName()); asyncContext.complete(); } }); logger.info("[{}] dispatch async", Thread.currentThread().getName()); // HttpServletResponseãæäœããŠããã®ã§è¯ããªã response.getWriter().printf( "%s [%s] dispatch async%n", LocalDateTime.now().format(DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")), Thread.currentThread().getName() ); response.getWriter().flush(); } }
ã³ã¡ã³ãã«ãæžããŠããŸããããã®Servletã¯å®è¡ãããšéåæåŠçã®APIã䜿ã£ãŠããã«ãé¢ããã
@WebServletã¢ãããŒã·ã§ã³ã®asyncSupported屿§ãtrueã«ããŠããªãã®ã§å®è¡ã§ããŸããã
// asyncSupported = true ããªãã®ã§ããã®Servlet㯠ServletRequest#startAsync ã®åŒã³åºãã§å€±æãã @WebServlet(urlPatterns = "/sync/simple") public class SimpleServlet extends HttpServlet {
éåæåŠçã®APIã¯ãã®åŸã§ã䜿ãã®ã§ãããä»åã¯ããããã€ã³ãã§ããã
AsyncContext asyncContext = request.startAsync(request, response);
ã§ã¯ãå®è¡ãããšã©ããªãã確èªããŠã¿ãŸãããïŒWildFlyããã³Apache Tomcatã®èµ·åã³ãã³ãã¯çç¥ããŸãïŒã
$ curl localhost:8080/sync/simple
çµæã¯ãã©ã¡ããHTTPã¹ããŒã¿ã¹ã³ãŒã500ã«ãªããäŸå€ãæããŠå€±æããŸãã
WildFlyã®ãã°ã
11:07:49,392 ERROR [io.undertow.request] (default task-1) UT005023: Exception handling request to /sync/simple: java.lang.IllegalStateException: UT010026: Async is not supported for this request, as not all filters or Servlets were marked as supporting async
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.spec.HttpServletRequestImpl.startAsync(HttpServletRequestImpl.java:1096)
at deployment.ROOT.war//org.littlewings.servlet.async.SimpleServlet.doGet(SimpleServlet.java:25)
at jakarta.servlet.api@6.0.0//jakarta.servlet.http.HttpServlet.service(HttpServlet.java:527)
at jakarta.servlet.api@6.0.0//jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at org.wildfly.security.elytron-web.undertow-server@4.1.0.Final//org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler.lambda$handleRequest$1(ElytronRunAsHandler.java:68)
at org.wildfly.security.elytron-base@2.6.0.Final//org.wildfly.security.auth.server.FlexibleIdentityAssociation.runAsFunctionEx(FlexibleIdentityAssociation.java:103)
at org.wildfly.security.elytron-base@2.6.0.Final//org.wildfly.security.auth.server.Scoped.runAsFunctionEx(Scoped.java:161)
at org.wildfly.security.elytron-base@2.6.0.Final//org.wildfly.security.auth.server.Scoped.runAs(Scoped.java:73)
at org.wildfly.security.elytron-web.undertow-server@4.1.0.Final//org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler.handleRequest(ElytronRunAsHandler.java:67)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
at io.undertow.core@2.3.18.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.core@2.3.18.Final//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
at io.undertow.core@2.3.18.Final//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
at org.wildfly.security.elytron-web.undertow-server-servlet@4.1.0.Final//org.wildfly.elytron.web.undertow.server.servlet.CleanUpHandler.handleRequest(CleanUpHandler.java:38)
at io.undertow.core@2.3.18.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at org.wildfly.extension.undertow@35.0.1.Final//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:44)
at io.undertow.core@2.3.18.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at org.wildfly.extension.undertow@35.0.1.Final//org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:51)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52)
at io.undertow.core@2.3.18.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:276)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:132)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
at org.wildfly.extension.undertow@35.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1421)
at org.wildfly.extension.undertow@35.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1421)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:256)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:101)
at io.undertow.core@2.3.18.Final//io.undertow.server.Connectors.executeRootHandler(Connectors.java:395)
at io.undertow.core@2.3.18.Final//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:861)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1348)
at org.jboss.xnio@3.8.16.Final//org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282)
at java.base/java.lang.Thread.run(Thread.java:1583)
[INFO] 2æ 24, 2025 11:09:30 åå org.apache.catalina.connector.Request startAsync [INFO] èŠå: åŠçãã§ãŒã³å ã®æ¬¡ã®ã¯ã©ã¹ãéåæããµããŒãããŠããªããããéåæãéå§ã§ããŸãã [org.littlewings.servlet.async.SimpleServlet] [INFO] java.lang.IllegalStateException: çŸåšã®ãã§ãŒã³ã®ãã£ã«ã¿ãŸãã¯ãµãŒãã¬ããã¯éåææäœããµããŒãããŠããŸããã [INFO] at org.apache.catalina.connector.Request.startAsync(Request.java:1510) [INFO] at org.apache.catalina.connector.RequestFacade.startAsync(RequestFacade.java:720) [INFO] at org.littlewings.servlet.async.SimpleServlet.doGet(SimpleServlet.java:25) [INFO] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) [INFO] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) [INFO] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) [INFO] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) [INFO] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) [INFO] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) [INFO] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) [INFO] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) [INFO] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) [INFO] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) [INFO] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) [INFO] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) [INFO] at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:663) [INFO] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [INFO] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) [INFO] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397) [INFO] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) [INFO] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905) [INFO] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743) [INFO] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) [INFO] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190) [INFO] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) [INFO] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) [INFO] at java.base/java.lang.Thread.run(Thread.java:1583) [INFO] [INFO] 2æ 24, 2025 11:09:30 åå org.apache.catalina.core.StandardWrapperValve invoke [INFO] é倧: ãµãŒãã¬ãã [org.littlewings.servlet.async.SimpleServlet] ã®Servlet.service()ãäŸå€ãæããŸãã [INFO] java.lang.IllegalStateException: çŸåšã®ãã§ãŒã³ã®ãã£ã«ã¿ãŸãã¯ãµãŒãã¬ããã¯éåææäœããµããŒãããŠããŸããã [INFO] at org.apache.catalina.connector.Request.startAsync(Request.java:1510) [INFO] at org.apache.catalina.connector.RequestFacade.startAsync(RequestFacade.java:720) [INFO] at org.littlewings.servlet.async.SimpleServlet.doGet(SimpleServlet.java:25) [INFO] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) [INFO] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) [INFO] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) [INFO] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) [INFO] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) [INFO] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) [INFO] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) [INFO] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) [INFO] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) [INFO] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) [INFO] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) [INFO] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) [INFO] at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:663) [INFO] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [INFO] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) [INFO] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397) [INFO] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) [INFO] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905) [INFO] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743) [INFO] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) [INFO] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190) [INFO] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) [INFO] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) [INFO] at java.base/java.lang.Thread.run(Thread.java:1583) [INFO]
asyncSupportedãæå¹ã«ããServletã䜿ã
次ã¯ãå
ã»ã©ãšã»ãŒåãã³ãŒãã§@WebServletã¢ãããŒã·ã§ã³ã®asyncSupported屿§ãtrueã«ããServletã§è©ŠããŠã¿ãŸãã
src/main/java/org/littlewings/servlet/async/SimpleAsyncServlet.java
package org.littlewings.servlet.async; import java.io.IOException; import java.io.PrintWriter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.TimeUnit; import jakarta.servlet.AsyncContext; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @WebServlet(urlPatterns = "/async/simple", asyncSupported = true) public class SimpleAsyncServlet extends HttpServlet { private Logger logger = LoggerFactory.getLogger(SimpleAsyncServlet.class); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { AsyncContext asyncContext = request.startAsync(request, response); asyncContext.start(() -> { try { TimeUnit.SECONDS.sleep(1L); HttpServletResponse res = (HttpServletResponse) asyncContext.getResponse(); PrintWriter writer = res.getWriter(); for (int i = 0; i < 5; i++) { int counter = i + 1; logger.info("[{}] in async{}", Thread.currentThread().getName(), counter); writer.printf( "%s [%s] in async%d%n", LocalDateTime.now().format(DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")), Thread.currentThread().getName(), counter ); writer.flush(); TimeUnit.SECONDS.sleep(1L); } } catch (IOException | InterruptedException e) { throw new RuntimeException(e); } finally { logger.info("[{}] complete async", Thread.currentThread().getName()); asyncContext.complete(); } }); logger.info("[{}] dispatch async", Thread.currentThread().getName()); // HttpServletResponseãæäœããŠããã®ã§è¯ããªã response.getWriter().printf( "%s [%s] dispatch async%n", LocalDateTime.now().format(DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")), Thread.currentThread().getName() ); response.getWriter().flush(); } }
ä»åã¯ãéåæåŠçã®APIã«ã€ããŠå°ãè§ŠããŸãããã
ServletRequest#startAsyncã䜿ã£ãŠãAsyncContextãååŸããããšã§éåæåŠçãå®è£
ã§ããããã«ãªããŸãã
AsyncContext asyncContext = request.startAsync(request, response);
å
ã»ã©ã¯asyncSupportãæå¹ã«ããŠããªãã£ãã®ã§ããã®åŒã³åºãã«å€±æããŸããã
ServletRequest#startAsyncã«ã¯åŒæ°ãåããªãããŒãžã§ã³ãããã®ã§ããããã®å Žåã¯ãªãªãžãã«ã®ServletRequestãš
ServletResponseãæå®ãããããšã«ãªãã¿ããã§ãããã€ãŸããä»åã®å®è£
ã ãšå·®ããããŸããã
ServletRequestWrapperãServletResponseWrapperã䜿ã£ãããããšãå·®ãåºãã§ããããã
éåæåŠçã1çªç°¡åã«è¡ãã«ã¯ãAsyncContext#startã«Runnableãæž¡ããšå®è£
ã§ããŸãã
asyncContext.start(() -> {
ãã®äžã®åŠçã¯ãå¥ã¹ã¬ããã§è¡ãããŸãã
ServletRequestãšServletResponseã¯ãAsyncContextããååŸããã®ãããã§ãããã
â»ä»åã¯ServletResponseãã䜿ã£ãŠããŸãããâŠ
HttpServletResponse res = (HttpServletResponse) asyncContext.getResponse();
PrintWriter writer = res.getWriter();
ããšã¯ã¹ãªãŒããå ¥ãã€ã€ã§ãããã¬ã¹ãã³ã¹ãæžãåºããŠ
for (int i = 0; i < 5; i++) { int counter = i + 1; logger.info("[{}] in async{}", Thread.currentThread().getName(), counter); writer.printf( "%s [%s] in async%d%n", LocalDateTime.now().format(DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")), Thread.currentThread().getName(), counter ); writer.flush(); TimeUnit.SECONDS.sleep(1L);
æåŸã«AsyncContext#completeãåŒã³åºããŠéåæåŠçãå®äºããŠããŸãã
} finally { logger.info("[{}] complete async", Thread.currentThread().getName()); asyncContext.complete(); }
ããšãServletããªã¯ãšã¹ããåãä»ããã¹ã¬ããã§ãServletResponseãæäœããŠããŸãããæ¬æ¥ã¯ã¹ã¬ããã»ãŒãã§ã¯
ãªãã¯ããªã®ã§ããããã®ã¯Jakarta Servletã®å®è£
ã§ã¯ãããªãããã«æ³šæãããŠããŸãã
logger.info("[{}] dispatch async", Thread.currentThread().getName()); // HttpServletResponseãæäœããŠããã®ã§è¯ããªã response.getWriter().printf( "%s [%s] dispatch async%n", LocalDateTime.now().format(DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")), Thread.currentThread().getName() ); response.getWriter().flush();
ä»åã¯ãåããèŠãé¢ä¿äžã¡ãã£ãšå ¥ããŠããŸãã
éæã«ã¹ã¬ããåããããããã«ãã°ãã¬ã¹ãã³ã¹ã®å 容ã«å«ããããã«ããŠããŸãã
ã§ã¯ã確èªããŠã¿ãŸãããã
$ curl localhost:8080/async/simple
WildFlyã
ãŸãã¯ã¬ã¹ãã³ã¹ã
2025-02-24 11:24:57 [default task-1] dispatch async 2025-02-24 11:24:58 [default task-2] in async1 2025-02-24 11:24:59 [default task-2] in async2 2025-02-24 11:25:00 [default task-2] in async3 2025-02-24 11:25:01 [default task-2] in async4 2025-02-24 11:25:02 [default task-2] in async5
ãã°ã
11:24:57,117 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-1) [default task-1] dispatch async 11:24:58,117 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-2) [default task-2] in async1 11:24:59,120 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-2) [default task-2] in async2 11:25:00,122 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-2) [default task-2] in async3 11:25:01,123 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-2) [default task-2] in async4 11:25:02,125 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-2) [default task-2] in async5 11:25:03,127 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-2) [default task-2] complete async
確ãã«å¥ã¹ã¬ããã«ãªã£ãŠããã®ã§ãããããã¯ã¯ãŒã«ãŒã¹ã¬ããã䜿ã£ãŠãããããªæ°ãããŸãã
ã¬ã¹ãã³ã¹ã
2025-02-24 11:25:58 [http-nio-8080-exec-3] dispatch async 2025-02-24 11:25:59 [http-nio-8080-exec-4] in async1 2025-02-24 11:26:00 [http-nio-8080-exec-4] in async2 2025-02-24 11:26:01 [http-nio-8080-exec-4] in async3 2025-02-24 11:26:02 [http-nio-8080-exec-4] in async4 2025-02-24 11:26:03 [http-nio-8080-exec-4] in async5
ãã°ã
[INFO] [http-nio-8080-exec-3] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-3] dispatch async [INFO] [http-nio-8080-exec-4] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-4] in async1 [INFO] [http-nio-8080-exec-4] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-4] in async2 [INFO] [http-nio-8080-exec-4] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-4] in async3 [INFO] [http-nio-8080-exec-4] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-4] in async4 [INFO] [http-nio-8080-exec-4] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-4] in async5 [INFO] [http-nio-8080-exec-4] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-4] complete async
ãã¡ããåãã§ããã
ãšããããã§ãAsyncContext#startã䜿ããšéåæåŠçãå®è£
ã§ããããªãã®ã®ãåãæ¿ãå
ã®ã¹ã¬ããã¯HTTPãªã¯ãšã¹ãã
æ±ããã®ãšåããã®ã䜿ãããå®è£
ãããããã§ããã
ãããªããšéåæåŠçãè¡ã£ãŠããéã¯æ±ããHTTPãªã¯ãšã¹ãæ°ãæžããšããããšã«ãªãã®ã§ããããå«ãªå Žåã¯
å¥ã«ã¹ã¬ããããŒã«ã䜿ããšããããšã«ãªãããã§ãã
éåžžã®Filterã远å ããŠã¿ã
ã¹ã¬ããããŒã«ã䜿ãåã«ãFilterã远å ããŠã¿ãŸãããã
ããããFilterã远å ããã¡ãããéåæåŠçãæå¹ã«ããServletã«å¹æãããããã«èšå®ããŸãã
src/main/java/org/littlewings/servlet/async/SimpleFilter.java
package org.littlewings.servlet.async; import java.io.IOException; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.annotation.WebFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // asyncSupportedããªãFilterã¯å®è¡ã§ããªãïŒServletRequest#startAsyncã®åŒã³åºãã倱æããïŒ @WebFilter(urlPatterns = "/*") public class SimpleFilter implements Filter { private Logger logger = LoggerFactory.getLogger(SimpleFilter.class); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { logger.info("do filter@{}", getClass().getSimpleName()); chain.doFilter(request, response); } }
ã³ã¡ã³ãã«ãæžããŠããŸããã@WebFilterã¢ãããŒã·ã§ã³ã®asyncSupported屿§ãtrueã«ãªã£ãŠããã®ã§ã
ãã®Filterãé©çšãããåŸã«ã¯éåæåŠçã¯äœ¿ããŸããã
// asyncSupportedããªãFilterã¯å®è¡ã§ããªãïŒServletRequest#startAsyncã®åŒã³åºãã倱æããïŒ @WebFilter(urlPatterns = "/*")
詊ããŠã¿ãŸããã¢ã¯ã»ã¹ããURLã¯ãå ã»ã©ã®éåæåŠçãæå¹ã«ããServletã§ãã
$ curl localhost:8080/async/simple
WildFlyã
11:30:29,143 INFO [org.littlewings.servlet.async.SimpleFilter] (default task-1) do filter@SimpleFilter
11:30:29,145 ERROR [io.undertow.request] (default task-1) UT005023: Exception handling request to /async/simple: java.lang.IllegalStateException: UT010026: Async is not supported for this request, as not all filters or Servlets were marked as supporting async
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.spec.HttpServletRequestImpl.startAsync(HttpServletRequestImpl.java:1096)
at deployment.ROOT.war//org.littlewings.servlet.async.SimpleAsyncServlet.doGet(SimpleAsyncServlet.java:24)
at jakarta.servlet.api@6.0.0//jakarta.servlet.http.HttpServlet.service(HttpServlet.java:527)
at jakarta.servlet.api@6.0.0//jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
at deployment.ROOT.war//org.littlewings.servlet.async.SimpleFilter.doFilter(SimpleFilter.java:23)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at org.wildfly.security.elytron-web.undertow-server@4.1.0.Final//org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler.lambda$handleRequest$1(ElytronRunAsHandler.java:68)
at org.wildfly.security.elytron-base@2.6.0.Final//org.wildfly.security.auth.server.FlexibleIdentityAssociation.runAsFunctionEx(FlexibleIdentityAssociation.java:103)
at org.wildfly.security.elytron-base@2.6.0.Final//org.wildfly.security.auth.server.Scoped.runAsFunctionEx(Scoped.java:161)
at org.wildfly.security.elytron-base@2.6.0.Final//org.wildfly.security.auth.server.Scoped.runAs(Scoped.java:73)
at org.wildfly.security.elytron-web.undertow-server@4.1.0.Final//org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler.handleRequest(ElytronRunAsHandler.java:67)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
at io.undertow.core@2.3.18.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.core@2.3.18.Final//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
at io.undertow.core@2.3.18.Final//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
at org.wildfly.security.elytron-web.undertow-server-servlet@4.1.0.Final//org.wildfly.elytron.web.undertow.server.servlet.CleanUpHandler.handleRequest(CleanUpHandler.java:38)
at io.undertow.core@2.3.18.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at org.wildfly.extension.undertow@35.0.1.Final//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:44)
at io.undertow.core@2.3.18.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at org.wildfly.extension.undertow@35.0.1.Final//org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:51)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52)
at io.undertow.core@2.3.18.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:276)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:132)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
at org.wildfly.extension.undertow@35.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1421)
at org.wildfly.extension.undertow@35.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1421)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:256)
at io.undertow.servlet@2.3.18.Final//io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:101)
at io.undertow.core@2.3.18.Final//io.undertow.server.Connectors.executeRootHandler(Connectors.java:395)
at io.undertow.core@2.3.18.Final//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:861)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1348)
at org.jboss.xnio@3.8.16.Final//org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282)
at java.base/java.lang.Thread.run(Thread.java:1583)
å
ã»ã©ã¯æåããŠãããServletRequest#startAsyncã®åŒã³åºãã倱æããããã«ãªããŸãã
ã¡ãªã¿ã«ãFilterèªäœã¯åããŠããŸãã
11:30:29,143 INFO [org.littlewings.servlet.async.SimpleFilter] (default task-1) do filter@SimpleFilter
[INFO] [http-nio-8080-exec-3] INFO org.littlewings.servlet.async.SimpleFilter - do filter@SimpleFilter [INFO] 2æ 24, 2025 11:32:47 åå org.apache.catalina.connector.Request startAsync [INFO] èŠå: åŠçãã§ãŒã³å ã®æ¬¡ã®ã¯ã©ã¹ãéåæããµããŒãããŠããªããããéåæãéå§ã§ããŸãã [org.littlewings.servlet.async.SimpleFilter] [INFO] java.lang.IllegalStateException: çŸåšã®ãã§ãŒã³ã®ãã£ã«ã¿ãŸãã¯ãµãŒãã¬ããã¯éåææäœããµããŒãããŠããŸããã [INFO] at org.apache.catalina.connector.Request.startAsync(Request.java:1510) [INFO] at org.apache.catalina.connector.RequestFacade.startAsync(RequestFacade.java:720) [INFO] at org.littlewings.servlet.async.SimpleAsyncServlet.doGet(SimpleAsyncServlet.java:24) [INFO] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) [INFO] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) [INFO] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) [INFO] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) [INFO] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) [INFO] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) [INFO] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) [INFO] at org.littlewings.servlet.async.SimpleFilter.doFilter(SimpleFilter.java:23) [INFO] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) [INFO] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) [INFO] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) [INFO] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) [INFO] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) [INFO] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) [INFO] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) [INFO] at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:663) [INFO] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [INFO] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) [INFO] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397) [INFO] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) [INFO] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905) [INFO] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743) [INFO] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) [INFO] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190) [INFO] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) [INFO] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) [INFO] at java.base/java.lang.Thread.run(Thread.java:1583) [INFO] [INFO] 2æ 24, 2025 11:32:47 åå org.apache.catalina.core.StandardWrapperValve invoke [INFO] é倧: ãµãŒãã¬ãã [org.littlewings.servlet.async.SimpleAsyncServlet] ã®Servlet.service()ãäŸå€ãæããŸãã [INFO] java.lang.IllegalStateException: çŸåšã®ãã§ãŒã³ã®ãã£ã«ã¿ãŸãã¯ãµãŒãã¬ããã¯éåææäœããµããŒãããŠããŸããã [INFO] at org.apache.catalina.connector.Request.startAsync(Request.java:1510) [INFO] at org.apache.catalina.connector.RequestFacade.startAsync(RequestFacade.java:720) [INFO] at org.littlewings.servlet.async.SimpleAsyncServlet.doGet(SimpleAsyncServlet.java:24) [INFO] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) [INFO] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) [INFO] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) [INFO] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) [INFO] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) [INFO] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) [INFO] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) [INFO] at org.littlewings.servlet.async.SimpleFilter.doFilter(SimpleFilter.java:23) [INFO] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) [INFO] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) [INFO] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) [INFO] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) [INFO] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) [INFO] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) [INFO] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) [INFO] at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:663) [INFO] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [INFO] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) [INFO] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397) [INFO] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) [INFO] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905) [INFO] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743) [INFO] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) [INFO] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190) [INFO] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) [INFO] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) [INFO] at java.base/java.lang.Thread.run(Thread.java:1583) [INFO]
ãã¡ããçµæã¯åãã§ãã
äž¡æ¹ãšãHTTPã¹ããŒã¿ã¹ã³ãŒã500ã«ãªããŸãã
ãšããããã§ããã®Filterã¯ããã§ç¡å¹ã«ããŠãããŸãã
// asyncSupportedããªãFilterã¯å®è¡ã§ããªãïŒServletRequest#startAsyncã®åŒã³åºãã倱æããïŒ // @WebFilter(urlPatterns = "/*") public class SimpleFilter implements Filter {
asyncSupportedãæå¹ã«ããFilterã䜿ã
ã§ã¯ã次ã¯@WebFilterã®asyncSupportedãtrueã«ããFilterãé©çšããŠã¿ãŸãã
src/main/java/org/littlewings/servlet/async/SimpleAsyncFilter.java
package org.littlewings.servlet.async; import java.io.IOException; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.annotation.WebFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @WebFilter(urlPatterns = "/*", asyncSupported = true) public class SimpleAsyncFilter implements Filter { private Logger logger = LoggerFactory.getLogger(SimpleAsyncFilter.class); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { logger.info("do filter@{}", getClass().getSimpleName()); chain.doFilter(request, response); } }
確èªã
$ curl localhost:8080/async/simple
WildFlyã
ãã°ã
11:35:54,639 INFO [org.littlewings.servlet.async.SimpleAsyncFilter] (default task-1) do filter@SimpleAsyncFilter 11:35:54,640 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-1) [default task-1] dispatch async 11:35:55,641 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-2) [default task-2] in async1 11:35:56,644 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-2) [default task-2] in async2 11:35:57,646 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-2) [default task-2] in async3 11:35:58,647 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-2) [default task-2] in async4 11:35:59,649 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-2) [default task-2] in async5 11:36:00,651 INFO [org.littlewings.servlet.async.SimpleAsyncServlet] (default task-2) [default task-2] complete async
FilterãåäœããåŸã«ãéåæåŠçãåãããã«ãªããŸããã
ã¬ã¹ãã³ã¹ã¯ãªã«ãå€ãããªãã®ã§çç¥ããŸãã
[INFO] [http-nio-8080-exec-3] INFO org.littlewings.servlet.async.SimpleAsyncFilter - do filter@SimpleAsyncFilter [INFO] [http-nio-8080-exec-3] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-3] dispatch async [INFO] [http-nio-8080-exec-4] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-4] in async1 [INFO] [http-nio-8080-exec-4] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-4] in async2 [INFO] [http-nio-8080-exec-4] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-4] in async3 [INFO] [http-nio-8080-exec-4] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-4] in async4 [INFO] [http-nio-8080-exec-4] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-4] in async5 [INFO] [http-nio-8080-exec-4] INFO org.littlewings.servlet.async.SimpleAsyncServlet - [http-nio-8080-exec-4] complete async
ãã¡ããOKã§ããã
ãã®Filterã¯é©çšãããŸãŸã«ããŸãã
ã¹ã¬ããããŒã«ã䜿ã£ãŠéåæåŠçãè¡ã
æåŸã¯ãã¹ã¬ããããŒã«ã䜿ããŸããå
ã»ã©ã¯éåæåŠçã䜿ã£ãŠã¿ããã®ã®ãAsyncContext#startã§ã¯ãªã¯ãšã¹ããæ±ã
ã¹ã¬ãããšåãã¹ã¬ããããŒã«ã®ãã®ã䜿ãããŠãããã ãšãã話ã§ããã
ä»åã¯ãã®ããã«å€æŽã
src/main/java/org/littlewings/servlet/async/SimpleAsyncUseThreadServlet.java
package org.littlewings.servlet.async; import java.io.IOException; import java.io.PrintWriter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.naming.InitialContext; import javax.naming.NamingException; import jakarta.annotation.PostConstruct; import jakarta.servlet.AsyncContext; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @WebServlet(urlPatterns = "/async/thread", asyncSupported = true) public class SimpleAsyncUseThreadServlet extends HttpServlet { private Logger logger = LoggerFactory.getLogger(SimpleAsyncUseThreadServlet.class); private ExecutorService executorService; @PostConstruct void postConstruct() { try { // Jakarta ConcurrencyïŒWildFlyïŒ executorService = InitialContext.doLookup("java:comp/DefaultManagedExecutorService"); } catch (NamingException e) { // Tomcat executorService = Executors.newFixedThreadPool(10); } } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { AsyncContext asyncContext = request.startAsync(request, response); executorService.execute(() -> { try { TimeUnit.SECONDS.sleep(1L); HttpServletResponse res = (HttpServletResponse) asyncContext.getResponse(); PrintWriter writer = res.getWriter(); for (int i = 0; i < 5; i++) { int counter = i + 1; logger.info("[{}] in async{}", Thread.currentThread().getName(), counter); writer.printf( "%s [%s] in async%d%n", LocalDateTime.now().format(DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")), Thread.currentThread().getName(), counter ); writer.flush(); TimeUnit.SECONDS.sleep(1L); } } catch (IOException | InterruptedException e) { throw new RuntimeException(e); } finally { logger.info("[{}] complete async", Thread.currentThread().getName()); asyncContext.complete(); } }); logger.info("[{}] dispatch async", Thread.currentThread().getName()); // HttpServletResponseãæäœããŠããã®ã§è¯ããªã response.getWriter().printf( "%s [%s] dispatch async%n", LocalDateTime.now().format(DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")), Thread.currentThread().getName() ); response.getWriter().flush(); } }
ã¹ã¬ããããŒã«ã¯ãWildFlyã®å Žåã¯Jakarta Concurrentyã®ManagedExecutorServiceãApache Tomcatã®å Žåã¯
Executors#newFixedThreadPoolã䜿ãããšã«ããŸãã
private ExecutorService executorService; @PostConstruct void postConstruct() { try { // Jakarta ConcurrencyïŒWildFlyïŒ executorService = InitialContext.doLookup("java:comp/DefaultManagedExecutorService"); } catch (NamingException e) { // Tomcat executorService = Executors.newFixedThreadPool(10); } }
ããããã³ãŒãã ãšãWildFly Glowã§ã¯ã¬ã€ã€ãŒãæ€åºã§ããªãã®ã§ee-concurrencyã¬ã€ã€ãŒãæç€ºçã«è¿œå ããŠããŸãã
<configuration> <overwrite-provisioned-server>true</overwrite-provisioned-server> <discover-provisioning-info> <version>35.0.1.Final</version> <layers-for-jndi> <layer>ee-concurrency</layer> </layers-for-jndi> </discover-provisioning-info> </configuration>
倿Žç¹ã¯ãAsyncContext#startã¯äœ¿ããã«ExecutorService#executeã«ä»»ããããã«ããã ãã§ãã
executorService.execute(() -> {
try {
TimeUnit.SECONDS.sleep(1L);
HttpServletResponse res = (HttpServletResponse) asyncContext.getResponse();
PrintWriter writer = res.getWriter();
for (int i = 0; i < 5; i++) {
int counter = i + 1;
logger.info("[{}] in async{}", Thread.currentThread().getName(), counter);
writer.printf(
"%s [%s] in async%d%n",
LocalDateTime.now().format(DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")),
Thread.currentThread().getName(),
counter
);
writer.flush();
TimeUnit.SECONDS.sleep(1L);
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
} finally {
logger.info("[{}] complete async", Thread.currentThread().getName());
asyncContext.complete();
}
});
確èªããŠã¿ãŸãã
$ curl localhost:8080/async/thread
WildFlyã
ã¬ã¹ãã³ã¹ã
2025-02-24 11:44:12 [default task-1] dispatch async 2025-02-24 11:44:13 [EE-ManagedExecutorService-default-Thread-1] in async1 2025-02-24 11:44:14 [EE-ManagedExecutorService-default-Thread-1] in async2 2025-02-24 11:44:15 [EE-ManagedExecutorService-default-Thread-1] in async3 2025-02-24 11:44:16 [EE-ManagedExecutorService-default-Thread-1] in async4 2025-02-24 11:44:17 [EE-ManagedExecutorService-default-Thread-1] in async5
ãã°ã
11:44:12,691 INFO [org.littlewings.servlet.async.SimpleAsyncFilter] (default task-1) do filter@SimpleAsyncFilter 11:44:12,695 INFO [org.littlewings.servlet.async.SimpleAsyncUseThreadServlet] (default task-1) [default task-1] dispatch async 11:44:13,697 INFO [org.littlewings.servlet.async.SimpleAsyncUseThreadServlet] (EE-ManagedExecutorService-default-Thread-1) [EE-ManagedExecutorService-default-Thread-1] in async1 11:44:14,699 INFO [org.littlewings.servlet.async.SimpleAsyncUseThreadServlet] (EE-ManagedExecutorService-default-Thread-1) [EE-ManagedExecutorService-default-Thread-1] in async2 11:44:15,701 INFO [org.littlewings.servlet.async.SimpleAsyncUseThreadServlet] (EE-ManagedExecutorService-default-Thread-1) [EE-ManagedExecutorService-default-Thread-1] in async3 11:44:16,703 INFO [org.littlewings.servlet.async.SimpleAsyncUseThreadServlet] (EE-ManagedExecutorService-default-Thread-1) [EE-ManagedExecutorService-default-Thread-1] in async4 11:44:17,706 INFO [org.littlewings.servlet.async.SimpleAsyncUseThreadServlet] (EE-ManagedExecutorService-default-Thread-1) [EE-ManagedExecutorService-default-Thread-1] in async5 11:44:18,707 INFO [org.littlewings.servlet.async.SimpleAsyncUseThreadServlet] (EE-ManagedExecutorService-default-Thread-1) [EE-ManagedExecutorService-default-Thread-1] complete async
åœç¶ãšããã°åœç¶ã§ããã䜿ãããã¹ã¬ãããå€ãããŸããã
ã¬ã¹ãã³ã¹ã
2025-02-24 11:45:41 [http-nio-8080-exec-3] dispatch async 2025-02-24 11:45:42 [pool-1-thread-1] in async1 2025-02-24 11:45:43 [pool-1-thread-1] in async2 2025-02-24 11:45:44 [pool-1-thread-1] in async3 2025-02-24 11:45:45 [pool-1-thread-1] in async4 2025-02-24 11:45:46 [pool-1-thread-1] in async5
ãã°ã
[INFO] [http-nio-8080-exec-3] INFO org.littlewings.servlet.async.SimpleAsyncFilter - do filter@SimpleAsyncFilter [INFO] [http-nio-8080-exec-3] INFO org.littlewings.servlet.async.SimpleAsyncUseThreadServlet - [http-nio-8080-exec-3] dispatch async [INFO] [pool-1-thread-1] INFO org.littlewings.servlet.async.SimpleAsyncUseThreadServlet - [pool-1-thread-1] in async1 [INFO] [pool-1-thread-1] INFO org.littlewings.servlet.async.SimpleAsyncUseThreadServlet - [pool-1-thread-1] in async2 [INFO] [pool-1-thread-1] INFO org.littlewings.servlet.async.SimpleAsyncUseThreadServlet - [pool-1-thread-1] in async3 [INFO] [pool-1-thread-1] INFO org.littlewings.servlet.async.SimpleAsyncUseThreadServlet - [pool-1-thread-1] in async4 [INFO] [pool-1-thread-1] INFO org.littlewings.servlet.async.SimpleAsyncUseThreadServlet - [pool-1-thread-1] in async5 [INFO] [pool-1-thread-1] INFO org.littlewings.servlet.async.SimpleAsyncUseThreadServlet - [pool-1-thread-1] complete async
ãã¡ããåãã§ãã
ä»åã®ç¢ºèªç¯å²ã¯ãããŸã§ã«ããŸãã
AsyncContext#startã§äœ¿ãããã¹ã¬ããã®å®äœã¯ïŒ
ãšããã§ãAsyncContext#startã§ã¯Servletã®ãªã¯ãšã¹ããåŠçããã®ãšåãçš®é¡ã®ã¹ã¬ããã䜿ãããŠããããã«èŠããŸããã
æ¬åœã«ãããªã®ããã³ãŒãã§ç¢ºèªããŠã¿ãããšæããŸãã
Undertowã®å Žå
Undertowã®å Žåã¯ããã®ãããã«å®è£ ãããŠããŸãã
以äžã®é ã§éžã¶ããã§ãã
Deploymentã«éåæçšã®Executorãèšå®ãããŠããã°ããã䜿ãDeploymentã«Executorãèšå®ãããŠããã°ããã䜿ã- ã¯ãŒã«ãŒã¹ã¬ããïŒServletã®ãªã¯ãšã¹ããåŠçããã®ãšåãã¹ã¬ããããŒã«ïŒã䜿ã
ã§ãWildFlyã§èŠããšããã¯ãŒã«ãŒã¹ã¬ããã䜿ãããŠããŸããã
ã¡ãªã¿ã«ãã¹ã¬ãããèªåã§æ±ã£ãå Žåã§ãAsyncContext#completeãåŒã³åºããæã«ã¯å
éšçã«ãã®åŠçãåŒã³åºããã
ããšã«ãªãããã®æã¯ã¯ãŒã«ãŒã¹ã¬ããã䜿ãããããšã«ãªããŸãã
ãã£ã¹ããããè¡ã£ãŠãããã©ããã§æåãéããããªã®ã§ããã£ã¹ããããè¡ããã¿ãŒã³ã¯ãŸã確èªãããã§ããã
Apache Tomcatã®å Žå
Apache Tomcatã§ã®AsyncContext#startã®å®è£
ãèŠãŠã¿ãŸãããããã®ãããã§ããã
https://github.com/apache/tomcat/blob/10.1.36/java/org/apache/coyote/AsyncStateMachine.java#L475
ããã§ã®AbstractProcessorã®å®è£
ã¯Http11ProcessorãAjpProcessorãªã©ãæããŸãã
ã€ãŸããConnectorãšåãã¹ã¬ããããã£ã±ã䜿ãããããã§ããã
AsyncContext#completeãåŒã³åºããæã¯ãåŒã³åºãå
ã®ã¹ã¬ãããšåããã®ã䜿ãããŠããŸããã
ãã®ããããå®è£
ãããŠããAsyncStateMachineã«ã¯ã以äžã®ããã«éåæåŠçã®ç¶æ
é·ç§»ã«é¢ããã³ã¡ã³ãããã£ãããš
æžããŠãããŸããã
/** * Manages the state transitions for async requests. * * <pre> * The internal states that are used are: * DISPATCHED - Standard request. Not in Async mode. * STARTING - ServletRequest.startAsync() has been called from * Servlet.service() but service() has not exited. * STARTED - ServletRequest.startAsync() has been called from * Servlet.service() and service() has exited. * READ_WRITE_OP - Performing an asynchronous read or write. * MUST_COMPLETE - ServletRequest.startAsync() followed by complete() have * been called during a single Servlet.service() method. The * complete() will be processed as soon as Servlet.service() * exits. * COMPLETE_PENDING - ServletRequest.startAsync() has been called from * Servlet.service() but, before service() exited, complete() * was called from another thread. The complete() will * be processed as soon as Servlet.service() exits. * COMPLETING - The call to complete() was made once the request was in * the STARTED state. * TIMING_OUT - The async request has timed out and is waiting for a call * to complete() or dispatch(). If that isn't made, the error * state will be entered. * MUST_DISPATCH - ServletRequest.startAsync() followed by dispatch() have * been called during a single Servlet.service() method. The * dispatch() will be processed as soon as Servlet.service() * exits. * DISPATCH_PENDING - ServletRequest.startAsync() has been called from * Servlet.service() but, before service() exited, dispatch() * was called from another thread. The dispatch() will * be processed as soon as Servlet.service() exits. * DISPATCHING - The dispatch is being processed. * MUST_ERROR - ServletRequest.startAsync() has been called from * Servlet.service() but, before service() exited, an I/O * error occurred on another thread. The container will * perform the necessary error handling when * Servlet.service() exits. * ERROR - Something went wrong. * * * The valid state transitions are: * * post() dispatched() * |-------»------------------»---------| |-------«-----------------------«-----| * | | | | * | | | post() | * | post() \|/ \|/ dispatched() | * | |-----»----------------»DISPATCHED«-------------«-------------| | * | | | /|\ | | | * | | startAsync()| |--|timeout() | | * ^ | | | | * | | complete() | dispatch() ^ | * | | |--«---------------«-- | ---«--MUST_ERROR--»-----| | | * | | | | /|\ | | | * | ^ | | | | | | * | | | | /-----|error() | | | * | | | | / | ^ | * | | \|/ ST-complete() \|/ / ST-dispatch() \|/ | | * | MUST_COMPLETE«--------«--------STARTING--------»---------»MUST_DISPATCH | * | / | \ | * | / | \ | * | OT-complete() / | \ OT-dispatch() | * | COMPLETE_PENDING«------«------/ | \-------»---------»DISPATCH_PENDING | * | | /|\ | /|\ | | * | | | | | |post() | * | | |OT-complete() | OT-dispatch()| | | * | | |---------«-------«---|---«--\ | | | * | | | \ | | | * | | /-------«-------«-- | --«---READ_WRITE--»----| | | * | | / ST-complete() | / /|\ \ | | * | | / | post()/ / \ | | * | | / | / / \ | | * | | / | / / \ | | * | | / | / / \ | | * | | / | | / \ | | * | | / | | / ST-dispatch()\ | | * | | | | | | \ | | * | post()| | timeout() post()| | |asyncOperation() \ | timeout() | * | | | |--| | | | | | |--| | * | \|/\|/\|/ | complete() \|/ \|/| dispatch() \|/\|/ \|/ | | * |--«-----COMPLETING«--------«----------STARTED--------»---------»DISPATCHING----| * /|\ /|\ | /|\ | /|\ /|\ * | | | |--| | | * | | timeout()| post() | | * | | | | | * | | complete() \|/ dispatch() | | * | |------------«-------TIMING_OUT--------»----------------| | * | | * | complete() dispatch() | * |---------------«-----------ERROR--------------»-----------------| * * * Notes: * For clarity, the transitions to ERROR which are valid from every state apart from * STARTING are not shown. * * All transitions may happen on either the Servlet.service() thread (ST) or on any * other thread (OT) unless explicitly marked. * </pre> */ class AsyncStateMachine {
https://github.com/apache/tomcat/blob/10.1.36/java/org/apache/coyote/AsyncStateMachine.java#L30-L129
ãããã«
Jakarta Servletã®éåæåŠçãWildFly 35.0.1.Final.ãApache Tomcat 10.1.36ã§è©ŠããŠã¿ãŸããã
ã»ãŒè§Šããããšããªãã£ãã®ãšãJakarta Servletã®ä»æ§æžããããããšèªãããšã«ãªã£ãã®ã§å匷ã«ãªããŸããã
ãã£ã¹ããããŸããã¯ä»åã¯èŠããªãã£ãã®ã§ããŸãã®æ©äŒã«èŠãŠã¿ããããªãšæããŸãã