diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java index 76b4b8f1e66..1772350e3de 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java @@ -74,7 +74,8 @@ public class DefaultSubscriptionRegistry extends AbstractSubscriptionRegistry { /** Static evaluation context to reuse. */ private static final EvaluationContext messageEvalContext = - SimpleEvaluationContext.forPropertyAccessors(new SimpMessageHeaderPropertyAccessor()).build(); + SimpleEvaluationContext.forPropertyAccessors(new SimpMessageHeaderPropertyAccessor()) + .withAssignmentDisabled().build(); private PathMatcher pathMatcher = new AntPathMatcher(); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java index 6c5aeb5f17e..b468dce18e2 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java @@ -456,7 +456,7 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler accessor.setLogin(this.systemLogin); accessor.setPasscode(this.systemPasscode); accessor.setHeartbeat(this.systemHeartbeatSendInterval, this.systemHeartbeatReceiveInterval); - accessor.setHost(getVirtualHost() != null ? getVirtualHost() : getRelayHost()); + accessor.setHost(getVirtualHost() != null ? getVirtualHost() : null); accessor.setSessionId(SYSTEM_SESSION_ID); if (logger.isDebugEnabled()) { logger.debug("Forwarding " + accessor.getShortLogMessage(EMPTY_PAYLOAD)); @@ -579,7 +579,7 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler stompHeaderAccessor = (stompHeaderAccessor.isMutable() ? stompHeaderAccessor : StompHeaderAccessor.wrap(message)); stompHeaderAccessor.setLogin(this.clientLogin); stompHeaderAccessor.setPasscode(this.clientPasscode); - stompHeaderAccessor.setHost(getVirtualHost() != null ? getVirtualHost() : getRelayHost()); + stompHeaderAccessor.setHost(getVirtualHost() != null ? getVirtualHost() : null); RelayConnectionHandler handler = new RelayConnectionHandler(sessionId, stompHeaderAccessor); this.connectionHandlers.put(sessionId, handler); this.stats.incrementConnectCount(); diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java index eb712b655cf..5daa6647a8a 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java @@ -98,6 +98,27 @@ class StompBrokerRelayMessageHandlerTests { assertThat(headers2.getHost()).isEqualTo("ABC"); } + @Test + void virtualHostDefault() { + Message connectMessage = connectMessage("sess1", "joe"); + MessageHeaderAccessor.getAccessor(connectMessage, StompHeaderAccessor.class).setHost("ABC"); + + this.brokerRelay.start(); + this.brokerRelay.handleMessage(connectMessage("sess1", "joe")); + + assertThat(this.tcpClient.getSentMessages()).hasSize(2); + + StompHeaderAccessor headers1 = this.tcpClient.getSentHeaders(0); + assertThat(headers1.getCommand()).isEqualTo(StompCommand.CONNECT); + assertThat(headers1.getSessionId()).isEqualTo(StompBrokerRelayMessageHandler.SYSTEM_SESSION_ID); + assertThat(headers1.getHost()).isNull(); + + StompHeaderAccessor headers2 = this.tcpClient.getSentHeaders(1); + assertThat(headers2.getCommand()).isEqualTo(StompCommand.CONNECT); + assertThat(headers2.getSessionId()).isEqualTo("sess1"); + assertThat(headers2.getHost()).isNull(); + } + @Test void loginAndPasscode() { this.brokerRelay.setSystemLogin("syslogin"); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorUriHelper.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorUriHelper.java index ec728485603..d9023959594 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorUriHelper.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorUriHelper.java @@ -135,19 +135,25 @@ abstract class ReactorUriHelper { if (c == '/' || c == '?' || c == '#') { break; } - if (c == ':' && (i + 2 < length)) { - if (uri.charAt(i + 1) == '/' && uri.charAt(i + 2) == '/') { - for (int j = i + 3; j < length; j++) { - c = uri.charAt(j); - if (c == '/' || c == '?' || c == '#') { - builder.append(uri, j, length); - return; - } + if (hasAuthority(uri, c, i, length)) { + for (i = i + 3; i < length; i++) { + c = uri.charAt(i); + if (c == '/' || c == '?' || c == '#') { + builder.append(uri, i, length); + return; } - return; } + return; } } + if (length > 0 && uri.charAt(0) != '/') { + builder.append('/'); + } builder.append(uri); } + + private static boolean hasAuthority(String uri, char c, int i, int length) { + return (c == ':' && (i + 2 < length) && (uri.charAt(i + 1) == '/' && uri.charAt(i + 2) == '/')); + } + } diff --git a/spring-web/src/main/java/org/springframework/web/accept/AbstractMappingContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/AbstractMappingContentNegotiationStrategy.java index 3b48770ce90..6793856f7c2 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/AbstractMappingContentNegotiationStrategy.java +++ b/spring-web/src/main/java/org/springframework/web/accept/AbstractMappingContentNegotiationStrategy.java @@ -148,9 +148,11 @@ public abstract class AbstractMappingContentNegotiationStrategy extends MappingM throws HttpMediaTypeNotAcceptableException { if (!isUseRegisteredExtensionsOnly()) { - Optional mediaType = MediaTypeFactory.getMediaType("file." + key); - if (mediaType.isPresent()) { - return mediaType.get(); + if (key.indexOf('.') == -1) { + Optional mediaType = MediaTypeFactory.getMediaType("file." + key); + if (mediaType.isPresent()) { + return mediaType.get(); + } } } if (isIgnoreUnknownExtensions()) { diff --git a/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java b/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java index 72672f913eb..28680bdebf9 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java +++ b/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java @@ -57,12 +57,7 @@ public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExten public MappingMediaTypeFileExtensionResolver(@Nullable Map mediaTypes) { if (mediaTypes != null) { Set allFileExtensions = CollectionUtils.newHashSet(mediaTypes.size()); - mediaTypes.forEach((extension, mediaType) -> { - String lowerCaseExtension = extension.toLowerCase(Locale.ROOT); - this.mediaTypes.put(lowerCaseExtension, mediaType); - addFileExtension(mediaType, lowerCaseExtension); - allFileExtensions.add(lowerCaseExtension); - }); + mediaTypes.forEach(this::addMapping); this.allFileExtensions.addAll(allFileExtensions); } } @@ -79,19 +74,15 @@ public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExten /** * Map an extension to a MediaType. Ignore if extension already mapped. */ - protected void addMapping(String extension, MediaType mediaType) { - MediaType previous = this.mediaTypes.putIfAbsent(extension, mediaType); + protected void addMapping(String key, MediaType mediaType) { + key = key.toLowerCase(Locale.ROOT); + MediaType previous = this.mediaTypes.putIfAbsent(key, mediaType); if (previous == null) { - addFileExtension(mediaType, extension); - this.allFileExtensions.add(extension); + this.fileExtensions.computeIfAbsent(mediaType, k -> new CopyOnWriteArrayList<>()).add(key); + this.allFileExtensions.add(key); } } - private void addFileExtension(MediaType mediaType, String extension) { - this.fileExtensions.computeIfAbsent(mediaType, key -> new CopyOnWriteArrayList<>()) - .add(extension); - } - @Override public List resolveFileExtensions(MediaType mediaType) { diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java index 5197e9971f8..19d50236c23 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java @@ -453,221 +453,172 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements @Override public void flush() { + invokeWithLock(this.delegate::flush); + } + + private void invokeWithLock(Runnable runnable) { int level = this.asyncWebRequest.tryObtainLock(); if (level > -1) { try { - this.delegate.flush(); + runnable.run(); } finally { - releaseLock(level); + if (level > 0) { + this.asyncWebRequest.stateLock.unlock(); + } } } } @Override public void close() { - int level = this.asyncWebRequest.tryObtainLock(); - if (level > -1) { - try { - this.delegate.close(); - } - finally { - releaseLock(level); - } - } - } - - @Override - public boolean checkError() { - return this.delegate.checkError(); + invokeWithLock(this.delegate::close); } @Override public void write(int c) { - int level = this.asyncWebRequest.tryObtainLock(); - if (level > -1) { - try { - this.delegate.write(c); - } - finally { - releaseLock(level); - } - } + invokeWithLock(() -> this.delegate.write(c)); } @Override public void write(char[] buf, int off, int len) { - int level = this.asyncWebRequest.tryObtainLock(); - if (level > -1) { - try { - this.delegate.write(buf, off, len); - } - finally { - releaseLock(level); - } - } + invokeWithLock(() -> this.delegate.write(buf, off, len)); } @Override public void write(char[] buf) { - this.delegate.write(buf); + write(buf, 0, buf.length); } @Override public void write(String s, int off, int len) { - int level = this.asyncWebRequest.tryObtainLock(); - if (level > -1) { - try { - this.delegate.write(s, off, len); - } - finally { - releaseLock(level); - } - } + invokeWithLock(() -> this.delegate.write(s, off, len)); } @Override public void write(String s) { - this.delegate.write(s); - } - - private void releaseLock(int level) { - if (level > 0) { - this.asyncWebRequest.stateLock.unlock(); - } + write(s, 0, s.length()); } // Plain delegates @Override public void print(boolean b) { - this.delegate.print(b); + write(String.valueOf(b)); } @Override public void print(char c) { - this.delegate.print(c); + write(c); } @Override public void print(int i) { - this.delegate.print(i); + write(String.valueOf(i)); } @Override public void print(long l) { - this.delegate.print(l); + write(String.valueOf(l)); } @Override public void print(float f) { - this.delegate.print(f); + write(String.valueOf(f)); } @Override public void print(double d) { - this.delegate.print(d); + write(String.valueOf(d)); } @Override public void print(char[] s) { - this.delegate.print(s); + write(s); } @Override public void print(String s) { - this.delegate.print(s); + write(String.valueOf(s)); } @Override public void print(Object obj) { - this.delegate.print(obj); + write(String.valueOf(obj)); } @Override public void println() { - this.delegate.println(); + invokeWithLock(this.delegate::println); } @Override public void println(boolean x) { - this.delegate.println(x); + invokeWithLock(() -> this.delegate.println(x)); } @Override public void println(char x) { - this.delegate.println(x); + invokeWithLock(() -> this.delegate.println(x)); } @Override public void println(int x) { - this.delegate.println(x); + invokeWithLock(() -> this.delegate.println(x)); } @Override public void println(long x) { - this.delegate.println(x); + invokeWithLock(() -> this.delegate.println(x)); } @Override public void println(float x) { - this.delegate.println(x); + invokeWithLock(() -> this.delegate.println(x)); } @Override public void println(double x) { - this.delegate.println(x); + invokeWithLock(() -> this.delegate.println(x)); } @Override public void println(char[] x) { - this.delegate.println(x); + invokeWithLock(() -> this.delegate.println(x)); } @Override public void println(String x) { - this.delegate.println(x); + invokeWithLock(() -> this.delegate.println(x)); } @Override public void println(Object x) { - this.delegate.println(x); + invokeWithLock(() -> this.delegate.println(x)); } @Override public PrintWriter printf(String format, Object... args) { - return this.delegate.printf(format, args); + return format(format, args); } @Override public PrintWriter printf(Locale l, String format, Object... args) { - return this.delegate.printf(l, format, args); + return format(format, args); } @Override public PrintWriter format(String format, Object... args) { - return this.delegate.format(format, args); + invokeWithLock(() -> this.delegate.format(format, args)); + return this; } @Override public PrintWriter format(Locale l, String format, Object... args) { - return this.delegate.format(l, format, args); + invokeWithLock(() -> this.delegate.format(format, args)); + return this; } - @Override - public PrintWriter append(CharSequence csq) { - return this.delegate.append(csq); - } - - @Override - public PrintWriter append(CharSequence csq, int start, int end) { - return this.delegate.append(csq, start, end); - } - - @Override - public PrintWriter append(char c) { - return this.delegate.append(c); - } } diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ReactorUriHelperTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ReactorUriHelperTests.java index 765e185741b..138b24d5b24 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ReactorUriHelperTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ReactorUriHelperTests.java @@ -35,10 +35,11 @@ import static org.mockito.Mockito.mock; */ class ReactorUriHelperTests { + private final HttpServerRequest nettyRequest = mock(); + + @Test void hostnameWithZoneId() throws URISyntaxException { - HttpServerRequest nettyRequest = mock(); - given(nettyRequest.scheme()).willReturn("http"); given(nettyRequest.hostName()).willReturn("fe80::a%en1"); given(nettyRequest.hostPort()).willReturn(80); @@ -50,7 +51,36 @@ class ReactorUriHelperTests { .hasPort(-1) .hasPath("/") .hasToString("http://[fe80::a%25en1]/"); + } + @Test + void requestUriWithScheme() throws URISyntaxException { + given(nettyRequest.scheme()).willReturn("http"); + given(nettyRequest.hostName()).willReturn("example.org"); + given(nettyRequest.hostPort()).willReturn(80); + given(nettyRequest.uri()).willReturn("http://example.org/path"); + + URI uri = ReactorUriHelper.createUri(nettyRequest); + assertThat(uri).hasScheme("http") + .hasHost("example.org") + .hasPort(-1) + .hasPath("/path") + .hasToString("http://example.org/path"); + } + + @Test + void requestUriWithoutLeadingSlash() throws URISyntaxException { + given(nettyRequest.scheme()).willReturn("http"); + given(nettyRequest.hostName()).willReturn("example.org"); + given(nettyRequest.hostPort()).willReturn(80); + given(nettyRequest.uri()).willReturn("foo/bar"); + + URI uri = ReactorUriHelper.createUri(nettyRequest); + assertThat(uri).hasScheme("http") + .hasHost("example.org") + .hasPort(-1) + .hasPath("/foo/bar") + .hasToString("http://example.org/foo/bar"); } @ParameterizedTest(name = "{displayName}({arguments})") @@ -61,8 +91,6 @@ class ReactorUriHelperTests { "'' | /", }) void forwardedPrefix(String forwardedPrefixHeader, String expectedPath) throws URISyntaxException { - HttpServerRequest nettyRequest = mock(); - given(nettyRequest.scheme()).willReturn("https"); given(nettyRequest.hostName()).willReturn("localhost"); given(nettyRequest.hostPort()).willReturn(443); diff --git a/spring-web/src/test/java/org/springframework/web/accept/MappingContentNegotiationStrategyTests.java b/spring-web/src/test/java/org/springframework/web/accept/MappingContentNegotiationStrategyTests.java index 70770d0ae00..8bea3227389 100644 --- a/spring-web/src/test/java/org/springframework/web/accept/MappingContentNegotiationStrategyTests.java +++ b/spring-web/src/test/java/org/springframework/web/accept/MappingContentNegotiationStrategyTests.java @@ -23,9 +23,11 @@ import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; +import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.context.request.NativeWebRequest; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * A test fixture with a test subclass of AbstractMappingContentNegotiationStrategy. @@ -36,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat; class MappingContentNegotiationStrategyTests { @Test - void resolveMediaTypes() throws Exception { + void resolveMediaTypesFromRegisteredExtensions() throws Exception { Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy("json", mapping); @@ -47,27 +49,7 @@ class MappingContentNegotiationStrategyTests { } @Test - void resolveMediaTypesNoMatch() throws Exception { - Map mapping = null; - TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy("blah", mapping); - - List mediaTypes = strategy.resolveMediaTypes(null); - - assertThat(mediaTypes).isEqualTo(ContentNegotiationStrategy.MEDIA_TYPE_ALL_LIST); - } - - @Test - void resolveMediaTypesNoKey() throws Exception { - Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); - TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy(null, mapping); - - List mediaTypes = strategy.resolveMediaTypes(null); - - assertThat(mediaTypes).isEqualTo(ContentNegotiationStrategy.MEDIA_TYPE_ALL_LIST); - } - - @Test - void resolveMediaTypesHandleNoMatch() throws Exception { + void resolveMediaTypesViaFactory() throws Exception { Map mapping = null; TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy("xml", mapping); @@ -77,6 +59,34 @@ class MappingContentNegotiationStrategyTests { assertThat(mediaTypes.get(0).toString()).isEqualTo("application/xml"); } + @Test + void resolveMediaTypesUnknownKey() { + Map mapping = null; + TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy("blah", mapping); + + assertThatThrownBy(() -> strategy.resolveMediaTypes(null)) + .isInstanceOf(HttpMediaTypeNotAcceptableException.class); + } + + @Test + void resolveMediaTypesInvalidKey() { + Map mapping = null; + TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy("not.json", mapping); + + assertThatThrownBy(() -> strategy.resolveMediaTypes(null)) + .isInstanceOf(HttpMediaTypeNotAcceptableException.class); + } + + @Test + void resolveMediaTypesNullKey() throws Exception { + Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); + TestMappingContentNegotiationStrategy strategy = new TestMappingContentNegotiationStrategy(null, mapping); + + List mediaTypes = strategy.resolveMediaTypes(null); + + assertThat(mediaTypes).isEqualTo(ContentNegotiationStrategy.MEDIA_TYPE_ALL_LIST); + } + private static class TestMappingContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy { @@ -92,10 +102,6 @@ class MappingContentNegotiationStrategyTests { return this.extension; } - @Override - protected MediaType handleNoMatch(NativeWebRequest request, String mappingKey) { - return "xml".equals(mappingKey) ? MediaType.APPLICATION_XML : null; - } } } diff --git a/spring-web/src/test/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolverTests.java b/spring-web/src/test/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolverTests.java index 88c348d0973..652b87b6a78 100644 --- a/spring-web/src/test/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolverTests.java @@ -68,13 +68,17 @@ class MappingMediaTypeFileExtensionResolverTests { } @Test - void allFileExtensions() { - Map mappings = new HashMap<>(); - mappings.put("json", MediaType.APPLICATION_JSON); - mappings.put("JsOn", MediaType.APPLICATION_JSON); - mappings.put("jSoN", MediaType.APPLICATION_JSON); + void mappingsAreCaseInsensitive() { + Map map = new HashMap<>(); + map.put("json", MediaType.APPLICATION_JSON); + map.put("JsOn", MediaType.APPLICATION_JSON); + map.put("jSoN", MediaType.APPLICATION_JSON); + + MappingMediaTypeFileExtensionResolver resolver = new MappingMediaTypeFileExtensionResolver(map); + map.forEach(resolver::addMapping); - MappingMediaTypeFileExtensionResolver resolver = new MappingMediaTypeFileExtensionResolver(mappings); assertThat(resolver.getAllFileExtensions()).containsExactly("json"); } + + } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java index dcc48f9a66c..b2e9acf8ca3 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java @@ -334,10 +334,8 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap logger.trace(exchange.getLogPrefix() + matches.size() + " matching mappings: " + matches); } if (CorsUtils.isPreFlightRequest(exchange.getRequest())) { - for (Match match : matches) { - if (match.hasCorsConfig()) { - return PREFLIGHT_AMBIGUOUS_MATCH; - } + if (matches.stream().allMatch(Match::hasCorsConfig)) { + return PREFLIGHT_AMBIGUOUS_MATCH; } } else { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/JettyWebSocketSession.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/JettyWebSocketSession.java index 84f6d056ec5..ae6826eaace 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/JettyWebSocketSession.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/JettyWebSocketSession.java @@ -149,7 +149,7 @@ public class JettyWebSocketSession extends AbstractWebSocketSession { if (JettyWebSocketSession.this.handlerCompletionSink != null) { JettyWebSocketSession.this.handlerCompletionSink.tryEmitError(error); } - getDelegate().close(StatusCode.SERVER_ERROR, error.getMessage(), Callback.NOOP); + getDelegate().close(StatusCode.SERVER_ERROR, null, Callback.NOOP); } void onHandleComplete() { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/HandlerMethodMappingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/HandlerMethodMappingTests.java index ba7488c4bed..664de757bdf 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/HandlerMethodMappingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/HandlerMethodMappingTests.java @@ -131,8 +131,8 @@ class HandlerMethodMappingTests { @Test // gh-26490 void ambiguousMatchOnPreFlightRequestWithCorsConfig() throws Exception { - this.mapping.registerMapping("/f?o", this.handler, this.method1); - this.mapping.registerMapping("/fo?", this.handler, this.handler.getClass().getMethod("corsHandlerMethod")); + this.mapping.registerMapping("/f?o", this.handler, this.handler.getClass().getMethod("corsHandlerMethod1")); + this.mapping.registerMapping("/fo?", this.handler, this.handler.getClass().getMethod("corsHandlerMethod2")); MockServerWebExchange exchange = MockServerWebExchange.from( MockServerHttpRequest.options("https://example.org/foo") @@ -256,7 +256,12 @@ class HandlerMethodMappingTests { @RequestMapping @CrossOrigin(originPatterns = "*") - public void corsHandlerMethod() { + public void corsHandlerMethod1() { + } + + @RequestMapping + @CrossOrigin(originPatterns = "*") + public void corsHandlerMethod2() { } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java index 2c7a8bdd62d..0261368eca5 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java @@ -409,10 +409,8 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap logger.trace(matches.size() + " matching mappings: " + matches); } if (CorsUtils.isPreFlightRequest(request)) { - for (Match match : matches) { - if (match.hasCorsConfig()) { - return PREFLIGHT_AMBIGUOUS_MATCH; - } + if (matches.stream().allMatch(Match::hasCorsConfig)) { + return PREFLIGHT_AMBIGUOUS_MATCH; } } else { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/UrlFilenameViewController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/UrlFilenameViewController.java index 6b6a3876527..07ecbfeee24 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/UrlFilenameViewController.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/UrlFilenameViewController.java @@ -28,21 +28,17 @@ import org.springframework.web.util.ServletRequestPathUtils; /** * Simple {@code Controller} implementation that transforms the virtual - * path of a URL into a view name and returns that view. + * path of a URL into a view name and returns that view, optionally prepending a + * {@link #setPrefix prefix} and/or appending a {@link #setSuffix suffix}. * - *

Can optionally prepend a {@link #setPrefix prefix} and/or append a - * {@link #setSuffix suffix} to build the viewname from the URL filename. - * - *

Find some examples below: + *

This is intended for use with individually mapped static content pages + * such as a home page or an index page. See examples below: *

    *
  1. {@code "/index" -> "index"}
  2. *
  3. {@code "/index.html" -> "index"}
  4. *
  5. {@code "/index.html"} + prefix {@code "pre_"} and suffix {@code "_suf" -> "pre_index_suf"}
  6. - *
  7. {@code "/products/view.html" -> "products/view"}
  8. *
* - *

Thanks to David Barri for suggesting prefix/suffix support! - * * @author Alef Arendsen * @author Juergen Hoeller * @author Rob Harrop diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurer.java index 8d62b7264c7..df3932e05d6 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurer.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurer.java @@ -35,6 +35,7 @@ import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.io.Resource; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.springframework.web.servlet.resource.ResourceHandlerUtils; /** * An extension of Groovy's {@link groovy.text.markup.TemplateConfiguration} and @@ -223,7 +224,11 @@ public class GroovyMarkupConfigurer extends TemplateConfiguration @Override public URL resolveTemplate(String templatePath) throws IOException { Assert.state(this.classLoader != null, "No template ClassLoader available"); - return GroovyMarkupConfigurer.this.resolveTemplate(this.classLoader, templatePath); + String path = ResourceHandlerUtils.normalizeInputPath(templatePath); + if (ResourceHandlerUtils.shouldIgnoreInputPath(path)) { + throw new IOException("Invalid template path:" + templatePath); + } + return GroovyMarkupConfigurer.this.resolveTemplate(this.classLoader, path); } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java index 953594f6d14..2574aa2016b 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java @@ -142,8 +142,8 @@ class HandlerMethodMappingTests { @Test // gh-26490 void ambiguousMatchOnPreFlightRequestWithCorsConfig() throws Exception { - this.mapping.registerMapping("/f?o", this.handler, this.method1); - this.mapping.registerMapping("/fo?", this.handler, this.handler.getClass().getMethod("corsHandlerMethod")); + this.mapping.registerMapping("/f?o", this.handler, this.handler.getClass().getMethod("corsHandlerMethod1")); + this.mapping.registerMapping("/fo?", this.handler, this.handler.getClass().getMethod("corsHandlerMethod2")); MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/foo"); request.addHeader(HttpHeaders.ORIGIN, "https://domain.com"); @@ -166,7 +166,7 @@ class HandlerMethodMappingTests { @Test void abortInterceptorInPreFlightRequestWithCorsConfig() throws Exception { - this.mapping.registerMapping("/foo", this.handler, this.handler.getClass().getMethod("corsHandlerMethod")); + this.mapping.registerMapping("/foo", this.handler, this.handler.getClass().getMethod("corsHandlerMethod1")); MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/foo"); request.addParameter("abort", "true"); @@ -297,7 +297,7 @@ class HandlerMethodMappingTests { @Test void registerCustomHandlerMethod() throws Exception { this.mapping.setCustomerHandlerMethod(true); - this.mapping.registerMapping("/foo", this.handler, this.handler.getClass().getMethod("corsHandlerMethod")); + this.mapping.registerMapping("/foo", this.handler, this.handler.getClass().getMethod("corsHandlerMethod1")); MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/foo"); request.addParameter("abort", "true"); @@ -418,7 +418,13 @@ class HandlerMethodMappingTests { @RequestMapping @CrossOrigin(originPatterns = "*") - public void corsHandlerMethod() { + public void corsHandlerMethod1() { + } + + @RequestMapping + @CrossOrigin(originPatterns = "*") + public void corsHandlerMethod2() { } } + }