diff --git a/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java index b3e8543af30..3a1f9f10294 100644 --- a/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java +++ b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java @@ -27,6 +27,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.Spliterator; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; @@ -286,6 +287,11 @@ public class LinkedCaseInsensitiveMap implements Map, Serializable return entrySet; } + @Override + public void forEach(BiConsumer action) { + this.targetMap.forEach(action); + } + @Override public LinkedCaseInsensitiveMap clone() { return new LinkedCaseInsensitiveMap<>(this); diff --git a/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java b/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java index 0d51b56117c..8c158ecf0fc 100644 --- a/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java +++ b/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2023 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. @@ -22,6 +22,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; import org.springframework.lang.Nullable; @@ -69,15 +70,13 @@ public class MultiValueMapAdapter implements MultiValueMap, Serializ @Override public void addAll(K key, List values) { - List currentValues = this.targetMap.computeIfAbsent(key, k -> new ArrayList<>(1)); + List currentValues = this.targetMap.computeIfAbsent(key, k -> new ArrayList<>(values.size())); currentValues.addAll(values); } @Override public void addAll(MultiValueMap values) { - for (Entry> entry : values.entrySet()) { - addAll(entry.getKey(), entry.getValue()); - } + values.forEach(this::addAll); } @Override @@ -138,6 +137,12 @@ public class MultiValueMapAdapter implements MultiValueMap, Serializ return this.targetMap.put(key, value); } + @Override + @Nullable + public List putIfAbsent(K key, List value) { + return this.targetMap.putIfAbsent(key, value); + } + @Override @Nullable public List remove(Object key) { @@ -169,6 +174,11 @@ public class MultiValueMapAdapter implements MultiValueMap, Serializ return this.targetMap.entrySet(); } + @Override + public void forEach(BiConsumer> action) { + this.targetMap.forEach(action); + } + @Override public boolean equals(@Nullable Object other) { return (this == other || this.targetMap.equals(other)); diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index ff60fead827..7178894b4b9 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -40,6 +40,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.StringJoiner; +import java.util.function.BiConsumer; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -1821,6 +1822,16 @@ public class HttpHeaders implements MultiValueMap, Serializable return this.headers.entrySet(); } + @Override + public void forEach(BiConsumer> action) { + this.headers.forEach(action); + } + + @Override + public List putIfAbsent(String key, List value) { + return this.headers.putIfAbsent(key, value); + } + @Override public boolean equals(@Nullable Object obj) { diff --git a/spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java b/spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java index 6cd7fc4fdc7..7c16b19d679 100644 --- a/spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -23,6 +23,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; import java.util.stream.Collectors; import org.springframework.lang.Nullable; @@ -155,4 +156,9 @@ class ReadOnlyHttpHeaders extends HttpHeaders { Collections::unmodifiableSet)); } + @Override + public void forEach(BiConsumer> action) { + this.headers.forEach((k, vs) -> action.accept(k, Collections.unmodifiableList(vs))); + } + } diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java index 332183457e6..d681b88f1d3 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -18,7 +18,6 @@ package org.springframework.http.client.reactive; import java.net.URI; import java.nio.file.Path; -import java.util.Collection; import io.netty.buffer.ByteBuf; import io.netty.handler.codec.http.cookie.DefaultCookie; @@ -129,9 +128,10 @@ class ReactorClientHttpRequest extends AbstractClientHttpRequest implements Zero @Override protected void applyCookies() { - getCookies().values().stream().flatMap(Collection::stream) - .map(cookie -> new DefaultCookie(cookie.getName(), cookie.getValue())) - .forEach(this.request::addCookie); + getCookies().values().forEach(values -> values.forEach(value -> { + DefaultCookie cookie = new DefaultCookie(value.getName(), value.getValue()); + this.request.addCookie(cookie); + })); } @Override diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java index 787d5e1575f..23f736fa461 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -18,7 +18,6 @@ package org.springframework.http.client.reactive; import java.net.URI; import java.nio.file.Path; -import java.util.Collection; import io.netty5.buffer.Buffer; import io.netty5.handler.codec.http.headers.DefaultHttpCookiePair; @@ -130,9 +129,10 @@ class ReactorNetty2ClientHttpRequest extends AbstractClientHttpRequest implement @Override protected void applyCookies() { - getCookies().values().stream().flatMap(Collection::stream) - .map(cookie -> new DefaultHttpCookiePair(cookie.getName(), cookie.getValue())) - .forEach(this.request::addCookie); + getCookies().values().forEach(values -> values.forEach(value -> { + DefaultHttpCookiePair cookie = new DefaultHttpCookiePair(value.getName(), value.getValue()); + this.request.addCookie(cookie); + })); } @Override diff --git a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java index 5d2765692fb..ad9f9b9945a 100644 --- a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java +++ b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java @@ -704,6 +704,31 @@ public class HttpHeadersTests { assertThat(readOnlyHttpHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys); } + @Test + void readOnlyHttpHeadersCopyOrderTest() { + headers.add("aardvark", "enigma"); + headers.add("beaver", "enigma"); + headers.add("cat", "enigma"); + headers.add("dog", "enigma"); + headers.add("elephant", "enigma"); + + String[] expectedKeys = new String[] { "aardvark", "beaver", "cat", "dog", "elephant" }; + + HttpHeaders readOnlyHttpHeaders = HttpHeaders.readOnlyHttpHeaders(headers); + + HttpHeaders forEachHeaders = new HttpHeaders(); + readOnlyHttpHeaders.forEach(forEachHeaders::putIfAbsent); + assertThat(forEachHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys); + + HttpHeaders putAllHeaders = new HttpHeaders(); + putAllHeaders.putAll(readOnlyHttpHeaders); + assertThat(putAllHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys); + + HttpHeaders addAllHeaders = new HttpHeaders(); + addAllHeaders.addAll(readOnlyHttpHeaders); + assertThat(addAllHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys); + } + @Test // gh-25034 void equalsUnwrapsHttpHeaders() { HttpHeaders headers1 = new HttpHeaders(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java index fbaa3a42808..1e400fad7d4 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2023 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. @@ -255,10 +255,7 @@ final class DefaultClientRequestBuilder implements ClientRequest.Builder { public Mono writeTo(ClientHttpRequest request, ExchangeStrategies strategies) { HttpHeaders requestHeaders = request.getHeaders(); if (!this.headers.isEmpty()) { - this.headers.entrySet().stream() - .filter(entry -> !requestHeaders.containsKey(entry.getKey())) - .forEach(entry -> requestHeaders - .put(entry.getKey(), entry.getValue())); + this.headers.forEach(requestHeaders::putIfAbsent); } MultiValueMap requestCookies = request.getCookies(); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java index 9198a8d5d63..56afb698159 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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. @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; @@ -295,6 +296,16 @@ public class WebSocketHttpHeaders extends HttpHeaders { return this.headers.entrySet(); } + @Override + public void forEach(BiConsumer> action) { + this.headers.forEach(action); + } + + @Override + public List putIfAbsent(String key, List value) { + return this.headers.putIfAbsent(key, value); + } + @Override public boolean equals(@Nullable Object other) {