SPR-5690 - Request header filtering in @RequestMapping
This commit is contained in:
parent
b8b74db01a
commit
afa461892f
|
|
@ -230,4 +230,25 @@ public @interface RequestMapping {
|
|||
*/
|
||||
String[] params() default {};
|
||||
|
||||
/**
|
||||
* The headers of the mapped request, narrowing the primary mapping.
|
||||
* <p>Same format for any environment: a sequence of "My-Header=myValue" style
|
||||
* expressions, with a request only mapped if each such header is found
|
||||
* to have the given value. "My-Header" style expressions are also supported,
|
||||
* with such headers having to be present in the request (allowed to have
|
||||
* any value). Finally, "!My-Header" style expressions indicate that the
|
||||
* specified header is <i>not</i> supposed to be present in the request.
|
||||
* <p>Also supports media type wildcards (*), for headers such as Accept
|
||||
* and Content-Type. For instance,
|
||||
* <pre>
|
||||
* @RequestMapping(value = "/something", headers = "content-type=text/*")
|
||||
* </pre>
|
||||
* will match requests with a Content-Type of "text/html", "text/plain", etc.
|
||||
* <p><b>Supported at the type level as well as at the method level!</b>
|
||||
* When used at the type level, all method-level mappings inherit
|
||||
* this header restriction (i.e. the type-level restriction
|
||||
* gets checked before the handler method is even resolved).
|
||||
* @see org.springframework.http.MediaType
|
||||
*/
|
||||
String[] headers() default {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -396,12 +396,15 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
if (!hasTypeLevelMapping() || !Arrays.equals(mapping.params(), getTypeLevelMapping().params())) {
|
||||
mappingInfo.params = mapping.params();
|
||||
}
|
||||
if (!hasTypeLevelMapping() || !Arrays.equals(mapping.headers(), getTypeLevelMapping().headers())) {
|
||||
mappingInfo.headers = mapping.headers();
|
||||
}
|
||||
boolean match = false;
|
||||
if (mappingInfo.paths.length > 0) {
|
||||
List<String> matchedPaths = new ArrayList<String>(mappingInfo.paths.length);
|
||||
for (String methodLevelPattern : mappingInfo.paths) {
|
||||
if (isPathMatch(methodLevelPattern, lookupPath)) {
|
||||
if (checkParameters(mappingInfo, request)) {
|
||||
if (mappingInfo.matches(request)) {
|
||||
match = true;
|
||||
matchedPaths.add(methodLevelPattern);
|
||||
}
|
||||
|
|
@ -418,7 +421,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
}
|
||||
else {
|
||||
// No paths specified: parameter match sufficient.
|
||||
match = checkParameters(mappingInfo, request);
|
||||
match = mappingInfo.matches(request);
|
||||
if (match && mappingInfo.methods.length == 0 && mappingInfo.params.length == 0 &&
|
||||
resolvedMethodName != null && !resolvedMethodName.equals(handlerMethod.getName())) {
|
||||
match = false;
|
||||
|
|
@ -514,11 +517,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean checkParameters(RequestMappingInfo mapping, HttpServletRequest request) {
|
||||
return ServletAnnotationMappingUtils.checkRequestMethod(mapping.methods, request) &&
|
||||
ServletAnnotationMappingUtils.checkParameters(mapping.params, request);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void extractHandlerMethodUriTemplates(String mappedPath,
|
||||
String lookupPath,
|
||||
|
|
@ -726,21 +724,29 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
|
||||
String[] params = new String[0];
|
||||
|
||||
String[] headers = new String[0];
|
||||
|
||||
String bestMatchedPath() {
|
||||
return matchedPaths.length > 0 ? matchedPaths[0] : null;
|
||||
}
|
||||
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
return ServletAnnotationMappingUtils.checkRequestMethod(this.methods, request) &&
|
||||
ServletAnnotationMappingUtils.checkParameters(this.params, request) &&
|
||||
ServletAnnotationMappingUtils.checkHeaders(this.headers, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
RequestMappingInfo other = (RequestMappingInfo) obj;
|
||||
return (Arrays.equals(this.paths, other.paths) && Arrays.equals(this.methods, other.methods) &&
|
||||
Arrays.equals(this.params, other.params));
|
||||
Arrays.equals(this.params, other.params) && Arrays.equals(this.headers, other.headers));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (Arrays.hashCode(this.paths) * 29 + Arrays.hashCode(this.methods) * 31 +
|
||||
Arrays.hashCode(this.params));
|
||||
return (Arrays.hashCode(this.paths) * 23 + Arrays.hashCode(this.methods) * 29 +
|
||||
Arrays.hashCode(this.params) * 31 + Arrays.hashCode(this.headers));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -777,14 +783,21 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
}
|
||||
else if (info1MethodCount == 1 & info2MethodCount > 1) {
|
||||
return -1;
|
||||
|
||||
}
|
||||
else if (info2MethodCount == 1 & info1MethodCount > 1) {
|
||||
return 1;
|
||||
}
|
||||
int info1ParamCount = info1.params.length;
|
||||
int info2ParamCount = info2.params.length;
|
||||
return (info1ParamCount < info2ParamCount ? 1 : (info1ParamCount == info2ParamCount ? 0 : -1));
|
||||
if (info1ParamCount != info2ParamCount) {
|
||||
return info2ParamCount - info1ParamCount;
|
||||
}
|
||||
int info1HeaderCount = info1.headers.length;
|
||||
int info2HeaderCount = info2.headers.length;
|
||||
if (info1HeaderCount != info2HeaderCount) {
|
||||
return info2HeaderCount - info1HeaderCount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,11 @@
|
|||
|
||||
package org.springframework.web.servlet.mvc.annotation;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
|
@ -32,6 +35,7 @@ abstract class ServletAnnotationMappingUtils {
|
|||
|
||||
/**
|
||||
* Check whether the given request matches the specified request methods.
|
||||
*
|
||||
* @param methods the HTTP request methods to check against
|
||||
* @param request the current HTTP request to check
|
||||
*/
|
||||
|
|
@ -49,8 +53,8 @@ abstract class ServletAnnotationMappingUtils {
|
|||
|
||||
/**
|
||||
* Check whether the given request matches the specified parameter conditions.
|
||||
* @param params the parameter conditions, following
|
||||
* {@link org.springframework.web.bind.annotation.RequestMapping#params()}
|
||||
*
|
||||
* @param params the parameter conditions, following {@link org.springframework.web.bind.annotation.RequestMapping#params()}
|
||||
* @param request the current HTTP request to check
|
||||
*/
|
||||
public static boolean checkParameters(String[] params, HttpServletRequest request) {
|
||||
|
|
@ -79,4 +83,62 @@ abstract class ServletAnnotationMappingUtils {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given request matches the specified header conditions.
|
||||
*
|
||||
* @param headers the header conditions, following {@link org.springframework.web.bind.annotation.RequestMapping#headers()}
|
||||
* @param request the current HTTP request to check
|
||||
*/
|
||||
public static boolean checkHeaders(String[] headers, HttpServletRequest request) {
|
||||
if (!ObjectUtils.isEmpty(headers)) {
|
||||
for (String header : headers) {
|
||||
int separator = header.indexOf('=');
|
||||
if (separator == -1) {
|
||||
if (header.startsWith("!")) {
|
||||
if (hasHeader(request, header.substring(1))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!hasHeader(request, header)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
String key = header.substring(0, separator);
|
||||
String value = header.substring(separator + 1);
|
||||
if (isMediaTypeHeader(key)) {
|
||||
List<MediaType> requestMediaTypes = MediaType.parseMediaTypes(request.getHeader(key));
|
||||
List<MediaType> valueMediaTypes = MediaType.parseMediaTypes(value);
|
||||
boolean found = false;
|
||||
for (Iterator<MediaType> valIter = valueMediaTypes.iterator(); valIter.hasNext() && !found;) {
|
||||
MediaType valueMediaType = valIter.next();
|
||||
for (Iterator<MediaType> reqIter = requestMediaTypes.iterator(); reqIter.hasNext() && !found;) {
|
||||
MediaType requestMediaType = reqIter.next();
|
||||
if (valueMediaType.includes(requestMediaType)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!value.equals(request.getHeader(key))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean hasHeader(HttpServletRequest request, String headerName) {
|
||||
return request.getHeader(headerName) != null;
|
||||
}
|
||||
|
||||
private static boolean isMediaTypeHeader(String headerName) {
|
||||
return "Accept".equalsIgnoreCase(headerName) || "Content-Type".equalsIgnoreCase(headerName);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -894,6 +894,23 @@ public class ServletAnnotationControllerTests {
|
|||
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headers() throws ServletException, IOException {
|
||||
initServlet(HeadersController.class);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/something");
|
||||
request.addHeader("Content-Type", "application/pdf");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
servlet.service(request, response);
|
||||
assertEquals("pdf", response.getContentAsString());
|
||||
|
||||
request = new MockHttpServletRequest("GET", "/something");
|
||||
request.addHeader("Content-Type", "text/html");
|
||||
response = new MockHttpServletResponse();
|
||||
servlet.service(request, response);
|
||||
assertEquals("text", response.getContentAsString());
|
||||
}
|
||||
|
||||
/*
|
||||
* Controllers
|
||||
*/
|
||||
|
|
@ -1472,6 +1489,20 @@ public class ServletAnnotationControllerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
public static class HeadersController {
|
||||
|
||||
@RequestMapping(value = "/something", headers = "content-type=application/pdf")
|
||||
public void handlePdf(Writer writer) throws IOException {
|
||||
writer.write("pdf");
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/something", headers = "content-type=text/*")
|
||||
public void handleHtml(Writer writer) throws IOException {
|
||||
writer.write("text");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class MyMessageConverter implements HttpMessageConverter {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,22 @@
|
|||
/*
|
||||
* Copyright 2002-2009 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.annotation;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
|
@ -26,4 +41,96 @@ public class ServletAnnotationMappingUtilsTests {
|
|||
assertFalse("Invalid request method result", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkParametersSimpleMatch() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
request.addParameter("param1", "value1");
|
||||
String[] params = new String[]{"param1", "!param2"};
|
||||
boolean result = ServletAnnotationMappingUtils.checkParameters(params, request);
|
||||
assertTrue("Invalid request method result", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkParametersSimpleNoMatch() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
request.addParameter("param1", "value1");
|
||||
request.addParameter("param2", "value2");
|
||||
String[] params = new String[]{"param1", "!param2"};
|
||||
boolean result = ServletAnnotationMappingUtils.checkParameters(params, request);
|
||||
assertFalse("Invalid request method result", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkParametersKeyValueMatch() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
request.addParameter("param1", "value1");
|
||||
String[] params = new String[]{"param1=value1"};
|
||||
boolean result = ServletAnnotationMappingUtils.checkParameters(params, request);
|
||||
assertTrue("Invalid request method result", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkParametersKeyValueNoMatch() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
request.addParameter("param1", "value1");
|
||||
String[] params = new String[]{"param1=foo"};
|
||||
boolean result = ServletAnnotationMappingUtils.checkParameters(params, request);
|
||||
assertFalse("Invalid request method result", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkHeadersSimpleMatch() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
request.addHeader("header1", "value1");
|
||||
String[] headers = new String[]{"header1", "!header2"};
|
||||
boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
|
||||
assertTrue("Invalid request method result", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkHeadersSimpleNoMatch() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
request.addHeader("header1", "value1");
|
||||
request.addHeader("header2", "value2");
|
||||
String[] headers = new String[]{"header1", "!header2"};
|
||||
boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
|
||||
assertFalse("Invalid request method result", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkHeadersKeyValueMatch() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
request.addHeader("header1", "value1");
|
||||
String[] headers = new String[]{"header1=value1"};
|
||||
boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
|
||||
assertTrue("Invalid request method result", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkHeadersKeyValueNoMatch() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
request.addHeader("header1", "value1");
|
||||
String[] headers = new String[]{"header1=foo"};
|
||||
boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
|
||||
assertFalse("Invalid request method result", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkHeadersAcceptMatch() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
request.addHeader("Accept", "application/pdf, text/html");
|
||||
String[] headers = new String[]{"accept=text/html, application/*"};
|
||||
boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
|
||||
assertTrue("Invalid request method result", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkHeadersAcceptNoMatch() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
request.addHeader("Accept", "application/pdf, text/html");
|
||||
String[] headers = new String[]{"accept=audio/basic, application/xml"};
|
||||
boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
|
||||
assertFalse("Invalid request method result", result);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue