diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java index 95f6092ebc..914ce0d574 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java @@ -638,10 +638,15 @@ public class MockHttpServletResponse implements HttpServletResponse { return true; } else if (HttpHeaders.CONTENT_LANGUAGE.equalsIgnoreCase(name)) { + String contentLanguages = value.toString(); HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.CONTENT_LANGUAGE, value.toString()); + headers.add(HttpHeaders.CONTENT_LANGUAGE, contentLanguages); Locale language = headers.getContentLanguage(); setLocale(language != null ? language : Locale.getDefault()); + // Since setLocale() sets the Content-Language header to the given + // single Locale, we have to explicitly set the Content-Language header + // to the user-provided value. + doAddHeaderValue(HttpHeaders.CONTENT_LANGUAGE, contentLanguages, true); return true; } else if (HttpHeaders.SET_COOKIE.equalsIgnoreCase(name)) { @@ -660,12 +665,8 @@ public class MockHttpServletResponse implements HttpServletResponse { } private void doAddHeaderValue(String name, Object value, boolean replace) { - HeaderValueHolder header = this.headers.get(name); Assert.notNull(value, "Header value must not be null"); - if (header == null) { - header = new HeaderValueHolder(); - this.headers.put(name, header); - } + HeaderValueHolder header = this.headers.computeIfAbsent(name, key -> new HeaderValueHolder()); if (replace) { header.setValue(value); } diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java index 1dd6f117ac..2045ecb28a 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; +import java.util.Locale; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; @@ -30,6 +31,8 @@ import org.springframework.web.util.WebUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.springframework.http.HttpHeaders.CONTENT_LANGUAGE; import static org.springframework.http.HttpHeaders.CONTENT_LENGTH; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; import static org.springframework.http.HttpHeaders.LAST_MODIFIED; @@ -117,6 +120,26 @@ class MockHttpServletResponseTests { assertThat(response.getCharacterEncoding()).isEqualTo("UTF-8"); } + @Test // gh-25281 + void contentLanguageHeaderWithSingleValue() { + String contentLanguage = "it"; + response.setHeader(CONTENT_LANGUAGE, contentLanguage); + assertSoftly(softly -> { + softly.assertThat(response.getHeader(CONTENT_LANGUAGE)).isEqualTo(contentLanguage); + softly.assertThat(response.getLocale()).isEqualTo(Locale.ITALIAN); + }); + } + + @Test // gh-25281 + void contentLanguageHeaderWithMultipleValues() { + String contentLanguage = "it, en"; + response.setHeader(CONTENT_LANGUAGE, contentLanguage); + assertSoftly(softly -> { + softly.assertThat(response.getHeader(CONTENT_LANGUAGE)).isEqualTo(contentLanguage); + softly.assertThat(response.getLocale()).isEqualTo(Locale.ITALIAN); + }); + } + @Test void setContentTypeThenCharacterEncoding() { response.setContentType("test/plain"); diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java index 7f94c2265f..d9fe018ebe 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.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. @@ -424,7 +424,7 @@ public class MockHttpServletResponse implements HttpServletResponse { /** * Return the names of all specified headers as a Set of Strings. - *
As of Servlet 3.0, this method is also defined HttpServletResponse. + *
As of Servlet 3.0, this method is also defined in {@link HttpServletResponse}. * @return the {@code Set} of header name {@code Strings}, or an empty {@code Set} if none */ @Override @@ -435,7 +435,7 @@ public class MockHttpServletResponse implements HttpServletResponse { /** * Return the primary value for the given header as a String, if any. * Will return the first value in case of multiple values. - *
As of Servlet 3.0, this method is also defined in HttpServletResponse. + *
As of Servlet 3.0, this method is also defined in {@link HttpServletResponse}. * As of Spring 3.1, it returns a stringified value for Servlet 3.0 compatibility. * Consider using {@link #getHeaderValue(String)} for raw Object access. * @param name the name of the header @@ -450,7 +450,7 @@ public class MockHttpServletResponse implements HttpServletResponse { /** * Return all values for the given header as a List of Strings. - *
As of Servlet 3.0, this method is also defined in HttpServletResponse. + *
As of Servlet 3.0, this method is also defined in {@link HttpServletResponse}. * As of Spring 3.1, it returns a List of stringified values for Servlet 3.0 compatibility. * Consider using {@link #getHeaderValues(String)} for raw Object access. * @param name the name of the header @@ -638,10 +638,15 @@ public class MockHttpServletResponse implements HttpServletResponse { return true; } else if (HttpHeaders.CONTENT_LANGUAGE.equalsIgnoreCase(name)) { + String contentLanguages = value.toString(); HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.CONTENT_LANGUAGE, value.toString()); + headers.add(HttpHeaders.CONTENT_LANGUAGE, contentLanguages); Locale language = headers.getContentLanguage(); setLocale(language != null ? language : Locale.getDefault()); + // Since setLocale() sets the Content-Language header to the given + // single Locale, we have to explicitly set the Content-Language header + // to the user-provided value. + doAddHeaderValue(HttpHeaders.CONTENT_LANGUAGE, contentLanguages, true); return true; } else if (HttpHeaders.SET_COOKIE.equalsIgnoreCase(name)) { @@ -660,12 +665,8 @@ public class MockHttpServletResponse implements HttpServletResponse { } private void doAddHeaderValue(String name, Object value, boolean replace) { - HeaderValueHolder header = this.headers.get(name); Assert.notNull(value, "Header value must not be null"); - if (header == null) { - header = new HeaderValueHolder(); - this.headers.put(name, header); - } + HeaderValueHolder header = this.headers.computeIfAbsent(name, key -> new HeaderValueHolder()); if (replace) { header.setValue(value); }