This is an automated email from the ASF dual-hosted git repository.
thiagohp pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
The following commit(s) were added to refs/heads/master by this push:
new 3c83ffa TAP5-2698: Support for Servlet API 3.0+ asynchronous requests
3c83ffa is described below
commit 3c83ffa5735ca8b5bad8624e517b7b89615802c8
Author: Thiago H. de Paula Figueiredo <[email protected]>
AuthorDate: Sat Dec 11 17:06:27 2021 -0300
TAP5-2698: Support for Servlet API 3.0+ asynchronous requests
TAP5-2698: Adding support for async timeout
---
tapestry-core/src/test/app1/WEB-INF/web.xml | 12 +-
.../apache/tapestry5/http/AsyncRequestHandler.java | 53 +++++++
.../http/AsyncRequestHandlerResponse.java | 171 +++++++++++++++++++++
.../org/apache/tapestry5/http/TapestryFilter.java | 71 ++++++++-
.../http/internal/AsyncRequestService.java | 33 ++++
.../internal/services/AsyncRequestServiceImpl.java | 54 +++++++
.../tapestry5/http/modules/TapestryHttpModule.java | 4 +
7 files changed, 391 insertions(+), 7 deletions(-)
diff --git a/tapestry-core/src/test/app1/WEB-INF/web.xml
b/tapestry-core/src/test/app1/WEB-INF/web.xml
index bd71436..c4c2489 100644
--- a/tapestry-core/src/test/app1/WEB-INF/web.xml
+++ b/tapestry-core/src/test/app1/WEB-INF/web.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Copyright 2006, 2010 The Apache Software Foundation
+ Copyright 2006, 2010, 2021 The Apache Software Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,11 +14,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<!DOCTYPE web-app
- PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
- "http://java.sun.com/dtd/web-app_2_3.dtd">
-<web-app>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
<display-name>Integration Test App 1</display-name>
<context-param>
<param-name>tapestry.app-package</param-name>
@@ -35,6 +34,7 @@
<filter>
<filter-name>app</filter-name>
<filter-class>org.apache.tapestry5.TapestryFilter</filter-class>
+ <async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>app</filter-name>
diff --git
a/tapestry-http/src/main/java/org/apache/tapestry5/http/AsyncRequestHandler.java
b/tapestry-http/src/main/java/org/apache/tapestry5/http/AsyncRequestHandler.java
new file mode 100644
index 0000000..9271375
--- /dev/null
+++
b/tapestry-http/src/main/java/org/apache/tapestry5/http/AsyncRequestHandler.java
@@ -0,0 +1,53 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.http;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.servlet.AsyncListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tapestry5.http.internal.AsyncRequestService;
+
+/**
+ * <p>
+ * Service whose implementations define whether a given request should be
handled
+ * asynchronously or not and, if yes, which {@link Executor} (usually, a
thread pool,
+ * but not necessarily) should handle it, possibly different {@link
HttpServletRequest}
+ * and {@link HttpServletResponse} objects to be used when calling
+ * {@linkplain} HttpServletRequest#startAsync()} and an optional {@linkplain
AsyncListener}.
+ * <p>
+ * <p>
+ * If one {@link AsyncRequestHandler} doesn't tells the request should be
asynchronous,
+ * the next one contributed to {@link AsyncRequestService} will be called
+ * and so on until one says the request should be asynchronous or all of them
+ * were called and the request will be synchronous.
+ * </p>
+ * @see AsyncRequestService
+ * @see Executor
+ * @see ExecutorService
+ * @see Executors
+ */
+public interface AsyncRequestHandler
+{
+
+ /**
+ * Returns whether this request is handled by this handler. If not,
+ * it should return {@link AsyncRequestHandlerResponse#notHandled()}.
+ * @return a non-null {@linkplain AsyncRequestHandlerResponse}.
+ */
+ AsyncRequestHandlerResponse handle(HttpServletRequest request,
HttpServletResponse response);
+
+}
diff --git
a/tapestry-http/src/main/java/org/apache/tapestry5/http/AsyncRequestHandlerResponse.java
b/tapestry-http/src/main/java/org/apache/tapestry5/http/AsyncRequestHandlerResponse.java
new file mode 100644
index 0000000..aec82c1
--- /dev/null
+++
b/tapestry-http/src/main/java/org/apache/tapestry5/http/AsyncRequestHandlerResponse.java
@@ -0,0 +1,171 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.http;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+import javax.servlet.AsyncListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Class used by {@linkplain AsyncRequestHandler} to return information on how
to handle
+ * a request.
+ * @see AsyncRequestHandler
+ */
+public class AsyncRequestHandlerResponse
+{
+
+ private static final AsyncRequestHandlerResponse NOT_HANDLED =
+ new AsyncRequestHandlerResponse(false);
+
+ final private boolean async;
+
+ final private Executor executor;
+
+ private HttpServletRequest request;
+
+ private HttpServletResponse response;
+
+ private AsyncListener listener;
+
+ private long timeout;
+
+ /**
+ * Creates an instance with a given {@link Executor}. It cannot be null.
+ * If you want an instance with a non-async response, use {@link
#notHandled()} instead.
+ * @param executor a non-null {@link Executor}.
+ */
+ public AsyncRequestHandlerResponse(Executor executor)
+ {
+ this(true, executor);
+ }
+
+ private AsyncRequestHandlerResponse(boolean async, Executor executor)
+ {
+ Objects.requireNonNull(executor, "Parameter executor cannot be null");
+ this.async = async;
+ this.executor = executor;
+ }
+
+ private AsyncRequestHandlerResponse(boolean async)
+ {
+ this.async = async;
+ executor = null;
+ }
+
+ /**
+ * Defines a different request and response to be passed to {@link
HttpServletRequest#startAsync(javax.servlet.ServletRequest,
javax.servlet.ServletResponse)}.
+ * Both cannot be null.
+ */
+ public AsyncRequestHandlerResponse with(HttpServletRequest request,
HttpServletResponse response)
+ {
+ Objects.requireNonNull(request, "Parameter request cannot be null");
+ Objects.requireNonNull(response, "Parameter response cannot be null");
+ this.request = request;
+ this.response = response;
+ return this;
+ }
+
+ /**
+ * Defines a listener to be added to the asynchronous request. It cannot
be null.
+ */
+ public AsyncRequestHandlerResponse with(AsyncListener listener)
+ {
+ Objects.requireNonNull(listener, "Parameter listener cannot be null");
+ this.listener = listener;
+ return this;
+ }
+
+ /**
+ * Sets the timeout for this asynchronous request in milliseconds.
+ */
+ public AsyncRequestHandlerResponse withTimeout(long timeout)
+ {
+ this.timeout = timeout;
+ return this;
+ }
+
+ /**
+ * Returns a response saying this {@linkplain AsyncRequestHandler} doesn't
handle this request.
+ * @return an {@link AsyncRequestHandlerResponse}.
+ */
+ public static AsyncRequestHandlerResponse notHandled()
+ {
+ return NOT_HANDLED;
+ }
+
+ /**
+ * Returns whether the request should be processed asynchronously or not.
+ */
+ public boolean isAsync()
+ {
+ return async;
+ }
+
+ /**
+ * Returns the {@link Executor} to be used to process the request.
+ */
+ public Executor getExecutor()
+ {
+ return executor;
+ }
+
+ /**
+ * Returns the request to be used with {@link
HttpServletRequest#startAsync()} or null.
+ */
+ public HttpServletRequest getRequest()
+ {
+ return request;
+ }
+
+ /**
+ * Returns the response to be used with {@link
HttpServletRequest#startAsync()} or null.
+ */
+ public HttpServletResponse getResponse()
+ {
+ return response;
+ }
+
+ /**
+ * Returns the listener to be added to the asynchronous request or null.
+ */
+ public AsyncListener getListener()
+ {
+ return listener;
+ }
+
+ /**
+ * Returns whether a request and a response were set in this object.
+ */
+ public boolean isHasRequestAndResponse()
+ {
+ return request != null && response != null;
+ }
+
+ /**
+ * Returns the timeout, in milliseconds, for the asynchronous request. Any
value
+ * less than or equal zero is considered not having set a timeout.
+ */
+ public long getTimeout() {
+ return timeout;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "AsyncRequestHandlerResponse [async=" + async + ", executor=" +
executor + ", request=" + request + ", response=" + response + ", listener="
+ + listener + "]";
+ }
+
+}
diff --git
a/tapestry-http/src/main/java/org/apache/tapestry5/http/TapestryFilter.java
b/tapestry-http/src/main/java/org/apache/tapestry5/http/TapestryFilter.java
index 7aea81f..3fdb98d 100644
--- a/tapestry-http/src/main/java/org/apache/tapestry5/http/TapestryFilter.java
+++ b/tapestry-http/src/main/java/org/apache/tapestry5/http/TapestryFilter.java
@@ -14,6 +14,7 @@ package org.apache.tapestry5.http;
import java.io.IOException;
+import javax.servlet.AsyncContext;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
@@ -24,6 +25,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.apache.tapestry5.http.internal.AsyncRequestService;
import org.apache.tapestry5.http.internal.ServletContextSymbolProvider;
import org.apache.tapestry5.http.internal.SingleKeySymbolProvider;
import org.apache.tapestry5.http.internal.TapestryAppInitializer;
@@ -67,6 +69,8 @@ public class TapestryFilter implements Filter
private Registry registry;
private HttpServletRequestHandler handler;
+
+ private AsyncRequestService asyncRequestService;
/**
* Key under which the Tapestry IoC {@link
org.apache.tapestry5.ioc.Registry} is stored in the
@@ -117,6 +121,7 @@ public class TapestryFilter implements Filter
registry.performRegistryStartup();
handler = registry.getService("HttpServletRequestHandler",
HttpServletRequestHandler.class);
+ asyncRequestService = registry.getService("AsyncRequestService",
AsyncRequestService.class);
init(registry);
@@ -165,7 +170,7 @@ public class TapestryFilter implements Filter
return new Class[0];
}
- public final void doFilter(ServletRequest request, ServletResponse
response, FilterChain chain)
+ public final void runFilter(ServletRequest request, ServletResponse
response, FilterChain chain)
throws IOException, ServletException
{
try
@@ -182,6 +187,70 @@ public class TapestryFilter implements Filter
registry.cleanupThread();
}
}
+
+ public final void doFilter(ServletRequest request, ServletResponse
response, FilterChain chain)
+ throws IOException, ServletException
+ {
+
+ AsyncRequestHandlerResponse handlerResponse =
asyncRequestService.handle(
+ (HttpServletRequest) request, (HttpServletResponse) response);
+
+ if (handlerResponse.isAsync())
+ {
+ AsyncContext asyncContext;
+ if (handlerResponse.isHasRequestAndResponse())
+ {
+ asyncContext =
request.startAsync(handlerResponse.getRequest(), handlerResponse.getResponse());
+ }
+ else
+ {
+ asyncContext = request.startAsync();
+ }
+ if (handlerResponse.getListener() != null)
+ {
+ asyncContext.addListener(handlerResponse.getListener());
+ }
+ if (handlerResponse.getTimeout() > 0)
+ {
+ asyncContext.setTimeout(handlerResponse.getTimeout());
+ }
+ handlerResponse.getExecutor().execute(
+ new ExceptionCatchingRunnable(() -> {
+ runFilter(request, response, chain);
+ asyncContext.complete();
+ }));
+ }
+ else
+ {
+ runFilter(request, response, chain);
+ }
+ }
+
+ private static interface ExceptionRunnable
+ {
+ void run() throws Exception;
+ }
+
+ private final class ExceptionCatchingRunnable implements Runnable
+ {
+ private final ExceptionRunnable runnable;
+
+ public ExceptionCatchingRunnable(ExceptionRunnable runnable)
+ {
+ this.runnable = runnable;
+ }
+ public void run()
+ {
+ try
+ {
+ runnable.run();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ }
/**
* Shuts down and discards the registry. Invokes
diff --git
a/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/AsyncRequestService.java
b/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/AsyncRequestService.java
new file mode 100644
index 0000000..a7f4d52
--- /dev/null
+++
b/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/AsyncRequestService.java
@@ -0,0 +1,33 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.http.internal;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tapestry5.http.AsyncRequestHandler;
+import org.apache.tapestry5.http.AsyncRequestHandlerResponse;
+import org.apache.tapestry5.ioc.annotations.UsesOrderedConfiguration;
+
+/**
+ * Service that handles Tapestry's support for asynchronous Servlet API
requests.
+ * @see AsyncRequestHandler
+ */
+@UsesOrderedConfiguration(AsyncRequestHandler.class)
+public interface AsyncRequestService
+{
+ /**
+ * Returns an {@linkplain AsyncRequestHandlerResponse} describing how to
handle this
+ * request concering being async or not.
+ */
+ AsyncRequestHandlerResponse handle(HttpServletRequest request,
HttpServletResponse response);
+}
diff --git
a/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/AsyncRequestServiceImpl.java
b/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/AsyncRequestServiceImpl.java
new file mode 100644
index 0000000..964aa60
--- /dev/null
+++
b/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/AsyncRequestServiceImpl.java
@@ -0,0 +1,54 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.tapestry5.http.internal.services;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tapestry5.http.AsyncRequestHandler;
+import org.apache.tapestry5.http.AsyncRequestHandlerResponse;
+import org.apache.tapestry5.http.internal.AsyncRequestService;
+
+/**
+ * Service that handles Tapestry's support for asynchronous Servlet API
requests.
+ */
+public class AsyncRequestServiceImpl implements AsyncRequestService
+{
+
+ final private List<AsyncRequestHandler> handlers;
+
+ public AsyncRequestServiceImpl(List<AsyncRequestHandler> handlers)
+ {
+ super();
+ this.handlers = handlers;
+ }
+
+ public AsyncRequestHandlerResponse handle(HttpServletRequest request,
HttpServletResponse response)
+ {
+
+ AsyncRequestHandlerResponse handlerResponse =
AsyncRequestHandlerResponse.notHandled();
+
+ for (AsyncRequestHandler asyncRequestHandler : handlers)
+ {
+ handlerResponse = asyncRequestHandler.handle(request, response);
+ if (handlerResponse.isAsync())
+ {
+ break;
+ }
+ }
+
+ return handlerResponse;
+ }
+
+}
diff --git
a/tapestry-http/src/main/java/org/apache/tapestry5/http/modules/TapestryHttpModule.java
b/tapestry-http/src/main/java/org/apache/tapestry5/http/modules/TapestryHttpModule.java
index 87f500a..8e39574 100644
---
a/tapestry-http/src/main/java/org/apache/tapestry5/http/modules/TapestryHttpModule.java
+++
b/tapestry-http/src/main/java/org/apache/tapestry5/http/modules/TapestryHttpModule.java
@@ -30,9 +30,11 @@ import
org.apache.tapestry5.commons.internal.util.TapestryException;
import org.apache.tapestry5.commons.services.CoercionTuple;
import org.apache.tapestry5.http.OptimizedSessionPersistedObject;
import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
+import org.apache.tapestry5.http.internal.AsyncRequestService;
import org.apache.tapestry5.http.internal.TypeCoercerHttpRequestBodyConverter;
import org.apache.tapestry5.http.internal.gzip.GZipFilter;
import org.apache.tapestry5.http.internal.services.ApplicationGlobalsImpl;
+import org.apache.tapestry5.http.internal.services.AsyncRequestServiceImpl;
import org.apache.tapestry5.http.internal.services.BaseURLSourceImpl;
import org.apache.tapestry5.http.internal.services.ContextImpl;
import
org.apache.tapestry5.http.internal.services.DefaultSessionPersistedObjectAnalyzer;
@@ -102,6 +104,7 @@ public final class TapestryHttpModule {
binder.bind(BaseURLSource.class, BaseURLSourceImpl.class);
binder.bind(ResponseCompressionAnalyzer.class,
ResponseCompressionAnalyzerImpl.class);
binder.bind(RestSupport.class, RestSupportImpl.class);
+ binder.bind(AsyncRequestService.class, AsyncRequestServiceImpl.class);
}
/**
@@ -316,6 +319,7 @@ public final class TapestryHttpModule {
configuration.addInstance("TypeCoercer",
TypeCoercerHttpRequestBodyConverter.class);
}
+ @SuppressWarnings("rawtypes")
public static void
contributeTypeCoercer(MappedConfiguration<CoercionTuple.Key, CoercionTuple>
configuration)
{
CoercionTuple.add(configuration, HttpServletRequest.class,
String.class, TapestryHttpModule::toString);