diff --git a/framework-docs/src/docs/asciidoc/web/webflux.adoc b/framework-docs/src/docs/asciidoc/web/webflux.adoc index 4df52640ea6..8d08274b7ec 100644 --- a/framework-docs/src/docs/asciidoc/web/webflux.adoc +++ b/framework-docs/src/docs/asciidoc/web/webflux.adoc @@ -2015,10 +2015,9 @@ generally supported for all return values. to be written (however, `text/event-stream` must be requested or declared in the mapping through the `produces` attribute). -| Any other return value -| If a return value is not matched to any of the above, it is, by default, treated as a view - name, if it is `String` or `void` (default view name selection applies), or as a model - attribute to be added to the model, unless it is a simple type, as determined by +| Other return values +| If a return value remains unresolved in any other way, it is treated as a model + attribute, unless it is a simple type as determined by {api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty], in which case it remains unresolved. |=== diff --git a/framework-docs/src/docs/asciidoc/web/webmvc.adoc b/framework-docs/src/docs/asciidoc/web/webmvc.adoc index 2ca22c135e7..addc18fc918 100644 --- a/framework-docs/src/docs/asciidoc/web/webmvc.adoc +++ b/framework-docs/src/docs/asciidoc/web/webmvc.adoc @@ -2282,23 +2282,18 @@ supported for all return values. | Write to the response `OutputStream` asynchronously. Also supported as the body of a `ResponseEntity`. See <> and <>. -| Reactive types -- Reactor, RxJava, or others through `ReactiveAdapterRegistry` -| Alternative to `DeferredResult` with multi-value streams (for example, `Flux`, `Observable`) - collected to a `List`. - - For streaming scenarios (for example, `text/event-stream`, `application/json+stream`), - `SseEmitter` and `ResponseBodyEmitter` are used instead, where `ServletOutputStream` - blocking I/O is performed on a Spring MVC-managed thread and back pressure is applied - against the completion of each write. - - See <> and <>. - -| Any other return value -| Any return value that does not match any of the earlier values in this table and that - is a `String` or `void` is treated as a view name (default view name selection through - `RequestToViewNameTranslator` applies), provided it is not a simple type, as determined by - {api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]. - Values that are simple types remain unresolved. +| Reactor and other reactive types registered via `ReactiveAdapterRegistry` +| A single value type, e.g. `Mono`, is comparable to returning `DeferredResult`. + A multi-value type, e.g. `Flux`, may be treated as a stream depending on the requested + media type, e.g. "text/event-stream", "application/json+stream", or otherwise is + collected to a List and rendered as a single value. See <> and + <>. + +| Other return values +| If a return value remains unresolved in any other way, it is treated as a model + attribute, unless it is a simple type as determined by + {api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty], + in which case it remains unresolved. |=== diff --git a/spring-web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java b/spring-web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java index a8977cce81a..86dfcf9b993 100644 --- a/spring-web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java +++ b/spring-web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -54,6 +54,10 @@ public abstract class InterceptingHttpAccessor extends HttpAccessor { * Set the request interceptors that this accessor should use. *

The interceptors will get immediately sorted according to their * {@linkplain AnnotationAwareOrderComparator#sort(List) order}. + *

Note: This method does not support concurrent changes, + * and in most cases should not be called after initialization on startup. + * See also related note on {@link org.springframework.web.client.RestTemplate} + * regarding concurrent configuration changes. * @see #getRequestFactory() * @see AnnotationAwareOrderComparator */ diff --git a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java index 74f198aba31..45db5197eee 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -78,11 +78,17 @@ import org.springframework.web.util.UriTemplateHandler; /** * Synchronous client to perform HTTP requests, exposing a simple, template * method API over underlying HTTP client libraries such as the JDK - * {@code HttpURLConnection}, Apache HttpComponents, and others. + * {@code HttpURLConnection}, Apache HttpComponents, and others. RestTemplate + * offers templates for common scenarios by HTTP method, in addition to the + * generalized {@code exchange} and {@code execute} methods that support of + * less frequent cases. * - *

The RestTemplate offers templates for common scenarios by HTTP method, in - * addition to the generalized {@code exchange} and {@code execute} methods that - * support of less frequent cases. + *

RestTemplate is typically used as a shared component. However, its + * configuration does not support concurrent modification, and as such its + * configuration is typically prepared on startup. If necessary, you can create + * multiple, differently configured RestTemplate instances on startup. Such + * instances may use the same the underlying {@link ClientHttpRequestFactory} + * if they need to share HTTP client resources. * *

NOTE: As of 5.0 this class is in maintenance mode, with * only minor requests for changes and bugs to be accepted going forward. Please, diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java index 9d13740cc74..4620d7e68a3 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java @@ -17,6 +17,7 @@ package org.springframework.web.reactive.function.client; import java.net.URI; +import java.net.URISyntaxException; import java.nio.charset.Charset; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -53,6 +54,7 @@ import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; import org.springframework.web.reactive.function.BodyExtractor; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; @@ -713,10 +715,22 @@ class DefaultWebClient implements WebClient { } private Mono insertCheckpoint(Mono result, HttpStatusCode statusCode, HttpRequest request) { - HttpMethod httpMethod = request.getMethod(); + HttpMethod method = request.getMethod(); + URI uri = getUriToLog(request); + return result.checkpoint(statusCode + " from " + method + " " + uri + " [DefaultWebClient]"); + } + + private static URI getUriToLog(HttpRequest request) { URI uri = request.getURI(); - String description = statusCode + " from " + httpMethod + " " + uri + " [DefaultWebClient]"; - return result.checkpoint(description); + if (StringUtils.hasText(uri.getQuery())) { + try { + uri = new URI(uri.getScheme(), uri.getHost(), uri.getPath(), null); + } + catch (URISyntaxException ex) { + // ignore + } + } + return uri; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/AbstractWebSocketSession.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/AbstractWebSocketSession.java index 1fc285962c3..68a26806277 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/AbstractWebSocketSession.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/AbstractWebSocketSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -73,9 +73,12 @@ public abstract class AbstractWebSocketSession implements WebSocketSession { this.id = id; this.handshakeInfo = info; this.bufferFactory = bufferFactory; - this.attributes.putAll(info.getAttributes()); this.logPrefix = initLogPrefix(info, id); + info.getAttributes().entrySet().stream() + .filter(entry -> (entry.getKey() != null && entry.getValue() != null)) + .forEach(entry -> this.attributes.put(entry.getKey(), entry.getValue())); + if (logger.isDebugEnabled()) { logger.debug(getLogPrefix() + "Session id \"" + getId() + "\" for " + getHandshakeInfo().getUri()); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSession.java index 93dd6527538..63a403e46cf 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -62,7 +62,9 @@ public abstract class AbstractWebSocketSession implements NativeWebSocketSess */ public AbstractWebSocketSession(@Nullable Map attributes) { if (attributes != null) { - this.attributes.putAll(attributes); + attributes.entrySet().stream() + .filter(entry -> (entry.getKey() != null && entry.getValue() != null)) + .forEach(entry -> this.attributes.put(entry.getKey(), entry.getValue())); } } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/adapter/standard/StandardWebSocketSessionTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/adapter/standard/StandardWebSocketSessionTests.java index 100c6b2e654..b40955d2223 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/adapter/standard/StandardWebSocketSessionTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/adapter/standard/StandardWebSocketSessionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -36,6 +36,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; * * @author Rossen Stoyanchev */ +@SuppressWarnings("resource") public class StandardWebSocketSessionTests { private final HttpHeaders headers = new HttpHeaders(); @@ -53,7 +54,6 @@ public class StandardWebSocketSessionTests { } @Test - @SuppressWarnings("resource") public void getPrincipalWithNativeSession() { TestPrincipal user = new TestPrincipal("joe"); @@ -67,7 +67,6 @@ public class StandardWebSocketSessionTests { } @Test - @SuppressWarnings("resource") public void getPrincipalNone() { Session nativeSession = Mockito.mock(Session.class); given(nativeSession.getUserPrincipal()).willReturn(null); @@ -82,7 +81,6 @@ public class StandardWebSocketSessionTests { } @Test - @SuppressWarnings("resource") public void getAcceptedProtocol() { String protocol = "foo"; @@ -98,4 +96,14 @@ public class StandardWebSocketSessionTests { verifyNoMoreInteractions(nativeSession); } + @Test // gh-29315 + public void addAttributesWithNullKeyOrValue() { + this.attributes.put(null, "value"); + this.attributes.put("key", null); + this.attributes.put("foo", "bar"); + + assertThat(new StandardWebSocketSession(this.headers, this.attributes, null, null).getAttributes()) + .hasSize(1).containsEntry("foo", "bar"); + } + }