Empty body checks in ConsumesRequestCondition
Normally consumes matches the "Content-Type" header but what should be done if there is no content? This commit adds checks for method parameters with @RequestBody(required=false) and if "false" then also match requests with no content. Closes gh-22010
This commit is contained in:
@@ -23,10 +23,13 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.InvalidMediaTypeException;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.cors.reactive.CorsUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
@@ -50,6 +53,8 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
||||
|
||||
private final List<ConsumeMediaTypeExpression> expressions;
|
||||
|
||||
private boolean bodyRequired = true;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance from 0 or more "consumes" expressions.
|
||||
@@ -141,6 +146,29 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
||||
return " || ";
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this condition should expect requests to have a body.
|
||||
* <p>By default this is set to {@code true} in which case it is assumed a
|
||||
* request body is required and this condition matches to the "Content-Type"
|
||||
* header or falls back on "Content-Type: application/octet-stream".
|
||||
* <p>If set to {@code false}, and the request does not have a body, then this
|
||||
* condition matches automatically, i.e. without checking expressions.
|
||||
* @param bodyRequired whether requests are expected to have a body
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setBodyRequired(boolean bodyRequired) {
|
||||
this.bodyRequired = bodyRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the setting for {@link #setBodyRequired(boolean)}.
|
||||
* @since 5.2
|
||||
*/
|
||||
public boolean isBodyRequired() {
|
||||
return this.bodyRequired;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the "other" instance if it has any expressions; returns "this"
|
||||
* instance otherwise. Practically that means a method-level "consumes"
|
||||
@@ -163,16 +191,27 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
||||
*/
|
||||
@Override
|
||||
public ConsumesRequestCondition getMatchingCondition(ServerWebExchange exchange) {
|
||||
if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
if (CorsUtils.isPreFlightRequest(request)) {
|
||||
return EMPTY_CONDITION;
|
||||
}
|
||||
if (isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
if (!hasBody(request) && !this.bodyRequired) {
|
||||
return EMPTY_CONDITION;
|
||||
}
|
||||
List<ConsumeMediaTypeExpression> result = getMatchingExpressions(exchange);
|
||||
return !CollectionUtils.isEmpty(result) ? new ConsumesRequestCondition(result) : null;
|
||||
}
|
||||
|
||||
private boolean hasBody(ServerHttpRequest request) {
|
||||
String contentLength = request.getHeaders().getFirst(HttpHeaders.CONTENT_LENGTH);
|
||||
String transferEncoding = request.getHeaders().getFirst(HttpHeaders.TRANSFER_ENCODING);
|
||||
return StringUtils.hasText(transferEncoding) ||
|
||||
(StringUtils.hasText(contentLength) && !contentLength.trim().equals("0"));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private List<ConsumeMediaTypeExpression> getMatchingExpressions(ServerWebExchange exchange) {
|
||||
List<ConsumeMediaTypeExpression> result = null;
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.web.reactive.result.method.annotation;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
@@ -25,6 +26,8 @@ import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.context.EmbeddedValueResolverAware;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -32,12 +35,14 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
import org.springframework.web.reactive.result.condition.ConsumesRequestCondition;
|
||||
import org.springframework.web.reactive.result.condition.RequestCondition;
|
||||
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
||||
import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping;
|
||||
@@ -255,6 +260,31 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerMapping(RequestMappingInfo mapping, Object handler, Method method) {
|
||||
super.registerMapping(mapping, handler, method);
|
||||
updateConsumesCondition(mapping, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
|
||||
super.registerHandlerMethod(handler, method, mapping);
|
||||
updateConsumesCondition(mapping, method);
|
||||
}
|
||||
|
||||
private void updateConsumesCondition(RequestMappingInfo info, Method method) {
|
||||
ConsumesRequestCondition condition = info.getConsumesCondition();
|
||||
if (!condition.isEmpty()) {
|
||||
for (Parameter parameter : method.getParameters()) {
|
||||
MergedAnnotation<RequestBody> annot = MergedAnnotations.from(parameter).get(RequestBody.class);
|
||||
if (annot.isPresent()) {
|
||||
condition.setBodyRequired(annot.getBoolean("required"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
|
||||
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
|
||||
|
||||
Reference in New Issue
Block a user