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:
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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 { }
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user