Align MVC handling between Spring 4 and Spring 5
A Spring Boot 2.0 app should behave the same as a Spering Boot 1.5 app with this change. The key thing was to change the return type of the FunctionController and move the computation of single valuedness for the output there (instead of trying to do it in the return value handler, which isn't used in Spring 5).
This commit is contained in:
@@ -16,9 +16,11 @@
|
||||
|
||||
package org.springframework.cloud.function.web.flux;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -26,15 +28,16 @@ import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.cloud.function.context.catalog.FunctionInspector;
|
||||
import org.springframework.cloud.function.context.message.MessageUtils;
|
||||
import org.springframework.cloud.function.web.flux.constants.WebRequestConstants;
|
||||
import org.springframework.cloud.function.web.flux.request.FluxRequest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestAttribute;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -65,11 +68,16 @@ public class FunctionController {
|
||||
|
||||
@PostMapping(path = "/**")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Flux<?>> post(
|
||||
@RequestAttribute(required = false, name = "org.springframework.cloud.function.web.flux.constants.WebRequestConstants.function") Function<Flux<?>, Flux<?>> function,
|
||||
@RequestAttribute(required = false, name = "org.springframework.cloud.function.web.flux.constants.WebRequestConstants.consumer") Consumer<Flux<?>> consumer,
|
||||
@RequestAttribute(required = false, name = "org.springframework.cloud.function.web.flux.constants.WebRequestConstants.input_single") Boolean single,
|
||||
public ResponseEntity<Publisher<?>> post(WebRequest request,
|
||||
@RequestBody FluxRequest<?> body) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<Flux<?>, Flux<?>> function = (Function<Flux<?>, Flux<?>>) request
|
||||
.getAttribute(WebRequestConstants.FUNCTION, WebRequest.SCOPE_REQUEST);
|
||||
@SuppressWarnings("unchecked")
|
||||
Consumer<Flux<?>> consumer = (Consumer<Flux<?>>) request
|
||||
.getAttribute(WebRequestConstants.CONSUMER, WebRequest.SCOPE_REQUEST);
|
||||
Boolean single = (Boolean) request.getAttribute(WebRequestConstants.INPUT_SINGLE,
|
||||
WebRequest.SCOPE_REQUEST);
|
||||
if (function != null) {
|
||||
Flux<?> flux = body.flux();
|
||||
if (debug) {
|
||||
@@ -82,7 +90,8 @@ public class FunctionController {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Handled POST with function");
|
||||
}
|
||||
return ResponseEntity.ok().body(debug ? result.log() : result);
|
||||
return ResponseEntity.ok().body(
|
||||
debug ? result.log() : response(request, function, single, result));
|
||||
}
|
||||
if (consumer != null) {
|
||||
Flux<?> flux = body.flux().cache(); // send a copy back to the caller
|
||||
@@ -98,16 +107,48 @@ public class FunctionController {
|
||||
throw new IllegalArgumentException("no such function");
|
||||
}
|
||||
|
||||
private Publisher<?> response(WebRequest request, Object handler, Boolean single,
|
||||
Flux<?> result) {
|
||||
if (single != null && single && isOutputSingle(handler)) {
|
||||
request.setAttribute(WebRequestConstants.OUTPUT_SINGLE, true,
|
||||
WebRequest.SCOPE_REQUEST);
|
||||
return Mono.from(result);
|
||||
}
|
||||
request.setAttribute(WebRequestConstants.OUTPUT_SINGLE, false,
|
||||
WebRequest.SCOPE_REQUEST);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isOutputSingle(Object handler) {
|
||||
Class<?> type = inspector.getOutputType(handler);
|
||||
Class<?> wrapper = inspector.getOutputWrapper(handler);
|
||||
if (Stream.class.isAssignableFrom(type)) {
|
||||
return false;
|
||||
}
|
||||
if (wrapper == type) {
|
||||
return true;
|
||||
}
|
||||
if (Mono.class.equals(wrapper) || Optional.class.equals(wrapper)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@GetMapping(path = "/**")
|
||||
@ResponseBody
|
||||
public Publisher<?> get(
|
||||
@RequestAttribute(required = false, name = "org.springframework.cloud.function.web.flux.constants.WebRequestConstants.function") Function<Flux<?>, Flux<?>> function,
|
||||
@RequestAttribute(required = false, name = "org.springframework.cloud.function.web.flux.constants.WebRequestConstants.supplier") Supplier<Flux<?>> supplier,
|
||||
@RequestAttribute(required = false, name = "org.springframework.cloud.function.web.flux.constants.WebRequestConstants.argument") String argument) {
|
||||
public Publisher<?> get(WebRequest request) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<Flux<?>, Flux<?>> function = (Function<Flux<?>, Flux<?>>) request
|
||||
.getAttribute(WebRequestConstants.FUNCTION, WebRequest.SCOPE_REQUEST);
|
||||
@SuppressWarnings("unchecked")
|
||||
Supplier<Flux<?>> supplier = (Supplier<Flux<?>>) request
|
||||
.getAttribute(WebRequestConstants.SUPPLIER, WebRequest.SCOPE_REQUEST);
|
||||
String argument = (String) request.getAttribute(WebRequestConstants.ARGUMENT,
|
||||
WebRequest.SCOPE_REQUEST);
|
||||
if (function != null) {
|
||||
return value(function, argument);
|
||||
}
|
||||
return supplier(supplier);
|
||||
return response(request, supplier, true, supplier(supplier));
|
||||
}
|
||||
|
||||
private Flux<?> supplier(Supplier<Flux<?>> supplier) {
|
||||
|
||||
@@ -35,5 +35,7 @@ public abstract class WebRequestConstants {
|
||||
public static final String HANDLER = WebRequestConstants.class.getName() + ".handler";
|
||||
public static final String INPUT_SINGLE = WebRequestConstants.class.getName()
|
||||
+ ".input_single";
|
||||
public static final String OUTPUT_SINGLE = WebRequestConstants.class.getName()
|
||||
+ ".output_single";
|
||||
|
||||
}
|
||||
|
||||
@@ -19,10 +19,8 @@ package org.springframework.cloud.function.web.flux.response;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -116,7 +114,7 @@ public class FluxReturnValueHandler implements AsyncHandlerMethodReturnValueHand
|
||||
if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
|
||||
Class<?> bodyType = ResolvableType.forMethodParameter(returnType)
|
||||
.getGeneric(0).resolve();
|
||||
return bodyType != null && Flux.class.isAssignableFrom(bodyType);
|
||||
return bodyType != null && Publisher.class.isAssignableFrom(bodyType);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -152,8 +150,7 @@ public class FluxReturnValueHandler implements AsyncHandlerMethodReturnValueHand
|
||||
NativeWebRequest.SCOPE_REQUEST);
|
||||
Class<?> type = inspector.getOutputType(handler);
|
||||
|
||||
boolean inputSingle = isInputSingle(webRequest, handler);
|
||||
if (inputSingle && isOutputSingle(handler)) {
|
||||
if (isOutputSingle(webRequest, handler, type)) {
|
||||
Object result = Flux.from(flux).blockFirst();
|
||||
if (result instanceof Message) {
|
||||
Message<?> message = (Message<?>) result;
|
||||
@@ -197,30 +194,18 @@ public class FluxReturnValueHandler implements AsyncHandlerMethodReturnValueHand
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInputSingle(NativeWebRequest webRequest, Object handler) {
|
||||
private boolean isOutputSingle(NativeWebRequest webRequest, Object handler,
|
||||
Class<?> type) {
|
||||
Boolean single = (Boolean) webRequest.getAttribute(
|
||||
WebRequestConstants.INPUT_SINGLE, NativeWebRequest.SCOPE_REQUEST);
|
||||
WebRequestConstants.OUTPUT_SINGLE, NativeWebRequest.SCOPE_REQUEST);
|
||||
if (single == null) {
|
||||
return handler instanceof Supplier;
|
||||
// If the declared return type is a collection then we can render it as a
|
||||
// "single" value
|
||||
return Collection.class.isAssignableFrom(type);
|
||||
}
|
||||
return single;
|
||||
}
|
||||
|
||||
private boolean isOutputSingle(Object handler) {
|
||||
Class<?> type = inspector.getOutputType(handler);
|
||||
Class<?> wrapper = inspector.getOutputWrapper(handler);
|
||||
if (Stream.class.isAssignableFrom(type)) {
|
||||
return false;
|
||||
}
|
||||
if (wrapper == type) {
|
||||
return true;
|
||||
}
|
||||
if (Mono.class.equals(wrapper) || Optional.class.equals(wrapper)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private MediaType findMediaType(NativeWebRequest webRequest) {
|
||||
List<MediaType> accepts = Arrays.asList(MediaType.ALL);
|
||||
MediaType mediaType = null;
|
||||
|
||||
Reference in New Issue
Block a user