Support StreamingResponseBody return value type
Issue: SPR-12831
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user