Support "Accept-Patch" for OPTIONS requests
This commit introduces support in both servlet and webflux for the "Accept-Patch" header in OPTIONS requests, as defined in section 3.1 of RFC 5789. See gh-26759
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -244,7 +244,8 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||
if (helper.hasMethodsMismatch()) {
|
||||
Set<String> methods = helper.getAllowedMethods();
|
||||
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
|
||||
HttpOptionsHandler handler = new HttpOptionsHandler(methods);
|
||||
Set<MediaType> mediaTypes = helper.getConsumablePatchMediaTypes();
|
||||
HttpOptionsHandler handler = new HttpOptionsHandler(methods, mediaTypes);
|
||||
return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
|
||||
}
|
||||
throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
|
||||
@@ -411,6 +412,21 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return declared "consumable" types but only among those that have
|
||||
* PATCH specified, or that have no methods at all.
|
||||
*/
|
||||
public Set<MediaType> getConsumablePatchMediaTypes() {
|
||||
Set<MediaType> result = new LinkedHashSet<>();
|
||||
for (PartialMatch match : this.partialMatches) {
|
||||
Set<RequestMethod> methods = match.getInfo().getMethodsCondition().getMethods();
|
||||
if (methods.isEmpty() || methods.contains(RequestMethod.PATCH)) {
|
||||
result.addAll(match.getInfo().getConsumesCondition().getConsumableMediaTypes());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Container for a RequestMappingInfo that matches the URL path at least.
|
||||
@@ -475,8 +491,9 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
public HttpOptionsHandler(Set<String> declaredMethods) {
|
||||
public HttpOptionsHandler(Set<String> declaredMethods, Set<MediaType> acceptPatch) {
|
||||
this.headers.setAllow(initAllowedHttpMethods(declaredMethods));
|
||||
this.headers.setAcceptPatch(new ArrayList<>(acceptPatch));
|
||||
}
|
||||
|
||||
private static Set<HttpMethod> initAllowedHttpMethods(Set<String> declaredMethods) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -22,6 +22,7 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -31,8 +32,10 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.RequestPath;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
@@ -177,10 +180,11 @@ class RequestMappingInfoHandlerMappingTests {
|
||||
|
||||
@PathPatternsParameterizedTest
|
||||
void getHandlerHttpOptions(TestRequestMappingInfoHandlerMapping mapping) throws Exception {
|
||||
testHttpOptions(mapping, "/foo", "GET,HEAD,OPTIONS");
|
||||
testHttpOptions(mapping, "/person/1", "PUT,OPTIONS");
|
||||
testHttpOptions(mapping, "/persons", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS");
|
||||
testHttpOptions(mapping, "/something", "PUT,POST");
|
||||
testHttpOptions(mapping, "/foo", "GET,HEAD,OPTIONS", null);
|
||||
testHttpOptions(mapping, "/person/1", "PUT,OPTIONS", null);
|
||||
testHttpOptions(mapping, "/persons", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS", null);
|
||||
testHttpOptions(mapping, "/something", "PUT,POST", null);
|
||||
testHttpOptions(mapping, "/qux", "PATCH,GET,HEAD,OPTIONS", new MediaType("foo", "bar"));
|
||||
}
|
||||
|
||||
@PathPatternsParameterizedTest
|
||||
@@ -401,8 +405,8 @@ class RequestMappingInfoHandlerMappingTests {
|
||||
.satisfies(ex -> assertThat(ex.getSupportedMediaTypes()).containsExactly(MediaType.APPLICATION_XML));
|
||||
}
|
||||
|
||||
private void testHttpOptions(
|
||||
TestRequestMappingInfoHandlerMapping mapping, String requestURI, String allowHeader) throws Exception {
|
||||
private void testHttpOptions(TestRequestMappingInfoHandlerMapping mapping, String requestURI,
|
||||
String allowHeader, @Nullable MediaType acceptPatch) throws Exception {
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", requestURI);
|
||||
HandlerMethod handlerMethod = getHandler(mapping, request);
|
||||
@@ -413,7 +417,15 @@ class RequestMappingInfoHandlerMappingTests {
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getClass()).isEqualTo(HttpHeaders.class);
|
||||
assertThat(((HttpHeaders) result).getFirst("Allow")).isEqualTo(allowHeader);
|
||||
HttpHeaders headers = (HttpHeaders) result;
|
||||
Set<HttpMethod> allowedMethods = Arrays.stream(allowHeader.split(","))
|
||||
.map(HttpMethod::valueOf)
|
||||
.collect(Collectors.toSet());
|
||||
assertThat(headers.getAllow()).hasSameElementsAs(allowedMethods);
|
||||
|
||||
if (acceptPatch != null && headers.getAllow().contains(HttpMethod.PATCH) ) {
|
||||
assertThat(headers.getAcceptPatch()).containsExactly(acceptPatch);
|
||||
}
|
||||
}
|
||||
|
||||
private void testHttpMediaTypeNotAcceptableException(TestRequestMappingInfoHandlerMapping mapping, String url) {
|
||||
@@ -502,6 +514,15 @@ class RequestMappingInfoHandlerMappingTests {
|
||||
headers.add("Allow", "PUT,POST");
|
||||
return headers;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/qux", method = RequestMethod.GET, produces = "application/xml")
|
||||
public String getBaz() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/qux", method = RequestMethod.PATCH, consumes = "foo/bar")
|
||||
public void patchBaz(String value) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user