Infer HTTP 404 from empty Optional/Publisher types

This commit handles "empty" cases for `ResponseEntity` controller
handler return types when wrapped with a `java.util.Optional` in Spring
MVC or a single `Publisher` like `Mono`.

Given the following example for Spring MVC:

```
@GetMapping("/user")
public Optional<ResponseEntity<User>> fetchUser() {
	Optional<User> user = //...
	return user.map(ResponseEntity::ok);
}
```

If the resulting `Optional` is empty, Spring MVC will infer a
`ResponseEntity` with an empty body and a 404 HTTP response status.

The same reasoning is applied to Spring WebFlux with Publisher types:

```
@GetMapping("/user")
public Mono<ResponseEntity<User>> fetchUser() {
	Mono<User> user = //...
	return user.map(ResponseEntity::ok);
}
```

This feature is only valid for `HttpEntity` return types and does not
apply to `@ResponseBody` controller handlers.

Issue: SPR-13281
This commit is contained in:
Brian Clozel
2018-08-15 10:58:41 +02:00
parent 04141dee65
commit 7e91733502
6 changed files with 158 additions and 83 deletions

View File

@@ -24,6 +24,7 @@ import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -33,6 +34,7 @@ import org.springframework.core.ResolvableType;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
@@ -121,8 +123,9 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (HttpEntity.class.isAssignableFrom(returnType.getParameterType()) &&
!RequestEntity.class.isAssignableFrom(returnType.getParameterType()));
Class<?> parameterType = returnType.nestedIfOptional().getNestedParameterType();
return (HttpEntity.class.isAssignableFrom(parameterType) &&
!RequestEntity.class.isAssignableFrom(parameterType));
}
@Override
@@ -150,7 +153,7 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
@Nullable
private Type getHttpEntityType(MethodParameter parameter) {
Assert.isAssignable(HttpEntity.class, parameter.getParameterType());
Assert.isAssignable(HttpEntity.class, parameter.getNestedParameterType());
Type parameterType = parameter.getGenericParameterType();
if (parameterType instanceof ParameterizedType) {
ParameterizedType type = (ParameterizedType) parameterType;
@@ -169,6 +172,7 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
}
@Override
@SuppressWarnings("unchecked")
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
@@ -180,6 +184,12 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
if (returnType.getParameterType() == Optional.class) {
Optional<HttpEntity<?>> optionalEntity = (Optional<HttpEntity<?>>) returnValue;
returnValue = optionalEntity.orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));
returnType = returnType.nestedIfOptional();
}
Assert.isInstanceOf(HttpEntity.class, returnValue);
HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;