diff --git a/spring-web/src/main/java/org/springframework/web/server/MethodNotAllowedException.java b/spring-web/src/main/java/org/springframework/web/server/MethodNotAllowedException.java index fe3f7caee3..394e5646ee 100644 --- a/spring-web/src/main/java/org/springframework/web/server/MethodNotAllowedException.java +++ b/spring-web/src/main/java/org/springframework/web/server/MethodNotAllowedException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.util.Assert; @@ -33,14 +34,21 @@ import org.springframework.util.Assert; @SuppressWarnings("serial") public class MethodNotAllowedException extends ResponseStatusException { - private String method; + private final String method; - private Set supportedMethods; + private final Set supportedMethods; - public MethodNotAllowedException(String method, Collection supportedMethods) { + public MethodNotAllowedException(HttpMethod method, Collection supportedMethods) { + this(method.name(), supportedMethods); + } + + public MethodNotAllowedException(String method, Collection supportedMethods) { super(HttpStatus.METHOD_NOT_ALLOWED, "Request method '" + method + "' not supported"); Assert.notNull(method, "'method' is required"); + if (supportedMethods == null) { + supportedMethods = Collections.emptySet(); + } this.method = method; this.supportedMethods = Collections.unmodifiableSet(new HashSet<>(supportedMethods)); } @@ -56,7 +64,7 @@ public class MethodNotAllowedException extends ResponseStatusException { /** * Return the list of supported HTTP methods. */ - public Set getSupportedMethods() { + public Set getSupportedMethods() { return supportedMethods; } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java index 99b557b809..5d4133bd9d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -21,10 +21,9 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.time.Instant; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -89,14 +88,10 @@ public class ResourceWebHandler implements WebHandler, InitializingBean, SmartInitializingSingleton { /** Set of supported HTTP methods */ - private static final Set SUPPORTED_METHODS = new LinkedHashSet<>(2); + private static final Set SUPPORTED_METHODS = EnumSet.of(HttpMethod.GET, HttpMethod.HEAD); private static final Log logger = LogFactory.getLog(ResourceWebHandler.class); - static { - SUPPORTED_METHODS.addAll(Arrays.asList("GET", "HEAD")); - } - private final List locations = new ArrayList<>(4); @@ -294,9 +289,9 @@ public class ResourceWebHandler } // Supported methods and required session - String httpMehtod = exchange.getRequest().getMethod().name(); - if (!SUPPORTED_METHODS.contains(httpMehtod)) { - return Mono.error(new MethodNotAllowedException(httpMehtod, SUPPORTED_METHODS)); + HttpMethod httpMethod = exchange.getRequest().getMethod(); + if (!SUPPORTED_METHODS.contains(httpMethod)) { + return Mono.error(new MethodNotAllowedException(httpMethod, SUPPORTED_METHODS)); } // Header phase diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java index 459708f668..96b2ecfee8 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -20,6 +20,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -218,12 +219,12 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe if (helper.hasMethodsMismatch()) { HttpMethod httpMethod = request.getMethod(); - Set methods = helper.getAllowedMethods(); + Set methods = helper.getAllowedMethods(); if (HttpMethod.OPTIONS.matches(httpMethod.name())) { HttpOptionsHandler handler = new HttpOptionsHandler(methods); return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD); } - throw new MethodNotAllowedException(httpMethod.name(), methods); + throw new MethodNotAllowedException(httpMethod, methods); } if (helper.hasConsumesMismatch()) { @@ -280,42 +281,42 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe * Any partial matches for "methods"? */ public boolean hasMethodsMismatch() { - return !this.partialMatches.stream(). - filter(PartialMatch::hasMethodsMatch).findAny().isPresent(); + return this.partialMatches.stream(). + noneMatch(PartialMatch::hasMethodsMatch); } /** * Any partial matches for "methods" and "consumes"? */ public boolean hasConsumesMismatch() { - return !this.partialMatches.stream(). - filter(PartialMatch::hasConsumesMatch).findAny().isPresent(); + return this.partialMatches.stream(). + noneMatch(PartialMatch::hasConsumesMatch); } /** * Any partial matches for "methods", "consumes", and "produces"? */ public boolean hasProducesMismatch() { - return !this.partialMatches.stream(). - filter(PartialMatch::hasProducesMatch).findAny().isPresent(); + return this.partialMatches.stream(). + noneMatch(PartialMatch::hasProducesMatch); } /** * Any partial matches for "methods", "consumes", "produces", and "params"? */ public boolean hasParamsMismatch() { - return !this.partialMatches.stream(). - filter(PartialMatch::hasParamsMatch).findAny().isPresent(); + return this.partialMatches.stream(). + noneMatch(PartialMatch::hasParamsMatch); } /** * Return declared HTTP methods. */ - public Set getAllowedMethods() { + public Set getAllowedMethods() { return this.partialMatches.stream(). flatMap(m -> m.getInfo().getMethodsCondition().getMethods().stream()). - map(requestMethod -> requestMethod.name()). - collect(Collectors.toCollection(LinkedHashSet::new)); + map(requestMethod -> HttpMethod.resolve(requestMethod.name())). + collect(Collectors.toSet()); } /** @@ -413,29 +414,23 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe private final HttpHeaders headers = new HttpHeaders(); - public HttpOptionsHandler(Set declaredMethods) { + public HttpOptionsHandler(Set declaredMethods) { this.headers.setAllow(initAllowedHttpMethods(declaredMethods)); } - private static Set initAllowedHttpMethods(Set declaredMethods) { - Set result = new LinkedHashSet<>(declaredMethods.size()); + private static Set initAllowedHttpMethods(Set declaredMethods) { if (declaredMethods.isEmpty()) { - for (HttpMethod method : HttpMethod.values()) { - if (!HttpMethod.TRACE.equals(method)) { - result.add(method); - } - } + return EnumSet.allOf(HttpMethod.class).stream() + .filter(method -> !method.equals(HttpMethod.TRACE)) + .collect(Collectors.toSet()); } else { - boolean hasHead = declaredMethods.contains("HEAD"); - for (String method : declaredMethods) { - result.add(HttpMethod.valueOf(method)); - if (!hasHead && "GET".equals(method)) { - result.add(HttpMethod.HEAD); - } + Set result = new LinkedHashSet<>(declaredMethods); + if (result.contains(HttpMethod.GET)) { + result.add(HttpMethod.HEAD); } + return result; } - return result; } @SuppressWarnings("unused") diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/HandshakeWebSocketService.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/HandshakeWebSocketService.java index 64511b7793..614a1f9842 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/HandshakeWebSocketService.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/HandshakeWebSocketService.java @@ -187,7 +187,7 @@ public class HandshakeWebSocketService implements WebSocketService, Lifecycle { } if (HttpMethod.GET != method) { - return Mono.error(new MethodNotAllowedException(method.name(), Collections.singleton("GET"))); + return Mono.error(new MethodNotAllowedException(method, Collections.singleton(HttpMethod.GET))); } if (!"WebSocket".equalsIgnoreCase(headers.getUpgrade())) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java index 49879cc654..8b932020fe 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java @@ -20,6 +20,7 @@ import java.lang.reflect.Method; import java.net.URI; import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.HashSet; import java.util.Map; import java.util.Optional; @@ -56,11 +57,7 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException; import org.springframework.web.server.support.HttpRequestPathHelper; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.get; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.HEAD; @@ -141,7 +138,7 @@ public class RequestMappingInfoHandlerMappingTests { Mono mono = this.handlerMapping.getHandler(exchange); assertError(mono, MethodNotAllowedException.class, - ex -> assertEquals(new HashSet<>(Arrays.asList("GET", "HEAD")), ex.getSupportedMethods())); + ex -> assertEquals(EnumSet.of(HttpMethod.GET, HttpMethod.HEAD), ex.getSupportedMethods())); } @Test // SPR-9603 @@ -192,10 +189,10 @@ public class RequestMappingInfoHandlerMappingTests { @Test public void getHandlerHttpOptions() throws Exception { - testHttpOptions("/foo", "GET,HEAD"); - testHttpOptions("/person/1", "PUT"); - testHttpOptions("/persons", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"); - testHttpOptions("/something", "PUT,POST"); + testHttpOptions("/foo", EnumSet.of(HttpMethod.GET, HttpMethod.HEAD)); + testHttpOptions("/person/1", EnumSet.of(HttpMethod.PUT)); + testHttpOptions("/persons", EnumSet.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH, HttpMethod.DELETE, HttpMethod.OPTIONS)); + testHttpOptions("/something", EnumSet.of(HttpMethod.PUT, HttpMethod.POST)); } @Test @@ -349,7 +346,7 @@ public class RequestMappingInfoHandlerMappingTests { ex.getSupportedMediaTypes())); } - private void testHttpOptions(String requestURI, String allowHeader) throws Exception { + private void testHttpOptions(String requestURI, Set allowedMethods) throws Exception { ServerWebExchange exchange = MockServerHttpRequest.options(requestURI).toExchange(); HandlerMethod handlerMethod = (HandlerMethod) this.handlerMapping.getHandler(exchange).block(); @@ -363,7 +360,7 @@ public class RequestMappingInfoHandlerMappingTests { Optional value = result.getReturnValue(); assertTrue(value.isPresent()); assertEquals(HttpHeaders.class, value.get().getClass()); - assertEquals(allowHeader, ((HttpHeaders) value.get()).getFirst("Allow")); + assertEquals(allowedMethods, ((HttpHeaders) value.get()).getAllow()); } private void testMediaTypeNotAcceptable(String url) throws Exception {