Skip to content

Commit 2f80a08

Browse files
committed
Merge ClientResponse and related improvements
Closes gh-24680
2 parents a64e709 + 94824e3 commit 2f80a08

32 files changed

+1053
-258
lines changed

spring-web/src/main/java/org/springframework/http/HttpEntity.java

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -99,11 +99,7 @@ public HttpEntity(MultiValueMap<String, String> headers) {
9999
*/
100100
public HttpEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers) {
101101
this.body = body;
102-
HttpHeaders tempHeaders = new HttpHeaders();
103-
if (headers != null) {
104-
tempHeaders.putAll(headers);
105-
}
106-
this.headers = HttpHeaders.readOnlyHttpHeaders(tempHeaders);
102+
this.headers = HttpHeaders.readOnlyHttpHeaders(headers != null ? headers : new HttpHeaders());
107103
}
108104

109105

spring-web/src/main/java/org/springframework/http/HttpHeaders.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -1769,7 +1769,9 @@ public String toString() {
17691769

17701770

17711771
/**
1772-
* Apply a read-only {@code HttpHeaders} wrapper around the given headers.
1772+
* Apply a read-only {@code HttpHeaders} wrapper around the given headers
1773+
* that also caches the parsed representations of the "Accept" and
1774+
* "Content-Type" headers.
17731775
*/
17741776
public static HttpHeaders readOnlyHttpHeaders(MultiValueMap<String, String> headers) {
17751777
Assert.notNull(headers, "HttpHeaders must not be null");

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

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.io.OutputStream;
2121

2222
import org.springframework.http.HttpHeaders;
23+
import org.springframework.lang.Nullable;
2324
import org.springframework.util.Assert;
2425

2526
/**
@@ -35,10 +36,22 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
3536

3637
private boolean executed = false;
3738

39+
@Nullable
40+
private HttpHeaders readOnlyHeaders;
41+
3842

3943
@Override
4044
public final HttpHeaders getHeaders() {
41-
return (this.executed ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
45+
if (this.readOnlyHeaders != null) {
46+
return this.readOnlyHeaders;
47+
}
48+
else if (this.executed) {
49+
this.readOnlyHeaders = HttpHeaders.readOnlyHttpHeaders(this.headers);
50+
return this.readOnlyHeaders;
51+
}
52+
else {
53+
return this.headers;
54+
}
4255
}
4356

4457
@Override

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

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -60,6 +60,9 @@ private enum State {NEW, COMMITTING, COMMITTED}
6060

6161
private final List<Supplier<? extends Publisher<Void>>> commitActions = new ArrayList<>(4);
6262

63+
@Nullable
64+
private HttpHeaders readOnlyHeaders;
65+
6366

6467
public AbstractClientHttpRequest() {
6568
this(new HttpHeaders());
@@ -74,10 +77,16 @@ public AbstractClientHttpRequest(HttpHeaders headers) {
7477

7578
@Override
7679
public HttpHeaders getHeaders() {
77-
if (State.COMMITTED.equals(this.state.get())) {
78-
return HttpHeaders.readOnlyHttpHeaders(this.headers);
80+
if (this.readOnlyHeaders != null) {
81+
return this.readOnlyHeaders;
82+
}
83+
else if (State.COMMITTED.equals(this.state.get())) {
84+
this.readOnlyHeaders = HttpHeaders.readOnlyHttpHeaders(this.headers);
85+
return this.readOnlyHeaders;
86+
}
87+
else {
88+
return this.headers;
7989
}
80-
return this.headers;
8190
}
8291

8392
@Override

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

+6-5
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
}
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+
}

0 commit comments

Comments
 (0)