Support StreamingResponseBody return value type

Issue: SPR-12831
This commit is contained in:
Rossen Stoyanchev
2015-03-23 15:37:06 -04:00
parent 76cf5beb59
commit 95f6e4cc9b
8 changed files with 386 additions and 9 deletions

View File

@@ -632,6 +632,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters()));
handlers.add(new StreamingResponseBodyReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(
getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
handlers.add(new HttpHeadersReturnValueHandler());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -265,7 +265,8 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
return this.returnValue.getClass();
}
Class<?> parameterType = super.getParameterType();
if (ResponseBodyEmitter.class.isAssignableFrom(parameterType)) {
if (ResponseBodyEmitter.class.isAssignableFrom(parameterType) ||
StreamingResponseBody.class.isAssignableFrom(parameterType)) {
return parameterType;
}
Assert.isTrue(!ResolvableType.NONE.equals(this.returnType), "Expected one of" +

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2002-2015 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.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.io.OutputStream;
/**
* A controller method return value type for asynchronous request processing
* where the application can write directly to the response {@code OutputStream}
* without holding up the Servlet container thread.
*
* <p><strong>Note:</strong> when using this option it is highly recommended to
* configure explicitly the TaskExecutor used in Spring MVC for executing
* asynchronous requests. Both the MVC Java config and the MVC namespaces provide
* options to configure asynchronous handling. If not using those, an application
* can set the {@code taskExecutor} property of
* {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
* RequestMappingHandlerAdapter}.
*
* @author Rossen Stoyanchev
* @since 4.2
*/
public interface StreamingResponseBody {
/**
* A callback for writing to the response body.
* @param outputStream the stream for the response body
* @throws IOException an exception while writing
*/
void writeTo(OutputStream outputStream) throws IOException;
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2002-2015 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.web.servlet.mvc.method.annotation;
import java.io.OutputStream;
import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Supports return values of type
* {@link org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody}
* and also {@code ResponseEntity<StreamingResponseBody>}.
*
* @author Rossen Stoyanchev
* @since 4.2
*/
public class StreamingResponseBodyReturnValueHandler implements HandlerMethodReturnValueHandler {
private static final Log logger = LogFactory.getLog(StreamingResponseBodyReturnValueHandler.class);
@Override
public boolean supportsReturnType(MethodParameter returnType) {
if (StreamingResponseBody.class.isAssignableFrom(returnType.getParameterType())) {
return true;
}
else if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
Class<?> bodyType = ResolvableType.forMethodParameter(returnType).getGeneric(0).resolve();
return (bodyType != null && StreamingResponseBody.class.isAssignableFrom(bodyType));
}
return false;
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
ServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
if (ResponseEntity.class.isAssignableFrom(returnValue.getClass())) {
ResponseEntity<?> responseEntity = (ResponseEntity<?>) returnValue;
outputMessage.setStatusCode(responseEntity.getStatusCode());
outputMessage.getHeaders().putAll(responseEntity.getHeaders());
returnValue = responseEntity.getBody();
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
}
Assert.isInstanceOf(StreamingResponseBody.class, returnValue);
StreamingResponseBody streamingBody = (StreamingResponseBody) returnValue;
Callable<Void> callable = new StreamingResponseBodyTask(outputMessage.getBody(), streamingBody);
WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(callable, mavContainer);
}
private static class StreamingResponseBodyTask implements Callable<Void> {
private final OutputStream outputStream;
private final StreamingResponseBody streamingBody;
public StreamingResponseBodyTask(OutputStream outputStream, StreamingResponseBody streamingBody) {
this.outputStream = outputStream;
this.streamingBody = streamingBody;
}
@Override
public Void call() throws Exception {
this.streamingBody.writeTo(this.outputStream);
return null;
}
}
}