Polish async feature for ServerHttpRequest/Response

ServerHttpAsyncResponseControl wraps a ServetHttpRequest and -Response
pair and allows putting the processing of the request in async mode
so that the response remains open until explicitly closed, either from
the current or from another thread.

ServletServerHttpAsyncResponseControl provides a Serlvet-based
implementation.
This commit is contained in:
Rossen Stoyanchev
2013-08-02 15:12:20 -04:00
parent 0d5901ffb6
commit 9700f09fad
9 changed files with 228 additions and 212 deletions

View File

@@ -1,35 +0,0 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* 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.springframework.http.server;
/**
* TODO..
*/
public interface AsyncServerHttpRequest extends ServerHttpRequest {
void setTimeout(long timeout);
void startAsync();
boolean isAsyncStarted();
void completeAsync();
boolean isAsyncCompleted();
}

View File

@@ -1,154 +0,0 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* 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.springframework.http.server;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
public class AsyncServletServerHttpRequest extends ServletServerHttpRequest
implements AsyncServerHttpRequest, AsyncListener {
private Long timeout;
private AsyncContext asyncContext;
private AtomicBoolean asyncCompleted = new AtomicBoolean(false);
private final List<Runnable> timeoutHandlers = new ArrayList<Runnable>();
private final List<Runnable> completionHandlers = new ArrayList<Runnable>();
private final HttpServletResponse servletResponse;
/**
* Create a new instance for the given request/response pair.
*/
public AsyncServletServerHttpRequest(HttpServletRequest request, HttpServletResponse response) {
super(request);
this.servletResponse = response;
}
/**
* Timeout period begins after the container thread has exited.
*/
@Override
public void setTimeout(long timeout) {
Assert.state(!isAsyncStarted(), "Cannot change the timeout with concurrent handling in progress");
this.timeout = timeout;
}
public void addTimeoutHandler(Runnable timeoutHandler) {
this.timeoutHandlers.add(timeoutHandler);
}
public void addCompletionHandler(Runnable runnable) {
this.completionHandlers.add(runnable);
}
@Override
public boolean isAsyncStarted() {
return ((this.asyncContext != null) && getServletRequest().isAsyncStarted());
}
/**
* Whether async request processing has completed.
* <p>It is important to avoid use of request and response objects after async
* processing has completed. Servlet containers often re-use them.
*/
@Override
public boolean isAsyncCompleted() {
return this.asyncCompleted.get();
}
@Override
public void startAsync() {
Assert.state(getServletRequest().isAsyncSupported(),
"Async support must be enabled on a servlet and for all filters involved " +
"in async request processing. This is done in Java code using the Servlet API " +
"or by adding \"<async-supported>true</async-supported>\" to servlet and " +
"filter declarations in web.xml.");
Assert.state(!isAsyncCompleted(), "Async processing has already completed");
if (isAsyncStarted()) {
return;
}
this.asyncContext = getServletRequest().startAsync(getServletRequest(), this.servletResponse);
this.asyncContext.addListener(this);
if (this.timeout != null) {
this.asyncContext.setTimeout(this.timeout);
}
}
@Override
public void completeAsync() {
Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext");
if (isAsyncStarted() && !isAsyncCompleted()) {
this.asyncContext.complete();
}
}
// ---------------------------------------------------------------------
// Implementation of AsyncListener methods
// ---------------------------------------------------------------------
@Override
public void onStartAsync(AsyncEvent event) throws IOException {
}
@Override
public void onError(AsyncEvent event) throws IOException {
}
@Override
public void onTimeout(AsyncEvent event) throws IOException {
try {
for (Runnable handler : this.timeoutHandlers) {
handler.run();
}
}
catch (Throwable t) {
// ignore
}
}
@Override
public void onComplete(AsyncEvent event) throws IOException {
try {
for (Runnable handler : this.completionHandlers) {
handler.run();
}
}
catch (Throwable t) {
// ignore
}
this.asyncContext = null;
this.asyncCompleted.set(true);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* 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.springframework.http.server;
/**
* A control that can put the processing of an HTTP request in asynchronous mode during
* which the response remains open until explicitly closed.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public interface ServerHttpAsyncResponseControl {
/**
* Enable asynchronous processing after which the response remains open until a call
* to {@link #complete()} is made or the server times out the request. Once enabled,
* additional calls to this method are ignored.
*/
void start();
/**
* A variation on {@link #start()} that allows specifying a timeout value to use to
* use for asynchronous processing. If {@link #complete()} is not called within the
* specified value, the request times out.
*/
void start(long timeout);
/**
* Whether asynchronous request processing has been started.
*/
boolean hasStarted();
/**
* Causes asynchronous request processing to be completed.
*/
void complete();
/**
* Whether asynchronous request processing has been completed.
*/
boolean isCompleted();
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* 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.springframework.http.server;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
/**
* A {@link ServerHttpAsyncResponseControl} to use on Servlet containers (Servlet 3.0+).
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class ServletServerHttpAsyncRequestControl implements ServerHttpAsyncResponseControl, AsyncListener {
private static long NO_TIMEOUT_VALUE = Long.MIN_VALUE;
private final ServletServerHttpRequest request;
private final ServletServerHttpResponse response;
private AsyncContext asyncContext;
private AtomicBoolean asyncCompleted = new AtomicBoolean(false);
/**
* Constructor accepting a request and response pair that are expected to be of type
* {@link ServletServerHttpRequest} and {@link ServletServerHttpResponse}
* respectively.
*/
public ServletServerHttpAsyncRequestControl(ServerHttpRequest request, ServerHttpResponse response) {
Assert.notNull(request, "request is required");
Assert.notNull(response, "response is required");
Assert.isInstanceOf(ServletServerHttpRequest.class, request);
Assert.isInstanceOf(ServletServerHttpResponse.class, response);
this.request = (ServletServerHttpRequest) request;
this.response = (ServletServerHttpResponse) response;
Assert.isTrue(this.request.getServletRequest().isAsyncSupported(),
"Async support must be enabled on a servlet and for all filters involved " +
"in async request processing. This is done in Java code using the Servlet API " +
"or by adding \"<async-supported>true</async-supported>\" to servlet and " +
"filter declarations in web.xml. Also you must use a Servlet 3.0+ container");
}
@Override
public boolean hasStarted() {
return ((this.asyncContext != null) && this.request.getServletRequest().isAsyncStarted());
}
@Override
public boolean isCompleted() {
return this.asyncCompleted.get();
}
@Override
public void start() {
start(NO_TIMEOUT_VALUE);
}
@Override
public void start(long timeout) {
Assert.state(!isCompleted(), "Async processing has already completed");
if (hasStarted()) {
return;
}
HttpServletRequest servletRequest = this.request.getServletRequest();
HttpServletResponse servletResponse = this.response.getServletResponse();
this.asyncContext = servletRequest.startAsync(servletRequest, servletResponse);
this.asyncContext.addListener(this);
if (timeout != NO_TIMEOUT_VALUE) {
this.asyncContext.setTimeout(timeout);
}
}
@Override
public void complete() {
if (hasStarted() && !isCompleted()) {
this.asyncContext.complete();
}
}
// ---------------------------------------------------------------------
// Implementation of AsyncListener methods
// ---------------------------------------------------------------------
@Override
public void onComplete(AsyncEvent event) throws IOException {
this.asyncContext = null;
this.asyncCompleted.set(true);
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException { }
@Override
public void onError(AsyncEvent event) throws IOException { }
@Override
public void onTimeout(AsyncEvent event) throws IOException { }
}