Browse Source

SPR-5690 - Request header filtering in @RequestMapping

conversation
Arjen Poutsma 16 years ago
parent
commit
afa461892f
  1. 21
      org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
  2. 37
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
  3. 66
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java
  4. 31
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
  5. 111
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtilsTests.java

21
org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java

@ -230,4 +230,25 @@ public @interface RequestMapping { @@ -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>
* &#064;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 {};
}

37
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java

@ -396,12 +396,15 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen @@ -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 @@ -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 @@ -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 @@ -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 @@ -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;
}
}

66
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java

@ -16,8 +16,11 @@ @@ -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 { @@ -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 { @@ -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 { @@ -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);
}
}

31
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java

@ -894,6 +894,23 @@ public class ServletAnnotationControllerTests { @@ -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 { @@ -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 {

111
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtilsTests.java

@ -1,7 +1,22 @@ @@ -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 { @@ -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…
Cancel
Save