Browse Source

Support HTTP HEAD

Issue: SPR-13130
pull/1120/head
Rossen Stoyanchev 10 years ago
parent
commit
d70ad765bf
  1. 3
      spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java
  2. 7
      spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java
  3. 11
      spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java
  4. 4
      spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java
  5. 19
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java
  6. 6
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java
  7. 20
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestConditionTests.java
  8. 55
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java

3
spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -340,6 +340,7 @@ public class CorsConfiguration { @@ -340,6 +340,7 @@ public class CorsConfiguration {
}
if (allowedMethods.isEmpty()) {
allowedMethods.add(HttpMethod.GET.name());
allowedMethods.add(HttpMethod.HEAD.name());
}
List<HttpMethod> result = new ArrayList<HttpMethod>(allowedMethods.size());
boolean allowed = false;

7
spring-web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -144,7 +144,10 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter { @@ -144,7 +144,10 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
protected boolean isEligibleForEtag(HttpServletRequest request, HttpServletResponse response,
int responseStatusCode, InputStream inputStream) {
if (responseStatusCode >= 200 && responseStatusCode < 300 && HttpMethod.GET.matches(request.getMethod())) {
String method = request.getMethod();
if (responseStatusCode >= 200 && responseStatusCode < 300 &&
(HttpMethod.GET.matches(method) || HttpMethod.HEAD.matches(method))) {
String cacheControl = null;
if (servlet3Present) {
cacheControl = response.getHeader(HEADER_CACHE_CONTROL);

11
spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -24,7 +24,10 @@ import org.junit.Test; @@ -24,7 +24,10 @@ import org.junit.Test;
import org.springframework.http.HttpMethod;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link CorsConfiguration}.
@ -176,7 +179,7 @@ public class CorsConfigurationTests { @@ -176,7 +179,7 @@ public class CorsConfigurationTests {
@Test
public void checkMethodAllowed() {
assertEquals(Arrays.asList(HttpMethod.GET), config.checkHttpMethod(HttpMethod.GET));
assertEquals(Arrays.asList(HttpMethod.GET, HttpMethod.HEAD), config.checkHttpMethod(HttpMethod.GET));
config.addAllowedMethod("GET");
assertEquals(Arrays.asList(HttpMethod.GET), config.checkHttpMethod(HttpMethod.GET));
config.addAllowedMethod("POST");
@ -189,7 +192,7 @@ public class CorsConfigurationTests { @@ -189,7 +192,7 @@ public class CorsConfigurationTests {
assertNull(config.checkHttpMethod(null));
assertNull(config.checkHttpMethod(HttpMethod.DELETE));
config.setAllowedMethods(new ArrayList<>());
assertNull(config.checkHttpMethod(HttpMethod.HEAD));
assertNull(config.checkHttpMethod(HttpMethod.POST));
}
@Test

4
spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -171,7 +171,7 @@ public class DefaultCorsProcessorTests { @@ -171,7 +171,7 @@ public class DefaultCorsProcessorTests {
this.conf.addAllowedOrigin("*");
this.processor.processRequest(this.conf, request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertEquals("GET", response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS));
assertEquals("GET,HEAD", response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS));
}
@Test

19
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2016 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.
@ -36,6 +36,10 @@ import org.springframework.web.bind.annotation.RequestMethod; @@ -36,6 +36,10 @@ import org.springframework.web.bind.annotation.RequestMethod;
*/
public final class RequestMethodsRequestCondition extends AbstractRequestCondition<RequestMethodsRequestCondition> {
private static final RequestMethodsRequestCondition HEAD_CONDITION =
new RequestMethodsRequestCondition(RequestMethod.HEAD);
private final Set<RequestMethod> methods;
@ -98,17 +102,24 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi @@ -98,17 +102,24 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
if (this.methods.isEmpty()) {
return this;
}
RequestMethod incomingRequestMethod = getRequestMethod(request);
if (incomingRequestMethod != null) {
RequestMethod requestMethod = getRequestMethod(request);
if (requestMethod != null) {
for (RequestMethod method : this.methods) {
if (method.equals(incomingRequestMethod)) {
if (method.equals(requestMethod)) {
return new RequestMethodsRequestCondition(method);
}
}
if (isHeadRequest(requestMethod) && getMethods().contains(RequestMethod.GET)) {
return HEAD_CONDITION;
}
}
return null;
}
private boolean isHeadRequest(RequestMethod requestMethod) {
return (requestMethod != null && RequestMethod.HEAD.equals(requestMethod));
}
private RequestMethod getRequestMethod(HttpServletRequest request) {
try {
return RequestMethod.valueOf(request.getMethod());

6
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -170,7 +170,9 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro @@ -170,7 +170,9 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
Object body = responseEntity.getBody();
if (responseEntity instanceof ResponseEntity) {
outputMessage.setStatusCode(((ResponseEntity<?>) responseEntity).getStatusCode());
if (HttpMethod.GET == inputMessage.getMethod() && isResourceNotModified(inputMessage, outputMessage)) {
HttpMethod method = inputMessage.getMethod();
boolean isGetOrHead = (HttpMethod.GET == method || HttpMethod.HEAD == method);
if (isGetOrHead && isResourceNotModified(inputMessage, outputMessage)) {
outputMessage.setStatusCode(HttpStatus.NOT_MODIFIED);
// Ensure headers are flushed, no body should be written.
outputMessage.flush();

20
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestConditionTests.java

@ -32,6 +32,7 @@ import static org.springframework.web.bind.annotation.RequestMethod.POST; @@ -32,6 +32,7 @@ import static org.springframework.web.bind.annotation.RequestMethod.POST;
/**
* @author Arjen Poutsma
* @author Rossen Stoyanchev
*/
public class RequestMethodsRequestConditionTests {
@ -61,6 +62,25 @@ public class RequestMethodsRequestConditionTests { @@ -61,6 +62,25 @@ public class RequestMethodsRequestConditionTests {
assertEquals(Collections.singleton(GET), actual.getContent());
}
@Test
public void methodHeadMatch() throws Exception {
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(GET, POST);
MockHttpServletRequest request = new MockHttpServletRequest("HEAD", "/foo");
RequestMethodsRequestCondition actual = condition.getMatchingCondition(request);
assertNotNull(actual);
assertEquals("GET should also match HEAD", Collections.singleton(HEAD), actual.getContent());
}
@Test
public void methodHeadNoMatch() throws Exception {
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(POST);
MockHttpServletRequest request = new MockHttpServletRequest("HEAD", "/foo");
RequestMethodsRequestCondition actual = condition.getMatchingCondition(request);
assertNull("HEAD should match only if GET is declared", actual);
}
@Test
public void noDeclaredMethodsMatchesAllMethods() {
RequestCondition condition = new RequestMethodsRequestCondition();

55
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java

@ -19,7 +19,6 @@ package org.springframework.web.servlet.mvc.method.annotation; @@ -19,7 +19,6 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@ -968,7 +967,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @@ -968,7 +967,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
public void httpEntity() throws ServletException, IOException {
initServletWithControllers(ResponseEntityController.class);
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/foo");
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo");
String requestBody = "Hello World";
request.setContent(requestBody.getBytes("UTF-8"));
request.addHeader("Content-Type", "text/plain; charset=utf-8");
@ -980,7 +979,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @@ -980,7 +979,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
assertEquals(requestBody, response.getContentAsString());
assertEquals("MyValue", response.getHeader("MyResponseHeader"));
request = new MockHttpServletRequest("PUT", "/bar");
request = new MockHttpServletRequest("GET", "/bar");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("MyValue", response.getHeader("MyResponseHeader"));
@ -1748,6 +1747,30 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @@ -1748,6 +1747,30 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
assertEquals("view", response.getForwardedUrl());
}
@Test
public void httpHead() throws ServletException, IOException {
initServletWithControllers(ResponseEntityController.class);
MockHttpServletRequest request = new MockHttpServletRequest("HEAD", "/baz");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(200, response.getStatus());
assertEquals("MyValue", response.getHeader("MyResponseHeader"));
assertEquals(4, response.getContentLength());
assertTrue(response.getContentAsByteArray().length == 0);
// Now repeat with GET
request = new MockHttpServletRequest("GET", "/baz");
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals(200, response.getStatus());
assertEquals("MyValue", response.getHeader("MyResponseHeader"));
assertEquals(4, response.getContentLength());
assertEquals("body", response.getContentAsString());
}
/*
* Controllers
@ -3019,25 +3042,27 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @@ -3019,25 +3042,27 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
@Controller
public static class ResponseEntityController {
@RequestMapping("/foo")
public ResponseEntity<String> foo(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
@RequestMapping(path = "/foo", method = RequestMethod.POST)
public ResponseEntity<String> foo(HttpEntity<byte[]> requestEntity) throws Exception {
assertNotNull(requestEntity);
assertEquals("MyValue", requestEntity.getHeaders().getFirst("MyRequestHeader"));
String requestBody = new String(requestEntity.getBody(), "UTF-8");
assertEquals("Hello World", requestBody);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>(requestBody, responseHeaders, HttpStatus.CREATED);
String body = new String(requestEntity.getBody(), "UTF-8");
assertEquals("Hello World", body);
URI location = new URI("/foo");
return ResponseEntity.created(location).header("MyResponseHeader", "MyValue").body(body);
}
@RequestMapping("/bar")
public ResponseEntity<String> bar() {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>(responseHeaders, HttpStatus.NOT_FOUND);
@RequestMapping(path = "/bar", method = RequestMethod.GET)
public ResponseEntity<Void> bar() {
return ResponseEntity.notFound().header("MyResponseHeader", "MyValue").build();
}
@RequestMapping("/baz")
public ResponseEntity<String> baz() {
return ResponseEntity.ok().header("MyResponseHeader", "MyValue").body("body");
}
}
@Controller

Loading…
Cancel
Save