Skip to content

Commit c908ec1

Browse files
committed
Wrappers for native headers on the client side
Native server request headers have been wrapped rather than copied since 5.1. This commit applies the same change to the client side where, to make matters worse, headers were copied repeatedly on every access, see also previous commit ca897b95. For Netty and Jetty the wrappers are the same as on the server side but duplicated in order to remain package private. See gh-24680
1 parent df99889 commit c908ec1

File tree

8 files changed

+727
-17
lines changed

8 files changed

+727
-17
lines changed

spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.http.client.reactive;
1818

1919
import java.nio.ByteBuffer;
20-
import java.util.Arrays;
2120
import java.util.concurrent.atomic.AtomicBoolean;
2221

2322
import org.apache.hc.client5.http.cookie.Cookie;
@@ -49,6 +48,8 @@ class HttpComponentsClientHttpResponse implements ClientHttpResponse {
4948

5049
private final Message<HttpResponse, Publisher<ByteBuffer>> message;
5150

51+
private final HttpHeaders headers;
52+
5253
private final HttpClientContext context;
5354

5455
private final AtomicBoolean rejectSubscribers = new AtomicBoolean();
@@ -61,6 +62,9 @@ public HttpComponentsClientHttpResponse(DataBufferFactory dataBufferFactory,
6162
this.dataBufferFactory = dataBufferFactory;
6263
this.message = message;
6364
this.context = context;
65+
66+
MultiValueMap<String, String> adapter = new HttpComponentsHeadersAdapter(message.getHead());
67+
this.headers = HttpHeaders.readOnlyHttpHeaders(adapter);
6468
}
6569

6670

@@ -107,9 +111,6 @@ public Flux<DataBuffer> getBody() {
107111

108112
@Override
109113
public HttpHeaders getHeaders() {
110-
return Arrays.stream(this.message.getHead().getHeaders())
111-
.collect(HttpHeaders::new,
112-
(httpHeaders, header) -> httpHeaders.add(header.getName(), header.getValue()),
113-
HttpHeaders::putAll);
114+
return this.headers;
114115
}
115116
}
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.client.reactive;
18+
19+
import java.util.AbstractSet;
20+
import java.util.ArrayList;
21+
import java.util.Arrays;
22+
import java.util.Collection;
23+
import java.util.Collections;
24+
import java.util.Iterator;
25+
import java.util.LinkedHashMap;
26+
import java.util.LinkedHashSet;
27+
import java.util.List;
28+
import java.util.Map;
29+
import java.util.Set;
30+
31+
import org.apache.hc.core5.http.Header;
32+
import org.apache.hc.core5.http.HttpResponse;
33+
34+
import org.springframework.http.HttpHeaders;
35+
import org.springframework.lang.Nullable;
36+
import org.springframework.util.MultiValueMap;
37+
38+
/**
39+
* {@code MultiValueMap} implementation for wrapping Apache HttpComponents
40+
* HttpClient headers.
41+
*
42+
* @author Rossen Stoyanchev
43+
* @since 5.3
44+
*/
45+
class HttpComponentsHeadersAdapter implements MultiValueMap<String, String> {
46+
47+
private final HttpResponse response;
48+
49+
50+
HttpComponentsHeadersAdapter(HttpResponse response) {
51+
this.response = response;
52+
}
53+
54+
55+
@Override
56+
public String getFirst(String key) {
57+
Header header = this.response.getFirstHeader(key);
58+
return (header != null ? header.getValue() : null);
59+
}
60+
61+
@Override
62+
public void add(String key, @Nullable String value) {
63+
this.response.addHeader(key, value);
64+
}
65+
66+
@Override
67+
public void addAll(String key, List<? extends String> values) {
68+
values.forEach(value -> add(key, value));
69+
}
70+
71+
@Override
72+
public void addAll(MultiValueMap<String, String> values) {
73+
values.forEach(this::addAll);
74+
}
75+
76+
@Override
77+
public void set(String key, @Nullable String value) {
78+
this.response.setHeader(key, value);
79+
}
80+
81+
@Override
82+
public void setAll(Map<String, String> values) {
83+
values.forEach(this::set);
84+
}
85+
86+
@Override
87+
public Map<String, String> toSingleValueMap() {
88+
Map<String, String> map = new LinkedHashMap<>(size());
89+
this.response.headerIterator().forEachRemaining(h -> map.putIfAbsent(h.getName(), h.getValue()));
90+
return map;
91+
}
92+
93+
@Override
94+
public int size() {
95+
return this.response.getHeaders().length;
96+
}
97+
98+
@Override
99+
public boolean isEmpty() {
100+
return (this.response.getHeaders().length == 0);
101+
}
102+
103+
@Override
104+
public boolean containsKey(Object key) {
105+
return (key instanceof String && this.response.containsHeader((String) key));
106+
}
107+
108+
@Override
109+
public boolean containsValue(Object value) {
110+
return (value instanceof String &&
111+
Arrays.stream(this.response.getHeaders()).anyMatch(h -> h.getValue().equals(value)));
112+
}
113+
114+
@Nullable
115+
@Override
116+
public List<String> get(Object key) {
117+
List<String> values = null;
118+
if (containsKey(key)) {
119+
Header[] headers = this.response.getHeaders((String) key);
120+
values = new ArrayList<>(headers.length);
121+
for (Header header : headers) {
122+
values.add(header.getValue());
123+
}
124+
}
125+
return values;
126+
}
127+
128+
@Nullable
129+
@Override
130+
public List<String> put(String key, List<String> values) {
131+
List<String> oldValues = remove(key);
132+
values.forEach(value -> add(key, value));
133+
return oldValues;
134+
}
135+
136+
@Nullable
137+
@Override
138+
public List<String> remove(Object key) {
139+
if (key instanceof String) {
140+
List<String> oldValues = get(key);
141+
this.response.removeHeaders((String) key);
142+
return oldValues;
143+
}
144+
return null;
145+
}
146+
147+
@Override
148+
public void putAll(Map<? extends String, ? extends List<String>> map) {
149+
map.forEach(this::put);
150+
}
151+
152+
@Override
153+
public void clear() {
154+
this.response.setHeaders();
155+
}
156+
157+
@Override
158+
public Set<String> keySet() {
159+
Set<String> keys = new LinkedHashSet<>(size());
160+
for (Header header : this.response.getHeaders()) {
161+
keys.add(header.getName());
162+
}
163+
return keys;
164+
}
165+
166+
@Override
167+
public Collection<List<String>> values() {
168+
Collection<List<String>> values = new ArrayList<>(size());
169+
for (Header header : this.response.getHeaders()) {
170+
values.add(get(header.getName()));
171+
}
172+
return values;
173+
}
174+
175+
@Override
176+
public Set<Entry<String, List<String>>> entrySet() {
177+
return new AbstractSet<Entry<String, List<String>>>() {
178+
@Override
179+
public Iterator<Entry<String, List<String>>> iterator() {
180+
return new EntryIterator();
181+
}
182+
183+
@Override
184+
public int size() {
185+
return HttpComponentsHeadersAdapter.this.size();
186+
}
187+
};
188+
}
189+
190+
191+
@Override
192+
public String toString() {
193+
return HttpHeaders.formatHeaders(this);
194+
}
195+
196+
197+
private class EntryIterator implements Iterator<Entry<String, List<String>>> {
198+
199+
private Iterator<Header> iterator = response.headerIterator();
200+
201+
@Override
202+
public boolean hasNext() {
203+
return this.iterator.hasNext();
204+
}
205+
206+
@Override
207+
public Entry<String, List<String>> next() {
208+
return new HeaderEntry(this.iterator.next().getName());
209+
}
210+
}
211+
212+
213+
private class HeaderEntry implements Entry<String, List<String>> {
214+
215+
private final String key;
216+
217+
HeaderEntry(String key) {
218+
this.key = key;
219+
}
220+
221+
@Override
222+
public String getKey() {
223+
return this.key;
224+
}
225+
226+
@Override
227+
public List<String> getValue() {
228+
List<String> values = HttpComponentsHeadersAdapter.this.get(this.key);
229+
return values != null ? values : Collections.emptyList();
230+
}
231+
232+
@Override
233+
public List<String> setValue(List<String> value) {
234+
List<String> previousValues = getValue();
235+
HttpComponentsHeadersAdapter.this.put(this.key, value);
236+
return previousValues;
237+
}
238+
}
239+
240+
}

spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,24 @@
3636
*
3737
* @author Sebastien Deleuze
3838
* @since 5.1
39-
* @see <a href="https://github.com/jetty-project/jetty-reactive-httpclient">Jetty ReactiveStreams HttpClient</a>
39+
* @see <a href="https://github.com/jetty-project/jetty-reactive-httpclient">
40+
* Jetty ReactiveStreams HttpClient</a>
4041
*/
4142
class JettyClientHttpResponse implements ClientHttpResponse {
4243

4344
private final ReactiveResponse reactiveResponse;
4445

4546
private final Flux<DataBuffer> content;
4647

48+
private final HttpHeaders headers;
49+
4750

4851
public JettyClientHttpResponse(ReactiveResponse reactiveResponse, Publisher<DataBuffer> content) {
4952
this.reactiveResponse = reactiveResponse;
5053
this.content = Flux.from(content);
54+
55+
MultiValueMap<String, String> adapter = new JettyHeadersAdapter(reactiveResponse.getHeaders());
56+
this.headers = HttpHeaders.readOnlyHttpHeaders(adapter);
5157
}
5258

5359

@@ -86,10 +92,7 @@ public Flux<DataBuffer> getBody() {
8692

8793
@Override
8894
public HttpHeaders getHeaders() {
89-
HttpHeaders headers = new HttpHeaders();
90-
this.reactiveResponse.getHeaders().stream()
91-
.forEach(field -> headers.add(field.getName(), field.getValue()));
92-
return headers;
95+
return this.headers;
9396
}
9497

9598
}

0 commit comments

Comments
 (0)