From 319e8e2c2f36ea146a03eaded6a39c575431fe6f Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 25 Jan 2016 15:58:18 -0500 Subject: [PATCH] Add allowHeader property to WebContentGenerator The WebContentGenerator now maintains an additional property that sub-classes can use for an "Allow" header in response to an HTTP OPTIONS request. This property is pre-initialized once at startup and does not have to rely on getSupportedMethods in addition to adding HTTP OPTIONS if not explicitly listed. Issue: SPR-13130 --- .../servlet/support/WebContentGenerator.java | 47 +++++++++++- .../support/WebContentGeneratorTests.java | 76 +++++++++++++++++++ 2 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 spring-webmvc/src/test/java/org/springframework/web/servlet/support/WebContentGeneratorTests.java diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java index dca9e5dcb2..3f578053c1 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java @@ -16,7 +16,9 @@ package org.springframework.web.servlet.support; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -25,6 +27,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.http.CacheControl; +import org.springframework.http.HttpMethod; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpSessionRequiredException; @@ -51,6 +55,7 @@ import org.springframework.web.context.support.WebApplicationObjectSupport; * @author Rod Johnson * @author Juergen Hoeller * @author Brian Clozel + * @author Rossen Stoyanchev * @see #setCacheSeconds * @see #setCacheControl * @see #setRequireSession @@ -76,6 +81,8 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport { /** Set of supported HTTP methods */ private Set supportedMethods; + private String allowHeader; + private boolean requireSession = false; private CacheControl cacheControl; @@ -115,6 +122,7 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport { this.supportedMethods.add(METHOD_HEAD); this.supportedMethods.add(METHOD_POST); } + initAllowHeader(); } /** @@ -122,7 +130,28 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport { * @param supportedMethods the supported HTTP methods for this content generator */ public WebContentGenerator(String... supportedMethods) { - this.supportedMethods = new LinkedHashSet(Arrays.asList(supportedMethods)); + setSupportedMethods(supportedMethods); + } + + private void initAllowHeader() { + Collection allowedMethods; + if (this.supportedMethods == null) { + allowedMethods = new ArrayList(HttpMethod.values().length - 1); + for (HttpMethod method : HttpMethod.values()) { + if (!HttpMethod.TRACE.equals(method)) { + allowedMethods.add(method.name()); + } + } + } + else if (this.supportedMethods.contains(HttpMethod.OPTIONS.name())) { + allowedMethods = this.supportedMethods; + } + else { + allowedMethods = new ArrayList(this.supportedMethods); + allowedMethods.add(HttpMethod.OPTIONS.name()); + + } + this.allowHeader = StringUtils.collectionToCommaDelimitedString(allowedMethods); } @@ -132,12 +161,13 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport { * unrestricted for general controllers and interceptors. */ public final void setSupportedMethods(String... methods) { - if (methods != null) { + if (!ObjectUtils.isEmpty(methods)) { this.supportedMethods = new LinkedHashSet(Arrays.asList(methods)); } else { this.supportedMethods = null; } + initAllowHeader(); } /** @@ -147,6 +177,19 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport { return StringUtils.toStringArray(this.supportedMethods); } + /** + * Return the "Allow" header value to use in response to an HTTP OPTIONS + * request based on the configured {@link #setSupportedMethods supported + * methods} also automatically adding "OPTIONS" to the list even if not + * present as a supported method. This means sub-classes don't have to + * explicitly list "OPTIONS" as a supported method as long as HTTP OPTIONS + * requests are handled before making a call to + * {@link #checkRequest(HttpServletRequest)}. + */ + protected String getAllowHeader() { + return this.allowHeader; + } + /** * Set whether a session should be required to handle requests. */ diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/WebContentGeneratorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/WebContentGeneratorTests.java new file mode 100644 index 0000000000..4ee9436bff --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/WebContentGeneratorTests.java @@ -0,0 +1,76 @@ +/* + * 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. + * 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.support; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for {@link WebContentGenerator}. + * @author Rossen Stoyanchev + */ +public class WebContentGeneratorTests { + + + @Test + public void getAllowHeaderWithConstructorTrue() throws Exception { + WebContentGenerator generator = new TestWebContentGenerator(true); + assertEquals("GET,HEAD,POST,OPTIONS", generator.getAllowHeader()); + } + + @Test + public void getAllowHeaderWithConstructorFalse() throws Exception { + WebContentGenerator generator = new TestWebContentGenerator(false); + assertEquals("GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS", generator.getAllowHeader()); + } + + @Test + public void getAllowHeaderWithSupportedMethodsConstructor() throws Exception { + WebContentGenerator generator = new TestWebContentGenerator("POST"); + assertEquals("POST,OPTIONS", generator.getAllowHeader()); + } + + @Test + public void getAllowHeaderWithSupportedMethodsSetter() throws Exception { + WebContentGenerator generator = new TestWebContentGenerator(); + generator.setSupportedMethods("POST"); + assertEquals("POST,OPTIONS", generator.getAllowHeader()); + } + + @Test + public void getAllowHeaderWithSupportedMethodsSetterEmpty() throws Exception { + WebContentGenerator generator = new TestWebContentGenerator(); + generator.setSupportedMethods(); + assertEquals("Effectively \"no restriction\" on supported methods", + "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS", generator.getAllowHeader()); + } + + + private static class TestWebContentGenerator extends WebContentGenerator { + + public TestWebContentGenerator() { + } + + public TestWebContentGenerator(boolean restrictDefaultSupportedMethods) { + super(restrictDefaultSupportedMethods); + } + + public TestWebContentGenerator(String... supportedMethods) { + super(supportedMethods); + } + } +}