You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by th...@apache.org on 2021/12/11 20:06:53 UTC
[tapestry-5] 01/01: TAP5-2698: Support for Servlet API 3.0+ asynchronous requests
This is an automated email from the ASF dual-hosted git repository.
thiagohp pushed a commit to branch async
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
commit fe8b89250807d5ed1d43d2ca6b221b27da2b8bdc
Author: Thiago H. de Paula Figueiredo <th...@arsmachina.com.br>
AuthorDate: Sat Dec 11 17:06:27 2021 -0300
TAP5-2698: Support for Servlet API 3.0+ asynchronous requests
---
.../apache/tapestry5/http/AsyncRequestHandler.java | 53 ++++++++
.../http/AsyncRequestHandlerResponse.java | 145 +++++++++++++++++++++
.../org/apache/tapestry5/http/TapestryFilter.java | 64 ++++++++-
.../http/internal/AsyncRequestService.java | 33 +++++
.../internal/services/AsyncRequestServiceImpl.java | 54 ++++++++
.../tapestry5/http/modules/TapestryHttpModule.java | 4 +
6 files changed, 352 insertions(+), 1 deletion(-)
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..9bd3040
--- /dev/null
+++ b/tapestry-http/src/main/java/org/apache/tapestry5/http/AsyncRequestHandlerResponse.java
@@ -0,0 +1,145 @@
+// 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;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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;
+ }
+
+}
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..5be696a 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,63 @@ 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());
+ }
+ handlerResponse.getExecutor().execute(
+ new ExceptionCatchingRunnable(() -> runFilter(request, response, chain)));
+ }
+ 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);