From f88759c3c9dc048f4a0d98eab973cd65c99fabc1 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 3 Sep 2020 20:21:00 +0100 Subject: [PATCH 1/7] Shared read-only instances of UrlPathHelper UrlPathHelper is often created and used without customizations or with the same customizations. This commit introduces re-usable, instances. Effectively a backport of commit 23233c. See gh-25690 --- .../MockHttpServletRequestBuilder.java | 5 +- .../setup/PatternMappingFilterProxy.java | 6 +- .../web/filter/ForwardedHeaderFilter.java | 26 ++------- .../web/util/UrlPathHelper.java | 55 +++++++++++++++++++ .../condition/PatternsRequestCondition.java | 2 +- ...stractMessageConverterMethodProcessor.java | 16 +----- ...vletCookieValueMethodArgumentResolver.java | 4 +- .../servlet/resource/ResourceUrlProvider.java | 4 +- .../support/AbstractFlashMapManager.java | 4 +- .../support/ServletUriComponentsBuilder.java | 4 +- 10 files changed, 76 insertions(+), 50 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java index e315c948a8a..8b844e5b1d3 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java @@ -83,9 +83,6 @@ import org.springframework.web.util.UrlPathHelper; public class MockHttpServletRequestBuilder implements ConfigurableSmartRequestBuilder, Mergeable { - private static final UrlPathHelper urlPathHelper = new UrlPathHelper(); - - private final String method; private final URI url; @@ -781,7 +778,7 @@ public class MockHttpServletRequestBuilder } String extraPath = requestUri.substring(this.contextPath.length() + this.servletPath.length()); this.pathInfo = (StringUtils.hasText(extraPath) ? - urlPathHelper.decodeRequestString(request, extraPath) : null); + UrlPathHelper.defaultInstance.decodeRequestString(request, extraPath) : null); } request.setPathInfo(this.pathInfo); } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/PatternMappingFilterProxy.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/PatternMappingFilterProxy.java index 7ea42b8c7d7..1966016813c 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/PatternMappingFilterProxy.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/PatternMappingFilterProxy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -45,8 +45,6 @@ final class PatternMappingFilterProxy implements Filter { private static final String PATH_MAPPING_PATTERN = "/*"; - private static final UrlPathHelper urlPathHelper = new UrlPathHelper(); - private final Filter delegate; /** Patterns that require an exact match, e.g. "/test" */ @@ -96,7 +94,7 @@ final class PatternMappingFilterProxy implements Filter { throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; - String requestPath = urlPathHelper.getPathWithinApplication(httpRequest); + String requestPath = UrlPathHelper.defaultInstance.getPathWithinApplication(httpRequest); if (matches(requestPath)) { this.delegate.doFilter(request, response, filterChain); diff --git a/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java b/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java index 8b5f6a6d6aa..b9c4a4c3014 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java @@ -79,20 +79,11 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter { } - private final UrlPathHelper pathHelper; - private boolean removeOnly; private boolean relativeRedirects; - public ForwardedHeaderFilter() { - this.pathHelper = new UrlPathHelper(); - this.pathHelper.setUrlDecode(false); - this.pathHelper.setRemoveSemicolonContent(false); - } - - /** * Enables mode in which any "Forwarded" or "X-Forwarded-*" headers are * removed only and the information in them ignored. @@ -149,7 +140,7 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter { } else { HttpServletRequest wrappedRequest = - new ForwardedHeaderExtractingRequest(request, this.pathHelper); + new ForwardedHeaderExtractingRequest(request); HttpServletResponse wrappedResponse = this.relativeRedirects ? RelativeRedirectResponseWrapper.wrapIfNecessary(response, HttpStatus.SEE_OTHER) : @@ -230,7 +221,7 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter { private final ForwardedPrefixExtractor forwardedPrefixExtractor; - ForwardedHeaderExtractingRequest(HttpServletRequest request, UrlPathHelper pathHelper) { + ForwardedHeaderExtractingRequest(HttpServletRequest request) { super(request); HttpRequest httpRequest = new ServletServerHttpRequest(request); @@ -244,7 +235,7 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter { String baseUrl = this.scheme + "://" + this.host + (port == -1 ? "" : ":" + port); Supplier delegateRequest = () -> (HttpServletRequest) getRequest(); - this.forwardedPrefixExtractor = new ForwardedPrefixExtractor(delegateRequest, pathHelper, baseUrl); + this.forwardedPrefixExtractor = new ForwardedPrefixExtractor(delegateRequest, baseUrl); } @@ -296,8 +287,6 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter { private final Supplier delegate; - private final UrlPathHelper pathHelper; - private final String baseUrl; private String actualRequestUri; @@ -316,14 +305,10 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter { * @param delegateRequest supplier for the current * {@link HttpServletRequestWrapper#getRequest() delegate request} which * may change during a forward (e.g. Tomcat. - * @param pathHelper the path helper instance * @param baseUrl the host, scheme, and port based on forwarded headers */ - public ForwardedPrefixExtractor( - Supplier delegateRequest, UrlPathHelper pathHelper, String baseUrl) { - + public ForwardedPrefixExtractor(Supplier delegateRequest, String baseUrl) { this.delegate = delegateRequest; - this.pathHelper = pathHelper; this.baseUrl = baseUrl; this.actualRequestUri = delegateRequest.get().getRequestURI(); @@ -353,7 +338,8 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter { @Nullable private String initRequestUri() { if (this.forwardedPrefix != null) { - return this.forwardedPrefix + this.pathHelper.getPathWithinApplication(this.delegate.get()); + return this.forwardedPrefix + + UrlPathHelper.rawPathInstance.getPathWithinApplication(this.delegate.get()); } return null; } diff --git a/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java b/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java index fcac7db744e..79673637dbc 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java +++ b/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java @@ -28,6 +28,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -70,6 +71,8 @@ public class UrlPathHelper { private String defaultEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING; + private boolean readOnly = false; + /** * Whether URL lookups should always use the full path within the current @@ -81,6 +84,7 @@ public class UrlPathHelper { *

By default this is set to "false". */ public void setAlwaysUseFullPath(boolean alwaysUseFullPath) { + checkReadOnly(); this.alwaysUseFullPath = alwaysUseFullPath; } @@ -103,6 +107,7 @@ public class UrlPathHelper { * @see java.net.URLDecoder#decode(String, String) */ public void setUrlDecode(boolean urlDecode) { + checkReadOnly(); this.urlDecode = urlDecode; } @@ -119,6 +124,7 @@ public class UrlPathHelper { *

Default is "true". */ public void setRemoveSemicolonContent(boolean removeSemicolonContent) { + checkReadOnly(); this.removeSemicolonContent = removeSemicolonContent; } @@ -126,6 +132,7 @@ public class UrlPathHelper { * Whether configured to remove ";" (semicolon) content from the request URI. */ public boolean shouldRemoveSemicolonContent() { + checkReadOnly(); return this.removeSemicolonContent; } @@ -143,6 +150,7 @@ public class UrlPathHelper { * @see WebUtils#DEFAULT_CHARACTER_ENCODING */ public void setDefaultEncoding(String defaultEncoding) { + checkReadOnly(); this.defaultEncoding = defaultEncoding; } @@ -153,6 +161,17 @@ public class UrlPathHelper { return this.defaultEncoding; } + /** + * Switch to read-only mode where further configuration changes are not allowed. + */ + private void setReadOnly() { + this.readOnly = true; + } + + private void checkReadOnly() { + Assert.isTrue(!this.readOnly, "This instance cannot be modified"); + } + /** * Return the mapping lookup path for the given request, within the current @@ -640,4 +659,40 @@ public class UrlPathHelper { return !flagToUse; } + + + /** + * Shared, read-only instance with defaults. The following apply: + *

    + *
  • {@code alwaysUseFullPath=false} + *
  • {@code urlDecode=true} + *
  • {@code removeSemicolon=true} + *
  • {@code defaultEncoding=}{@link WebUtils#DEFAULT_CHARACTER_ENCODING} + *
+ */ + public static final UrlPathHelper defaultInstance = new UrlPathHelper(); + + static { + defaultInstance.setReadOnly(); + } + + + /** + * Shared, read-only instance for the full, encoded path. The following apply: + *
    + *
  • {@code alwaysUseFullPath=true} + *
  • {@code urlDecode=false} + *
  • {@code removeSemicolon=false} + *
  • {@code defaultEncoding=}{@link WebUtils#DEFAULT_CHARACTER_ENCODING} + *
+ */ + public static final UrlPathHelper rawPathInstance = new UrlPathHelper(); + + static { + rawPathInstance.setAlwaysUseFullPath(true); + rawPathInstance.setUrlDecode(false); + rawPathInstance.setRemoveSemicolonContent(false); + rawPathInstance.setReadOnly(); + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java index 3616aa33a9d..4062a053405 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java @@ -121,7 +121,7 @@ public class PatternsRequestCondition extends AbstractRequestCondition fileExtensions) { this.patterns = initPatterns(patterns); - this.pathHelper = urlPathHelper != null ? urlPathHelper : new UrlPathHelper(); + this.pathHelper = urlPathHelper != null ? urlPathHelper : UrlPathHelper.defaultInstance; this.pathMatcher = pathMatcher != null ? pathMatcher : new AntPathMatcher(); this.useSuffixPatternMatch = useSuffixPatternMatch; this.useTrailingSlashMatch = useTrailingSlashMatch; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java index dbda3378dfd..932ff78a057 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java @@ -91,16 +91,6 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe new ParameterizedTypeReference>() { }.getType(); - private static final UrlPathHelper decodingUrlPathHelper = new UrlPathHelper(); - - private static final UrlPathHelper rawUrlPathHelper = new UrlPathHelper(); - - static { - rawUrlPathHelper.setRemoveSemicolonContent(false); - rawUrlPathHelper.setUrlDecode(false); - } - - private final ContentNegotiationManager contentNegotiationManager; private final Set safeExtensions = new HashSet<>(); @@ -430,7 +420,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe } HttpServletRequest servletRequest = request.getServletRequest(); - String requestUri = rawUrlPathHelper.getOriginatingRequestUri(servletRequest); + String requestUri = UrlPathHelper.rawPathInstance.getOriginatingRequestUri(servletRequest); int index = requestUri.lastIndexOf('/') + 1; String filename = requestUri.substring(index); @@ -442,10 +432,10 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe filename = filename.substring(0, index); } - filename = decodingUrlPathHelper.decodeRequestString(servletRequest, filename); + filename = UrlPathHelper.defaultInstance.decodeRequestString(servletRequest, filename); String ext = StringUtils.getFilenameExtension(filename); - pathParams = decodingUrlPathHelper.decodeRequestString(servletRequest, pathParams); + pathParams = UrlPathHelper.defaultInstance.decodeRequestString(servletRequest, pathParams); String extInPathParams = StringUtils.getFilenameExtension(pathParams); if (!safeExtension(servletRequest, ext) || !safeExtension(servletRequest, extInPathParams)) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java index d1928fb9f65..97c66a581a3 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2020 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. @@ -37,7 +37,7 @@ import org.springframework.web.util.WebUtils; */ public class ServletCookieValueMethodArgumentResolver extends AbstractCookieValueMethodArgumentResolver { - private UrlPathHelper urlPathHelper = new UrlPathHelper(); + private UrlPathHelper urlPathHelper = UrlPathHelper.defaultInstance; public ServletCookieValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java index 8abd1b5ef97..690de8df2ee 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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,7 +54,7 @@ public class ResourceUrlProvider implements ApplicationListener Date: Thu, 3 Sep 2020 20:35:06 +0100 Subject: [PATCH 2/7] Avoid unnecessary parsing of path params See gh-25690 --- .../web/util/UrlPathHelper.java | 13 +------ .../springframework/web/util/WebUtils.java | 5 ++- .../web/util/UrlPathHelperTests.java | 14 ++------ .../web/util/WebUtilsTests.java | 34 +++++++++++++------ 4 files changed, 31 insertions(+), 35 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java b/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java index 79673637dbc..8e6118c1a51 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java +++ b/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java @@ -556,8 +556,7 @@ public class UrlPathHelper { * @return the updated URI string */ public String removeSemicolonContent(String requestUri) { - return (this.removeSemicolonContent ? - removeSemicolonContentInternal(requestUri) : removeJsessionid(requestUri)); + return (this.removeSemicolonContent ? removeSemicolonContentInternal(requestUri) : requestUri); } private String removeSemicolonContentInternal(String requestUri) { @@ -571,16 +570,6 @@ public class UrlPathHelper { return requestUri; } - private String removeJsessionid(String requestUri) { - int startIndex = requestUri.toLowerCase().indexOf(";jsessionid="); - if (startIndex != -1) { - int endIndex = requestUri.indexOf(';', startIndex + 12); - String start = requestUri.substring(0, startIndex); - requestUri = (endIndex != -1) ? start + requestUri.substring(endIndex) : start; - } - return requestUri; - } - /** * Decode the given URI path variables via {@link #decodeRequestString} unless * {@link #setUrlDecode} is set to {@code true} in which case it is assumed diff --git a/spring-web/src/main/java/org/springframework/web/util/WebUtils.java b/spring-web/src/main/java/org/springframework/web/util/WebUtils.java index fec0cfdbe87..8a0e76127ae 100644 --- a/spring-web/src/main/java/org/springframework/web/util/WebUtils.java +++ b/spring-web/src/main/java/org/springframework/web/util/WebUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -733,6 +733,9 @@ public abstract class WebUtils { int index = pair.indexOf('='); if (index != -1) { String name = pair.substring(0, index); + if (name.equalsIgnoreCase("jsessionid")) { + continue; + } String rawValue = pair.substring(index + 1); for (String value : StringUtils.commaDelimitedListToStringArray(rawValue)) { result.add(name, value); diff --git a/spring-web/src/test/java/org/springframework/web/util/UrlPathHelperTests.java b/spring-web/src/test/java/org/springframework/web/util/UrlPathHelperTests.java index 82a069ae128..0797120c32d 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UrlPathHelperTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UrlPathHelperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -126,22 +126,14 @@ public class UrlPathHelperTests { } @Test - public void getRequestKeepSemicolonContent() throws UnsupportedEncodingException { + public void getRequestKeepSemicolonContent() { helper.setRemoveSemicolonContent(false); request.setRequestURI("/foo;a=b;c=d"); assertThat(helper.getRequestUri(request)).isEqualTo("/foo;a=b;c=d"); request.setRequestURI("/foo;jsessionid=c0o7fszeb1"); - assertThat(helper.getRequestUri(request)).as("jsessionid should always be removed").isEqualTo("/foo"); - - request.setRequestURI("/foo;a=b;jsessionid=c0o7fszeb1;c=d"); - assertThat(helper.getRequestUri(request)).as("jsessionid should always be removed").isEqualTo("/foo;a=b;c=d"); - - // SPR-10398 - - request.setRequestURI("/foo;a=b;JSESSIONID=c0o7fszeb1;c=d"); - assertThat(helper.getRequestUri(request)).as("JSESSIONID should always be removed").isEqualTo("/foo;a=b;c=d"); + assertThat(helper.getRequestUri(request)).isEqualTo("/foo;jsessionid=c0o7fszeb1"); } @Test diff --git a/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java index 68226877b16..998af1c9eb1 100644 --- a/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -16,7 +16,6 @@ package org.springframework.web.util; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -65,29 +64,42 @@ public class WebUtilsTests { MultiValueMap variables; variables = WebUtils.parseMatrixVariables(null); - assertThat(variables.size()).isEqualTo(0); + assertThat(variables).hasSize(0); variables = WebUtils.parseMatrixVariables("year"); - assertThat(variables.size()).isEqualTo(1); + assertThat(variables).hasSize(1); assertThat(variables.getFirst("year")).isEqualTo(""); variables = WebUtils.parseMatrixVariables("year=2012"); - assertThat(variables.size()).isEqualTo(1); + assertThat(variables).hasSize(1); assertThat(variables.getFirst("year")).isEqualTo("2012"); variables = WebUtils.parseMatrixVariables("year=2012;colors=red,blue,green"); - assertThat(variables.size()).isEqualTo(2); - assertThat(variables.get("colors")).isEqualTo(Arrays.asList("red", "blue", "green")); + assertThat(variables).hasSize(2); + assertThat(variables.get("colors")).containsExactly("red", "blue", "green"); assertThat(variables.getFirst("year")).isEqualTo("2012"); variables = WebUtils.parseMatrixVariables(";year=2012;colors=red,blue,green;"); - assertThat(variables.size()).isEqualTo(2); - assertThat(variables.get("colors")).isEqualTo(Arrays.asList("red", "blue", "green")); + assertThat(variables).hasSize(2); + assertThat(variables.get("colors")).containsExactly("red", "blue", "green"); assertThat(variables.getFirst("year")).isEqualTo("2012"); variables = WebUtils.parseMatrixVariables("colors=red;colors=blue;colors=green"); - assertThat(variables.size()).isEqualTo(1); - assertThat(variables.get("colors")).isEqualTo(Arrays.asList("red", "blue", "green")); + assertThat(variables).hasSize(1); + assertThat(variables.get("colors")).containsExactly("red", "blue", "green"); + + variables = WebUtils.parseMatrixVariables("jsessionid=c0o7fszeb1"); + assertThat(variables).isEmpty(); + + variables = WebUtils.parseMatrixVariables("a=b;jsessionid=c0o7fszeb1;c=d"); + assertThat(variables).hasSize(2); + assertThat(variables.get("a")).containsExactly("b"); + assertThat(variables.get("c")).containsExactly("d"); + + variables = WebUtils.parseMatrixVariables("a=b;jsessionid=c0o7fszeb1;c=d"); + assertThat(variables).hasSize(2); + assertThat(variables.get("a")).containsExactly("b"); + assertThat(variables.get("c")).containsExactly("d"); } @Test From c19fc9d53f09cccb1399409abf5ba609e9f88602 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 7 Sep 2020 17:03:50 +0100 Subject: [PATCH 3/7] Switch to Reactor Dysprosium snapshots See gh-25730 --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d38d9744b65..f1d8032ca77 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ configure(allprojects) { project -> imports { mavenBom "com.fasterxml.jackson:jackson-bom:2.10.5" mavenBom "io.netty:netty-bom:4.1.51.Final" - mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR10" + mavenBom "io.projectreactor:reactor-bom:Dysprosium-BUILD-SNAPSHOT" mavenBom "io.rsocket:rsocket-bom:1.0.1" mavenBom "org.eclipse.jetty:jetty-bom:9.4.31.v20200723" mavenBom "org.jetbrains.kotlin:kotlin-bom:1.3.72" @@ -279,6 +279,7 @@ configure(allprojects) { project -> repositories { mavenCentral() maven { url "https://repo.spring.io/libs-spring-framework-build" } + maven { url "https://repo.spring.io/snapshot" } // Reactor } } configurations.all { From b1d84067cdee05152ae37ff57ec17953aea27832 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 7 Sep 2020 17:28:20 +0100 Subject: [PATCH 4/7] UriComponentsBuilder Javadoc update Closes gh-25604 --- .../web/util/UriComponentsBuilder.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index e98856167fa..5ac9f927a48 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -186,7 +186,13 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { } /** - * Create a builder that is initialized with the given {@code URI}. + * Create a builder that is initialized from the given {@code URI}. + *

Note: the components in the resulting builder will be + * in fully encoded (raw) form and further changes must also supply values + * that are fully encoded, for example via methods in {@link UriUtils}. + * In addition please use {@link #build(boolean)} with a value of "true" to + * build the {@link UriComponents} instance in order to indicate that the + * components are encoded. * @param uri the URI to initialize with * @return the new {@code UriComponentsBuilder} */ @@ -387,11 +393,13 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { } /** - * Build a {@code UriComponents} instance from the various components - * contained in this builder. - * @param encoded whether all the components set in this builder are - * encoded ({@code true}) or not ({@code false}) + * Variant of {@link #build()} to create a {@link UriComponents} instance + * when components are already fully encoded. This is useful for example if + * the builder was created via {@link UriComponentsBuilder#fromUri(URI)}. + * @param encoded whether the components in this builder are already encoded * @return the URI components + * @throws IllegalArgumentException if any of the components contain illegal + * characters that should have been encoded. */ public UriComponents build(boolean encoded) { return buildInternal(encoded ? From b6ff12d2f5a140785611f46ba6af2f36c1785d49 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 7 Sep 2020 17:34:01 +0100 Subject: [PATCH 5/7] Expose protected method in AbstractJackson2HttpMessageConverter Closes gh-25509 --- .../json/AbstractJackson2HttpMessageConverter.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java index 8a6d2023000..78ca9fb8d31 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java @@ -285,7 +285,15 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener } } - private static Charset getCharset(@Nullable MediaType contentType) { + /** + * Return the charset to use for JSON input. + *

By default this is either the charset from the input {@code MediaType} + * or otherwise falling back on {@code UTF-8}. + * @param contentType the content type of the HTTP input message + * @return the charset to use + * @since 5.1.18 + */ + protected static Charset getCharset(@Nullable MediaType contentType) { if (contentType != null && contentType.getCharset() != null) { return contentType.getCharset(); } From 94c91c9e9c608ffcbd7418eb68f6c3b0743fbaa7 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 7 Sep 2020 21:17:12 +0100 Subject: [PATCH 6/7] Explain how to provide serialization view programmatically Closes gh-25596 --- src/docs/asciidoc/web/webmvc.adoc | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index 6c1cd68586f..34d84028206 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -3403,6 +3403,39 @@ which allow rendering only a subset of all fields in an `Object`. To use it with NOTE: `@JsonView` allows an array of view classes, but you can specify only one per controller method. If you need to activate multiple views, you can use a composite interface. +If you want to do the above programmatically, instead of declaring an `@JsonView` annotation, +wrap the return value with `MappingJacksonValue` and use it to supply the serialization view: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @RestController + public class UserController { + + @GetMapping("/user") + public MappingJacksonValue getUser() { + User user = new User("eric", "7!jd#h23"); + MappingJacksonValue value = new MappingJacksonValue(user); + value.setSerializationView(User.WithoutPasswordView.class); + return value; + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @RestController + class UserController { + + @GetMapping("/user") + fun getUser(): MappingJacksonValue { + val value = MappingJacksonValue(User("eric", "7!jd#h23")) + value.serializationView = User.WithoutPasswordView::class.java + return value + } + } +---- + For controllers that rely on view resolution, you can add the serialization view class to the model, as the following example shows: From d616c6632d69d1b4e756b7691eab3bf688ce6898 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 7 Sep 2020 21:26:24 +0100 Subject: [PATCH 7/7] Remove session on 4xx response from WebSocket handshake Closes gh-25608 --- .../transport/TransportHandlingSockJsService.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java index 40a19881077..272700b49a4 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -34,6 +34,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.lang.Nullable; import org.springframework.scheduling.TaskScheduler; import org.springframework.util.Assert; @@ -270,6 +271,7 @@ public class TransportHandlingSockJsService extends AbstractSockJsService implem } SockJsSession session = this.sessions.get(sessionId); + boolean isNewSession = false; if (session == null) { if (transportHandler instanceof SockJsSessionFactory) { Map attributes = new HashMap<>(); @@ -278,6 +280,7 @@ public class TransportHandlingSockJsService extends AbstractSockJsService implem } SockJsSessionFactory sessionFactory = (SockJsSessionFactory) transportHandler; session = createSockJsSession(sessionId, sessionFactory, handler, attributes); + isNewSession = true; } else { response.setStatusCode(HttpStatus.NOT_FOUND); @@ -311,6 +314,14 @@ public class TransportHandlingSockJsService extends AbstractSockJsService implem } transportHandler.handleRequest(request, response, handler, session); + + if (isNewSession && (response instanceof ServletServerHttpResponse)) { + int status = ((ServletServerHttpResponse) response).getServletResponse().getStatus(); + if (HttpStatus.valueOf(status).is4xxClientError()) { + this.sessions.remove(sessionId); + } + } + chain.applyAfterHandshake(request, response, null); } catch (SockJsException ex) {