Consistent and lenient HttpMethod resolution across all web modules

Issue: SPR-13776
This commit is contained in:
Juergen Hoeller
2015-12-09 12:26:44 +01:00
parent 34b596c6bf
commit 4261f34b63
29 changed files with 149 additions and 109 deletions

View File

@@ -470,7 +470,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
* Returns the value of the {@code Access-Control-Allow-Methods} response header.
* Return the value of the {@code Access-Control-Allow-Methods} response header.
*/
public List<HttpMethod> getAccessControlAllowMethods() {
List<HttpMethod> result = new ArrayList<HttpMethod>();
@@ -478,7 +478,10 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
if (value != null) {
String[] tokens = value.split(",\\s*");
for (String token : tokens) {
result.add(HttpMethod.valueOf(token));
HttpMethod resolved = HttpMethod.resolve(token);
if (resolved != null) {
result.add(resolved);
}
}
}
return result;
@@ -492,7 +495,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
* Returns the value of the {@code Access-Control-Allow-Origin} response header.
* Return the value of the {@code Access-Control-Allow-Origin} response header.
*/
public String getAccessControlAllowOrigin() {
return getFirst(ACCESS_CONTROL_ALLOW_ORIGIN);
@@ -550,11 +553,10 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
* Returns the value of the {@code Access-Control-Request-Method} request header.
* Return the value of the {@code Access-Control-Request-Method} request header.
*/
public HttpMethod getAccessControlRequestMethod() {
String value = getFirst(ACCESS_CONTROL_REQUEST_METHOD);
return (value != null ? HttpMethod.valueOf(value) : null);
return HttpMethod.resolve(getFirst(ACCESS_CONTROL_REQUEST_METHOD));
}
/**
@@ -615,12 +617,15 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
public Set<HttpMethod> getAllow() {
String value = getFirst(ALLOW);
if (!StringUtils.isEmpty(value)) {
List<HttpMethod> allowedMethod = new ArrayList<HttpMethod>(5);
List<HttpMethod> result = new LinkedList<HttpMethod>();
String[] tokens = value.split(",\\s*");
for (String token : tokens) {
allowedMethod.add(HttpMethod.valueOf(token));
HttpMethod resolved = HttpMethod.resolve(token);
if (resolved != null) {
result.add(resolved);
}
}
return EnumSet.copyOf(allowedMethod);
return EnumSet.copyOf(result);
}
else {
return EnumSet.noneOf(HttpMethod.class);
@@ -635,7 +640,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
* Returns the value of the {@code Cache-Control} header.
* Return the value of the {@code Cache-Control} header.
*/
public String getCacheControl() {
return getFirst(CACHE_CONTROL);
@@ -656,7 +661,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
* Returns the value of the {@code Connection} header.
* Return the value of the {@code Connection} header.
*/
public List<String> getConnection() {
return getFirstValueAsList(CONNECTION);
@@ -920,7 +925,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
* Returns the value of the {@code Range} header.
* Return the value of the {@code Range} header.
* <p>Returns an empty list when the range is unknown.
*/
public List<HttpRange> getRange() {
@@ -936,7 +941,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
}
/**
* Returns the value of the {@code Upgrade} header.
* Return the value of the {@code Upgrade} header.
*/
public String getUpgrade() {
return getFirst(UPGRADE);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2015 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.
@@ -16,16 +16,52 @@
package org.springframework.http;
import java.util.HashMap;
import java.util.Map;
/**
* Java 5 enumeration of HTTP request methods. Intended for use
* with {@link org.springframework.http.client.ClientHttpRequest}
* and {@link org.springframework.web.client.RestTemplate}.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
*/
public enum HttpMethod {
GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
private static final Map<String, HttpMethod> mappings = new HashMap<String, HttpMethod>(8);
static {
for (HttpMethod httpMethod : values()) {
mappings.put(httpMethod.name(), httpMethod);
}
}
/**
* Resolve the given method value to an {@code HttpMethod}.
* @param method the method value as a String
* @return the corresponding {@code HttpMethod}, or {@code null} if not found
* @since 4.2.4
*/
public static HttpMethod resolve(String method) {
return (method != null ? mappings.get(method) : null);
}
/**
* Determine whether this {@code HttpMethod} matches the given
* method value.
* @param method the method value as a String
* @return {@code true} if it matches, {@code false} otherwise
* @since 4.2.4
*/
public boolean matches(String method) {
return name().equals(method);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2015 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.
@@ -19,8 +19,8 @@ package org.springframework.http;
import java.net.URI;
/**
* Represents an HTTP request message, consisting of {@linkplain #getMethod() method}
* and {@linkplain #getURI() uri}.
* Represents an HTTP request message, consisting of
* {@linkplain #getMethod() method} and {@linkplain #getURI() uri}.
*
* @author Arjen Poutsma
* @since 3.1
@@ -29,13 +29,14 @@ public interface HttpRequest extends HttpMessage {
/**
* Return the HTTP method of the request.
* @return the HTTP method as an HttpMethod enum value
* @return the HTTP method as an HttpMethod enum value, or {@code null}
* if not resolvable (e.g. in case of a non-standard HTTP method)
*/
HttpMethod getMethod();
/**
* Return the URI of the request.
* @return the URI of the request
* @return the URI of the request (never {@code null})
*/
URI getURI();

View File

@@ -20,7 +20,6 @@ import java.net.URI;
import java.nio.charset.Charset;
import java.util.Arrays;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
@@ -104,8 +103,6 @@ public class RequestEntity<T> extends HttpEntity<T> {
*/
public RequestEntity(T body, MultiValueMap<String, String> headers, HttpMethod method, URI url) {
super(body, headers);
Assert.notNull(method, "'method' is required");
Assert.notNull(url, "'url' is required");
this.method = method;
this.url = url;
}

View File

@@ -68,7 +68,7 @@ final class HttpComponentsAsyncClientHttpRequest extends AbstractBufferingAsyncC
@Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(this.httpRequest.getMethod());
return HttpMethod.resolve(this.httpRequest.getMethod());
}
@Override

View File

@@ -66,7 +66,7 @@ final class HttpComponentsClientHttpRequest extends AbstractBufferingClientHttpR
@Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(this.httpRequest.getMethod());
return HttpMethod.resolve(this.httpRequest.getMethod());
}
@Override

View File

@@ -65,7 +65,7 @@ final class HttpComponentsStreamingClientHttpRequest extends AbstractClientHttpR
@Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(this.httpRequest.getMethod());
return HttpMethod.resolve(this.httpRequest.getMethod());
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -57,7 +57,7 @@ final class SimpleBufferingAsyncClientHttpRequest extends AbstractBufferingAsync
@Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(this.connection.getRequestMethod());
return HttpMethod.resolve(this.connection.getRequestMethod());
}
@Override
@@ -78,8 +78,8 @@ final class SimpleBufferingAsyncClientHttpRequest extends AbstractBufferingAsync
@Override
public ClientHttpResponse call() throws Exception {
SimpleBufferingClientHttpRequest.addHeaders(connection, headers);
// JDK < 1.8 doesn't support getOutputStream with HTTP DELETE
if (HttpMethod.DELETE.equals(getMethod()) && bufferedOutput.length == 0) {
// JDK <1.8 doesn't support getOutputStream with HTTP DELETE
if (HttpMethod.DELETE == getMethod() && bufferedOutput.length == 0) {
connection.setDoOutput(false);
}
if (connection.getDoOutput() && outputStreaming) {

View File

@@ -52,7 +52,7 @@ final class SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttp
@Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(this.connection.getRequestMethod());
return HttpMethod.resolve(this.connection.getRequestMethod());
}
@Override
@@ -69,8 +69,8 @@ final class SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttp
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
addHeaders(this.connection, headers);
// JDK < 1.8 doesn't support getOutputStream with HTTP DELETE
if (HttpMethod.DELETE.equals(getMethod()) && bufferedOutput.length == 0) {
// JDK <1.8 doesn't support getOutputStream with HTTP DELETE
if (HttpMethod.DELETE == getMethod() && bufferedOutput.length == 0) {
this.connection.setDoOutput(false);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -63,7 +63,7 @@ final class SimpleStreamingAsyncClientHttpRequest extends AbstractAsyncClientHtt
@Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(this.connection.getRequestMethod());
return HttpMethod.resolve(this.connection.getRequestMethod());
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -53,7 +53,7 @@ final class SimpleStreamingClientHttpRequest extends AbstractClientHttpRequest {
public HttpMethod getMethod() {
return HttpMethod.valueOf(this.connection.getRequestMethod());
return HttpMethod.resolve(this.connection.getRequestMethod());
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -55,8 +55,6 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
protected static final String FORM_CHARSET = "UTF-8";
private static final String METHOD_POST = "POST";
private final HttpServletRequest servletRequest;
@@ -84,7 +82,7 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
@Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(this.servletRequest.getMethod());
return HttpMethod.resolve(this.servletRequest.getMethod());
}
@Override
@@ -166,9 +164,22 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
}
}
private boolean isFormPost(HttpServletRequest request) {
return (request.getContentType() != null && request.getContentType().contains(FORM_CONTENT_TYPE) &&
METHOD_POST.equalsIgnoreCase(request.getMethod()));
@Override
public ServerHttpAsyncRequestControl getAsyncRequestControl(ServerHttpResponse response) {
if (this.asyncRequestControl == null) {
Assert.isInstanceOf(ServletServerHttpResponse.class, response);
ServletServerHttpResponse servletServerResponse = (ServletServerHttpResponse) response;
this.asyncRequestControl = new ServletServerHttpAsyncRequestControl(this, servletServerResponse);
}
return this.asyncRequestControl;
}
private static boolean isFormPost(HttpServletRequest request) {
String contentType = request.getContentType();
return (contentType != null && contentType.contains(FORM_CONTENT_TYPE) &&
HttpMethod.POST.matches(request.getMethod()));
}
/**
@@ -177,7 +188,7 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
* from the body, which can fail if any other code has used ServletRequest
* to access a parameter thus causing the input stream to be "consumed".
*/
private InputStream getBodyFromServletRequestParameters(HttpServletRequest request) throws IOException {
private static InputStream getBodyFromServletRequestParameters(HttpServletRequest request) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
Writer writer = new OutputStreamWriter(bos, FORM_CHARSET);
@@ -205,14 +216,4 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
return new ByteArrayInputStream(bos.toByteArray());
}
@Override
public ServerHttpAsyncRequestControl getAsyncRequestControl(ServerHttpResponse response) {
if (this.asyncRequestControl == null) {
Assert.isInstanceOf(ServletServerHttpResponse.class, response);
ServletServerHttpResponse servletServerResponse = (ServletServerHttpResponse) response;
this.asyncRequestControl = new ServletServerHttpAsyncRequestControl(this, servletServerResponse);
}
return this.asyncRequestControl;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 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.
@@ -17,8 +17,9 @@
package org.springframework.web;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletException;
@@ -105,11 +106,14 @@ public class HttpRequestMethodNotSupportedException extends ServletException {
* Return the actually supported HTTP methods, if known, as {@link HttpMethod} instances.
*/
public Set<HttpMethod> getSupportedHttpMethods() {
Set<HttpMethod> supportedMethods = new LinkedHashSet<HttpMethod>();
List<HttpMethod> supportedMethods = new LinkedList<HttpMethod>();
for (String value : this.supportedMethods) {
supportedMethods.add(HttpMethod.valueOf(value));
HttpMethod resolved = HttpMethod.resolve(value);
if (resolved != null) {
supportedMethods.add(resolved);
}
}
return Collections.unmodifiableSet(supportedMethods);
return EnumSet.copyOf(supportedMethods);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2015 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.
@@ -17,16 +17,14 @@
package org.springframework.web.bind.annotation;
/**
* Java 5 enumeration of HTTP request methods. Intended for use
* with the {@link RequestMapping#method()} attribute of the
* {@link RequestMapping} annotation.
* Java 5 enumeration of HTTP request methods. Intended for use with the
* {@link RequestMapping#method()} attribute of the {@link RequestMapping} annotation.
*
* <p>Note that, by default, {@link org.springframework.web.servlet.DispatcherServlet}
* supports GET, HEAD, POST, PUT, PATCH and DELETE only. DispatcherServlet will
* process TRACE and OPTIONS with the default HttpServlet behavior unless
* explicitly told to dispatch those request types as well: Check out
* the "dispatchOptionsRequest" and "dispatchTraceRequest" properties,
* switching them to "true" if necessary.
* process TRACE and OPTIONS with the default HttpServlet behavior unless explicitly
* told to dispatch those request types as well: Check out the "dispatchOptionsRequest"
* and "dispatchTraceRequest" properties, switching them to "true" if necessary.
*
* @author Juergen Hoeller
* @since 2.5

View File

@@ -106,7 +106,7 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
* @since 4.0.2
*/
public HttpMethod getHttpMethod() {
return HttpMethod.valueOf(getRequest().getMethod());
return HttpMethod.resolve(getRequest().getMethod());
}
@Override

View File

@@ -344,10 +344,13 @@ public class CorsConfiguration {
List<HttpMethod> result = new ArrayList<HttpMethod>(allowedMethods.size());
boolean allowed = false;
for (String method : allowedMethods) {
if (requestMethod.name().equals(method)) {
if (requestMethod.matches(method)) {
allowed = true;
}
result.add(HttpMethod.valueOf(method));
HttpMethod resolved = HttpMethod.resolve(method);
if (resolved != null) {
result.add(resolved);
}
}
return (allowed ? result : null);
}

View File

@@ -28,7 +28,7 @@ import org.springframework.http.HttpMethod;
* @author Sebastien Deleuze
* @since 4.2
*/
public class CorsUtils {
public abstract class CorsUtils {
/**
* Returns {@code true} if the request is a valid CORS one.
@@ -41,7 +41,7 @@ public class CorsUtils {
* Returns {@code true} if the request is a valid CORS pre-flight one.
*/
public static boolean isPreFlightRequest(HttpServletRequest request) {
return (isCorsRequest(request) && request.getMethod().equals(HttpMethod.OPTIONS.name()) &&
return (isCorsRequest(request) && HttpMethod.OPTIONS.matches(request.getMethod()) &&
request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD) != null);
}

View File

@@ -144,7 +144,7 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
protected boolean isEligibleForEtag(HttpServletRequest request, HttpServletResponse response,
int responseStatusCode, InputStream inputStream) {
if (responseStatusCode >= 200 && responseStatusCode < 300 && HttpMethod.GET.name().equals(request.getMethod())) {
if (responseStatusCode >= 200 && responseStatusCode < 300 && HttpMethod.GET.matches(request.getMethod())) {
String cacheControl = null;
if (servlet3Present) {
cacheControl = response.getHeader(HEADER_CACHE_CONTROL);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2015 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.
@@ -61,7 +61,7 @@ public abstract class AbstractMultipartHttpServletRequest extends HttpServletReq
@Override
public HttpMethod getRequestMethod() {
return HttpMethod.valueOf(getRequest().getMethod());
return HttpMethod.resolve(getRequest().getMethod());
}
@Override

View File

@@ -30,6 +30,8 @@ import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.springframework.http.HttpMethod;
/**
* {@link javax.servlet.http.HttpServletRequest} wrapper that caches all content read from
* the {@linkplain #getInputStream() input stream} and {@linkplain #getReader() reader},
@@ -46,8 +48,6 @@ public class ContentCachingRequestWrapper extends HttpServletRequestWrapper {
private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
private static final String METHOD_POST = "POST";
private final ByteArrayOutputStream cachedContent;
@@ -125,7 +125,7 @@ public class ContentCachingRequestWrapper extends HttpServletRequestWrapper {
private boolean isFormPost() {
String contentType = getContentType();
return (contentType != null && contentType.contains(FORM_CONTENT_TYPE) &&
METHOD_POST.equalsIgnoreCase(getMethod()));
HttpMethod.POST.matches(getMethod()));
}
private void writeRequestParametersToCachedContent() {