diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedErrorHandler.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedErrorHandler.java new file mode 100644 index 00000000000..42f4ffafb56 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedErrorHandler.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-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.boot.context.embedded.jetty; + +import java.io.IOException; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.ErrorHandler; + +/** + * Variation of Jetty's {@link ErrorHandler} that supports all {@link HttpMethod + * HttpMethods} rather than just {@code GET}, {@code POST} and {@code HEAD}. Jetty + * intentionally only + * supports a limited set of HTTP methods for error pages, however, Spring Boot + * prefers Tomcat, Jetty and Undertow to all behave in the same way. + * + * @author Phillip Webb + */ +class JettyEmbeddedErrorHandler extends ErrorHandler { + + private final ErrorHandler delegate; + + JettyEmbeddedErrorHandler(ErrorHandler delegate) { + this.delegate = delegate; + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, + HttpServletResponse response) throws IOException { + String method = request.getMethod(); + if (!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) + && !HttpMethod.HEAD.is(method)) { + request = new ErrorHttpServletRequest(request); + } + this.delegate.handle(target, baseRequest, request, response); + } + + private static class ErrorHttpServletRequest extends HttpServletRequestWrapper { + + private boolean simulateGetMethod = true; + + ErrorHttpServletRequest(HttpServletRequest request) { + super(request); + } + + @Override + public String getMethod() { + return (this.simulateGetMethod ? HttpMethod.GET.toString() + : super.getMethod()); + } + + @Override + public ServletContext getServletContext() { + this.simulateGetMethod = false; + return super.getServletContext(); + } + + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java index f2c274b16cd..ea756cd3756 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java @@ -411,6 +411,7 @@ public class JettyEmbeddedServletContainerFactory @Override public void configure(WebAppContext context) throws Exception { ErrorHandler errorHandler = context.getErrorHandler(); + context.setErrorHandler(new JettyEmbeddedErrorHandler(errorHandler)); addJettyErrorPages(errorHandler, getErrorPages()); } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java index ac997d36043..9b60faefc3a 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-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. @@ -343,6 +343,19 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { assertThat(getResponse(getLocalUrl("/bang")), equalTo("Hello World")); } + @Test + public void errorPageFromPutRequest() throws Exception { + AbstractEmbeddedServletContainerFactory factory = getFactory(); + factory.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/hello")); + this.container = factory.getEmbeddedServletContainer(exampleServletRegistration(), + errorServletRegistration()); + this.container.start(); + assertThat(getResponse(getLocalUrl("/hello"), HttpMethod.PUT), + equalTo("Hello World")); + assertThat(getResponse(getLocalUrl("/bang"), HttpMethod.PUT), + equalTo("Hello World")); + } + @Test public void basicSslFromClassPath() throws Exception { testBasicSslWithKeyStore("classpath:test.jks"); @@ -792,7 +805,12 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { protected String getResponse(String url, String... headers) throws IOException, URISyntaxException { - ClientHttpResponse response = getClientResponse(url, headers); + return getResponse(url, HttpMethod.GET, headers); + } + + protected String getResponse(String url, HttpMethod method, String... headers) + throws IOException, URISyntaxException { + ClientHttpResponse response = getClientResponse(url, method, headers); try { return StreamUtils.copyToString(response.getBody(), Charset.forName("UTF-8")); } @@ -804,7 +822,14 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { protected String getResponse(String url, HttpComponentsClientHttpRequestFactory requestFactory, String... headers) throws IOException, URISyntaxException { - ClientHttpResponse response = getClientResponse(url, requestFactory, headers); + return getResponse(url, HttpMethod.GET, requestFactory, headers); + } + + protected String getResponse(String url, HttpMethod method, + HttpComponentsClientHttpRequestFactory requestFactory, String... headers) + throws IOException, URISyntaxException { + ClientHttpResponse response = getClientResponse(url, method, requestFactory, + headers); try { return StreamUtils.copyToString(response.getBody(), Charset.forName("UTF-8")); } @@ -815,21 +840,27 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { protected ClientHttpResponse getClientResponse(String url, String... headers) throws IOException, URISyntaxException { - return getClientResponse(url, new HttpComponentsClientHttpRequestFactory() { - - @Override - protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) { - return AbstractEmbeddedServletContainerFactoryTests.this.httpClientContext; - } - - }, headers); + return getClientResponse(url, HttpMethod.GET, headers); } - protected ClientHttpResponse getClientResponse(String url, + protected ClientHttpResponse getClientResponse(String url, HttpMethod method, + String... headers) throws IOException, URISyntaxException { + return getClientResponse(url, method, + new HttpComponentsClientHttpRequestFactory() { + + @Override + protected HttpContext createHttpContext(HttpMethod httpMethod, + URI uri) { + return AbstractEmbeddedServletContainerFactoryTests.this.httpClientContext; + } + + }, headers); + } + + protected ClientHttpResponse getClientResponse(String url, HttpMethod method, HttpComponentsClientHttpRequestFactory requestFactory, String... headers) throws IOException, URISyntaxException { - ClientHttpRequest request = requestFactory.createRequest(new URI(url), - HttpMethod.GET); + ClientHttpRequest request = requestFactory.createRequest(new URI(url), method); request.getHeaders().add("Cookie", "JSESSIONID=" + "123"); for (String header : headers) { String[] parts = header.split(":");