Merge ClientResponse and related improvements

Closes gh-24680
This commit is contained in:
Rossen Stoyanchev
2020-05-11 08:49:54 +01:00
32 changed files with 1054 additions and 259 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -99,11 +99,7 @@ public class HttpEntity<T> {
*/
public HttpEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers) {
this.body = body;
HttpHeaders tempHeaders = new HttpHeaders();
if (headers != null) {
tempHeaders.putAll(headers);
}
this.headers = HttpHeaders.readOnlyHttpHeaders(tempHeaders);
this.headers = HttpHeaders.readOnlyHttpHeaders(headers != null ? headers : new HttpHeaders());
}

View File

@@ -1769,7 +1769,9 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
/**
* Apply a read-only {@code HttpHeaders} wrapper around the given headers.
* Apply a read-only {@code HttpHeaders} wrapper around the given headers
* that also caches the parsed representations of the "Accept" and
* "Content-Type" headers.
*/
public static HttpHeaders readOnlyHttpHeaders(MultiValueMap<String, String> headers) {
Assert.notNull(headers, "HttpHeaders must not be null");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 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.
@@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.OutputStream;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -35,10 +36,22 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
private boolean executed = false;
@Nullable
private HttpHeaders readOnlyHeaders;
@Override
public final HttpHeaders getHeaders() {
return (this.executed ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
if (this.readOnlyHeaders != null) {
return this.readOnlyHeaders;
}
else if (this.executed) {
this.readOnlyHeaders = HttpHeaders.readOnlyHttpHeaders(this.headers);
return this.readOnlyHeaders;
}
else {
return this.headers;
}
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -60,6 +60,9 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
private final List<Supplier<? extends Publisher<Void>>> commitActions = new ArrayList<>(4);
@Nullable
private HttpHeaders readOnlyHeaders;
public AbstractClientHttpRequest() {
this(new HttpHeaders());
@@ -74,10 +77,16 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
@Override
public HttpHeaders getHeaders() {
if (State.COMMITTED.equals(this.state.get())) {
return HttpHeaders.readOnlyHttpHeaders(this.headers);
if (this.readOnlyHeaders != null) {
return this.readOnlyHeaders;
}
else if (State.COMMITTED.equals(this.state.get())) {
this.readOnlyHeaders = HttpHeaders.readOnlyHttpHeaders(this.headers);
return this.readOnlyHeaders;
}
else {
return this.headers;
}
return this.headers;
}
@Override

View File

@@ -17,7 +17,6 @@
package org.springframework.http.client.reactive;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.hc.client5.http.cookie.Cookie;
@@ -49,6 +48,8 @@ class HttpComponentsClientHttpResponse implements ClientHttpResponse {
private final Message<HttpResponse, Publisher<ByteBuffer>> message;
private final HttpHeaders headers;
private final HttpClientContext context;
private final AtomicBoolean rejectSubscribers = new AtomicBoolean();
@@ -61,6 +62,9 @@ class HttpComponentsClientHttpResponse implements ClientHttpResponse {
this.dataBufferFactory = dataBufferFactory;
this.message = message;
this.context = context;
MultiValueMap<String, String> adapter = new HttpComponentsHeadersAdapter(message.getHead());
this.headers = HttpHeaders.readOnlyHttpHeaders(adapter);
}
@@ -107,9 +111,6 @@ class HttpComponentsClientHttpResponse implements ClientHttpResponse {
@Override
public HttpHeaders getHeaders() {
return Arrays.stream(this.message.getHead().getHeaders())
.collect(HttpHeaders::new,
(httpHeaders, header) -> httpHeaders.add(header.getName(), header.getValue()),
HttpHeaders::putAll);
return this.headers;
}
}

View File

@@ -0,0 +1,240 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.client.reactive;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;
/**
* {@code MultiValueMap} implementation for wrapping Apache HttpComponents
* HttpClient headers.
*
* @author Rossen Stoyanchev
* @since 5.3
*/
class HttpComponentsHeadersAdapter implements MultiValueMap<String, String> {
private final HttpResponse response;
HttpComponentsHeadersAdapter(HttpResponse response) {
this.response = response;
}
@Override
public String getFirst(String key) {
Header header = this.response.getFirstHeader(key);
return (header != null ? header.getValue() : null);
}
@Override
public void add(String key, @Nullable String value) {
this.response.addHeader(key, value);
}
@Override
public void addAll(String key, List<? extends String> values) {
values.forEach(value -> add(key, value));
}
@Override
public void addAll(MultiValueMap<String, String> values) {
values.forEach(this::addAll);
}
@Override
public void set(String key, @Nullable String value) {
this.response.setHeader(key, value);
}
@Override
public void setAll(Map<String, String> values) {
values.forEach(this::set);
}
@Override
public Map<String, String> toSingleValueMap() {
Map<String, String> map = new LinkedHashMap<>(size());
this.response.headerIterator().forEachRemaining(h -> map.putIfAbsent(h.getName(), h.getValue()));
return map;
}
@Override
public int size() {
return this.response.getHeaders().length;
}
@Override
public boolean isEmpty() {
return (this.response.getHeaders().length == 0);
}
@Override
public boolean containsKey(Object key) {
return (key instanceof String && this.response.containsHeader((String) key));
}
@Override
public boolean containsValue(Object value) {
return (value instanceof String &&
Arrays.stream(this.response.getHeaders()).anyMatch(h -> h.getValue().equals(value)));
}
@Nullable
@Override
public List<String> get(Object key) {
List<String> values = null;
if (containsKey(key)) {
Header[] headers = this.response.getHeaders((String) key);
values = new ArrayList<>(headers.length);
for (Header header : headers) {
values.add(header.getValue());
}
}
return values;
}
@Nullable
@Override
public List<String> put(String key, List<String> values) {
List<String> oldValues = remove(key);
values.forEach(value -> add(key, value));
return oldValues;
}
@Nullable
@Override
public List<String> remove(Object key) {
if (key instanceof String) {
List<String> oldValues = get(key);
this.response.removeHeaders((String) key);
return oldValues;
}
return null;
}
@Override
public void putAll(Map<? extends String, ? extends List<String>> map) {
map.forEach(this::put);
}
@Override
public void clear() {
this.response.setHeaders();
}
@Override
public Set<String> keySet() {
Set<String> keys = new LinkedHashSet<>(size());
for (Header header : this.response.getHeaders()) {
keys.add(header.getName());
}
return keys;
}
@Override
public Collection<List<String>> values() {
Collection<List<String>> values = new ArrayList<>(size());
for (Header header : this.response.getHeaders()) {
values.add(get(header.getName()));
}
return values;
}
@Override
public Set<Entry<String, List<String>>> entrySet() {
return new AbstractSet<Entry<String, List<String>>>() {
@Override
public Iterator<Entry<String, List<String>>> iterator() {
return new EntryIterator();
}
@Override
public int size() {
return HttpComponentsHeadersAdapter.this.size();
}
};
}
@Override
public String toString() {
return HttpHeaders.formatHeaders(this);
}
private class EntryIterator implements Iterator<Entry<String, List<String>>> {
private Iterator<Header> iterator = response.headerIterator();
@Override
public boolean hasNext() {
return this.iterator.hasNext();
}
@Override
public Entry<String, List<String>> next() {
return new HeaderEntry(this.iterator.next().getName());
}
}
private class HeaderEntry implements Entry<String, List<String>> {
private final String key;
HeaderEntry(String key) {
this.key = key;
}
@Override
public String getKey() {
return this.key;
}
@Override
public List<String> getValue() {
List<String> values = HttpComponentsHeadersAdapter.this.get(this.key);
return values != null ? values : Collections.emptyList();
}
@Override
public List<String> setValue(List<String> value) {
List<String> previousValues = getValue();
HttpComponentsHeadersAdapter.this.put(this.key, value);
return previousValues;
}
}
}

View File

@@ -36,7 +36,8 @@ import org.springframework.util.MultiValueMap;
*
* @author Sebastien Deleuze
* @since 5.1
* @see <a href="https://github.com/jetty-project/jetty-reactive-httpclient">Jetty ReactiveStreams HttpClient</a>
* @see <a href="https://github.com/jetty-project/jetty-reactive-httpclient">
* Jetty ReactiveStreams HttpClient</a>
*/
class JettyClientHttpResponse implements ClientHttpResponse {
@@ -44,10 +45,15 @@ class JettyClientHttpResponse implements ClientHttpResponse {
private final Flux<DataBuffer> content;
private final HttpHeaders headers;
public JettyClientHttpResponse(ReactiveResponse reactiveResponse, Publisher<DataBuffer> content) {
this.reactiveResponse = reactiveResponse;
this.content = Flux.from(content);
MultiValueMap<String, String> adapter = new JettyHeadersAdapter(reactiveResponse.getHeaders());
this.headers = HttpHeaders.readOnlyHttpHeaders(adapter);
}
@@ -86,10 +92,7 @@ class JettyClientHttpResponse implements ClientHttpResponse {
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
this.reactiveResponse.getHeaders().stream()
.forEach(field -> headers.add(field.getName(), field.getValue()));
return headers;
return this.headers;
}
}

View File

@@ -0,0 +1,230 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.client.reactive;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;
/**
* {@code MultiValueMap} implementation for wrapping Jetty HTTP headers.
*
* <p>There is a duplicate of this class in the server package!
*
* @author Rossen Stoyanchev
* @since 5.3
*/
class JettyHeadersAdapter implements MultiValueMap<String, String> {
private final HttpFields headers;
JettyHeadersAdapter(HttpFields headers) {
this.headers = headers;
}
@Override
public String getFirst(String key) {
return this.headers.get(key);
}
@Override
public void add(String key, @Nullable String value) {
this.headers.add(key, value);
}
@Override
public void addAll(String key, List<? extends String> values) {
values.forEach(value -> add(key, value));
}
@Override
public void addAll(MultiValueMap<String, String> values) {
values.forEach(this::addAll);
}
@Override
public void set(String key, @Nullable String value) {
this.headers.put(key, value);
}
@Override
public void setAll(Map<String, String> values) {
values.forEach(this::set);
}
@Override
public Map<String, String> toSingleValueMap() {
Map<String, String> singleValueMap = new LinkedHashMap<>(this.headers.size());
Iterator<HttpField> iterator = this.headers.iterator();
iterator.forEachRemaining(field -> {
if (!singleValueMap.containsKey(field.getName())) {
singleValueMap.put(field.getName(), field.getValue());
}
});
return singleValueMap;
}
@Override
public int size() {
return this.headers.getFieldNamesCollection().size();
}
@Override
public boolean isEmpty() {
return (this.headers.size() == 0);
}
@Override
public boolean containsKey(Object key) {
return (key instanceof String && this.headers.containsKey((String) key));
}
@Override
public boolean containsValue(Object value) {
return (value instanceof String &&
this.headers.stream().anyMatch(field -> field.contains((String) value)));
}
@Nullable
@Override
public List<String> get(Object key) {
if (containsKey(key)) {
return this.headers.getValuesList((String) key);
}
return null;
}
@Nullable
@Override
public List<String> put(String key, List<String> value) {
List<String> oldValues = get(key);
this.headers.put(key, value);
return oldValues;
}
@Nullable
@Override
public List<String> remove(Object key) {
if (key instanceof String) {
List<String> oldValues = get(key);
this.headers.remove((String) key);
return oldValues;
}
return null;
}
@Override
public void putAll(Map<? extends String, ? extends List<String>> map) {
map.forEach(this::put);
}
@Override
public void clear() {
this.headers.clear();
}
@Override
public Set<String> keySet() {
return this.headers.getFieldNamesCollection();
}
@Override
public Collection<List<String>> values() {
return this.headers.getFieldNamesCollection().stream()
.map(this.headers::getValuesList).collect(Collectors.toList());
}
@Override
public Set<Entry<String, List<String>>> entrySet() {
return new AbstractSet<Entry<String, List<String>>>() {
@Override
public Iterator<Entry<String, List<String>>> iterator() {
return new EntryIterator();
}
@Override
public int size() {
return headers.size();
}
};
}
@Override
public String toString() {
return HttpHeaders.formatHeaders(this);
}
private class EntryIterator implements Iterator<Entry<String, List<String>>> {
private Enumeration<String> names = headers.getFieldNames();
@Override
public boolean hasNext() {
return this.names.hasMoreElements();
}
@Override
public Entry<String, List<String>> next() {
return new HeaderEntry(this.names.nextElement());
}
}
private class HeaderEntry implements Entry<String, List<String>> {
private final String key;
HeaderEntry(String key) {
this.key = key;
}
@Override
public String getKey() {
return this.key;
}
@Override
public List<String> getValue() {
return headers.getValuesList(this.key);
}
@Override
public List<String> setValue(List<String> value) {
List<String> previousValues = headers.getValuesList(this.key);
headers.put(this.key, value);
return previousValues;
}
}
}

View File

@@ -0,0 +1,229 @@
/*
* Copyright 2002-2018 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.client.reactive;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import io.netty.handler.codec.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;
/**
* {@code MultiValueMap} implementation for wrapping Netty HTTP headers.
*
* <p>There is a duplicate of this class in the server package!
*
* @author Rossen Stoyanchev
* @since 5.3
*/
class NettyHeadersAdapter implements MultiValueMap<String, String> {
private final HttpHeaders headers;
NettyHeadersAdapter(HttpHeaders headers) {
this.headers = headers;
}
@Override
@Nullable
public String getFirst(String key) {
return this.headers.get(key);
}
@Override
public void add(String key, @Nullable String value) {
this.headers.add(key, value);
}
@Override
public void addAll(String key, List<? extends String> values) {
this.headers.add(key, values);
}
@Override
public void addAll(MultiValueMap<String, String> values) {
values.forEach(this.headers::add);
}
@Override
public void set(String key, @Nullable String value) {
this.headers.set(key, value);
}
@Override
public void setAll(Map<String, String> values) {
values.forEach(this.headers::set);
}
@Override
public Map<String, String> toSingleValueMap() {
Map<String, String> singleValueMap = new LinkedHashMap<>(this.headers.size());
this.headers.entries()
.forEach(entry -> {
if (!singleValueMap.containsKey(entry.getKey())) {
singleValueMap.put(entry.getKey(), entry.getValue());
}
});
return singleValueMap;
}
@Override
public int size() {
return this.headers.names().size();
}
@Override
public boolean isEmpty() {
return this.headers.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return (key instanceof String && this.headers.contains((String) key));
}
@Override
public boolean containsValue(Object value) {
return (value instanceof String &&
this.headers.entries().stream()
.anyMatch(entry -> value.equals(entry.getValue())));
}
@Override
@Nullable
public List<String> get(Object key) {
if (containsKey(key)) {
return this.headers.getAll((String) key);
}
return null;
}
@Nullable
@Override
public List<String> put(String key, @Nullable List<String> value) {
List<String> previousValues = this.headers.getAll(key);
this.headers.set(key, value);
return previousValues;
}
@Nullable
@Override
public List<String> remove(Object key) {
if (key instanceof String) {
List<String> previousValues = this.headers.getAll((String) key);
this.headers.remove((String) key);
return previousValues;
}
return null;
}
@Override
public void putAll(Map<? extends String, ? extends List<String>> map) {
map.forEach(this.headers::add);
}
@Override
public void clear() {
this.headers.clear();
}
@Override
public Set<String> keySet() {
return this.headers.names();
}
@Override
public Collection<List<String>> values() {
return this.headers.names().stream()
.map(this.headers::getAll).collect(Collectors.toList());
}
@Override
public Set<Entry<String, List<String>>> entrySet() {
return new AbstractSet<Entry<String, List<String>>>() {
@Override
public Iterator<Entry<String, List<String>>> iterator() {
return new EntryIterator();
}
@Override
public int size() {
return headers.size();
}
};
}
@Override
public String toString() {
return org.springframework.http.HttpHeaders.formatHeaders(this);
}
private class EntryIterator implements Iterator<Entry<String, List<String>>> {
private Iterator<String> names = headers.names().iterator();
@Override
public boolean hasNext() {
return this.names.hasNext();
}
@Override
public Entry<String, List<String>> next() {
return new HeaderEntry(this.names.next());
}
}
private class HeaderEntry implements Entry<String, List<String>> {
private final String key;
HeaderEntry(String key) {
this.key = key;
}
@Override
public String getKey() {
return this.key;
}
@Override
public List<String> getValue() {
return headers.getAll(this.key);
}
@Override
public List<String> setValue(List<String> value) {
List<String> previousValues = headers.getAll(this.key);
headers.set(this.key, value);
return previousValues;
}
}
}

View File

@@ -42,12 +42,14 @@ import org.springframework.util.MultiValueMap;
*/
class ReactorClientHttpResponse implements ClientHttpResponse {
private final NettyDataBufferFactory bufferFactory;
private final HttpClientResponse response;
private final NettyInbound inbound;
private final NettyDataBufferFactory bufferFactory;
private final HttpHeaders headers;
private final AtomicBoolean rejectSubscribers = new AtomicBoolean();
@@ -55,6 +57,9 @@ class ReactorClientHttpResponse implements ClientHttpResponse {
this.response = response;
this.inbound = inbound;
this.bufferFactory = new NettyDataBufferFactory(alloc);
MultiValueMap<String, String> adapter = new NettyHeadersAdapter(response.responseHeaders());
this.headers = HttpHeaders.readOnlyHttpHeaders(adapter);
}
@@ -81,9 +86,7 @@ class ReactorClientHttpResponse implements ClientHttpResponse {
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
this.response.responseHeaders().entries().forEach(e -> headers.add(e.getKey(), e.getValue()));
return headers;
return this.headers;
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -47,6 +47,9 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
private boolean bodyUsed = false;
@Nullable
private HttpHeaders readOnlyHeaders;
/**
* Construct a new instance of the ServletServerHttpResponse based on the given {@link HttpServletResponse}.
@@ -74,7 +77,16 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
@Override
public HttpHeaders getHeaders() {
return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
if (this.readOnlyHeaders != null) {
return this.readOnlyHeaders;
}
else if (this.headersWritten) {
this.readOnlyHeaders = HttpHeaders.readOnlyHttpHeaders(this.headers);
return this.readOnlyHeaders;
}
else {
return this.headers;
}
}
@Override

View File

@@ -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.
@@ -76,7 +76,7 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest {
* @param contextPath the context path for the request
* @param headers the headers for the request
*/
public AbstractServerHttpRequest(URI uri, @Nullable String contextPath, HttpHeaders headers) {
public AbstractServerHttpRequest(URI uri, @Nullable String contextPath, MultiValueMap<String, String> headers) {
this.uri = uri;
this.path = RequestPath.parse(uri, contextPath);
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);

View File

@@ -75,6 +75,9 @@ public abstract class AbstractServerHttpResponse implements ServerHttpResponse {
private final List<Supplier<? extends Mono<Void>>> commitActions = new ArrayList<>(4);
@Nullable
private HttpHeaders readOnlyHeaders;
public AbstractServerHttpResponse(DataBufferFactory dataBufferFactory) {
this(dataBufferFactory, new HttpHeaders());
@@ -155,8 +158,16 @@ public abstract class AbstractServerHttpResponse implements ServerHttpResponse {
@Override
public HttpHeaders getHeaders() {
return (this.state.get() == State.COMMITTED ?
HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
if (this.readOnlyHeaders != null) {
return this.readOnlyHeaders;
}
else if (this.state.get() == State.COMMITTED) {
this.readOnlyHeaders = HttpHeaders.readOnlyHttpHeaders(this.headers);
return this.readOnlyHeaders;
}
else {
return this.headers;
}
}
@Override

View File

@@ -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.
@@ -20,7 +20,6 @@ import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.function.Consumer;
import reactor.core.publisher.Flux;
@@ -31,7 +30,6 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
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;
@@ -46,12 +44,10 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
private URI uri;
private HttpHeaders httpHeaders;
private HttpHeaders headers;
private String httpMethodValue;
private final MultiValueMap<String, HttpCookie> cookies;
@Nullable
private String uriPath;
@@ -70,21 +66,12 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
Assert.notNull(original, "ServerHttpRequest is required");
this.uri = original.getURI();
this.headers = HttpHeaders.writableHttpHeaders(original.getHeaders());
this.httpMethodValue = original.getMethodValue();
this.body = original.getBody();
this.httpHeaders = HttpHeaders.writableHttpHeaders(original.getHeaders());
this.cookies = new LinkedMultiValueMap<>(original.getCookies().size());
copyMultiValueMap(original.getCookies(), this.cookies);
this.originalRequest = original;
}
private static <K, V> void copyMultiValueMap(MultiValueMap<K,V> source, MultiValueMap<K,V> target) {
source.forEach((key, value) -> target.put(key, new LinkedList<>(value)));
}
@Override
public ServerHttpRequest.Builder method(HttpMethod httpMethod) {
@@ -113,14 +100,14 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
@Override
public ServerHttpRequest.Builder header(String headerName, String... headerValues) {
this.httpHeaders.put(headerName, Arrays.asList(headerValues));
this.headers.put(headerName, Arrays.asList(headerValues));
return this;
}
@Override
public ServerHttpRequest.Builder headers(Consumer<HttpHeaders> headersConsumer) {
Assert.notNull(headersConsumer, "'headersConsumer' must not be null");
headersConsumer.accept(this.httpHeaders);
headersConsumer.accept(this.headers);
return this;
}
@@ -132,8 +119,8 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
@Override
public ServerHttpRequest build() {
return new MutatedServerHttpRequest(getUriToUse(), this.contextPath, this.httpHeaders,
this.httpMethodValue, this.cookies, this.sslInfo, this.body, this.originalRequest);
return new MutatedServerHttpRequest(getUriToUse(), this.contextPath,
this.httpMethodValue, this.sslInfo, this.body, this.originalRequest);
}
private URI getUriToUse() {
@@ -179,8 +166,6 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
private final String methodValue;
private final MultiValueMap<String, HttpCookie> cookies;
@Nullable
private final SslInfo sslInfo;
@@ -190,12 +175,11 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
public MutatedServerHttpRequest(URI uri, @Nullable String contextPath,
HttpHeaders headers, String methodValue, MultiValueMap<String, HttpCookie> cookies,
@Nullable SslInfo sslInfo, Flux<DataBuffer> body, ServerHttpRequest originalRequest) {
String methodValue, @Nullable SslInfo sslInfo,
Flux<DataBuffer> body, ServerHttpRequest originalRequest) {
super(uri, contextPath, headers);
super(uri, contextPath, originalRequest.getHeaders());
this.methodValue = methodValue;
this.cookies = cookies;
this.sslInfo = sslInfo != null ? sslInfo : originalRequest.getSslInfo();
this.body = body;
this.originalRequest = originalRequest;
@@ -208,7 +192,7 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
@Override
protected MultiValueMap<String, HttpCookie> initCookies() {
return this.cookies;
return this.originalRequest.getCookies();
}
@Nullable

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -36,6 +36,8 @@ import org.springframework.util.MultiValueMap;
/**
* {@code MultiValueMap} implementation for wrapping Jetty HTTP headers.
*
* <p>There is a duplicate of this class in the client package!
*
* @author Brian Clozel
* @since 5.1.1
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -36,6 +36,7 @@ import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
/**
* {@link ServletHttpHandlerAdapter} extension that uses Jetty APIs for writing
@@ -79,9 +80,9 @@ public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter {
super(createHeaders(request), request, asyncContext, servletPath, bufferFactory, bufferSize);
}
private static HttpHeaders createHeaders(HttpServletRequest request) {
private static MultiValueMap<String, String> createHeaders(HttpServletRequest request) {
HttpFields fields = ((Request) request).getMetaData().getFields();
return new HttpHeaders(new JettyHeadersAdapter(fields));
return new JettyHeadersAdapter(fields);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -33,6 +33,8 @@ import org.springframework.util.MultiValueMap;
/**
* {@code MultiValueMap} implementation for wrapping Netty HTTP headers.
*
* <p>There is a duplicate of this class in the client package!
*
* @author Brian Clozel
* @since 5.1.1
*/

View File

@@ -33,7 +33,6 @@ import reactor.netty.http.server.HttpServerRequest;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
@@ -59,7 +58,7 @@ class ReactorServerHttpRequest extends AbstractServerHttpRequest {
public ReactorServerHttpRequest(HttpServerRequest request, NettyDataBufferFactory bufferFactory)
throws URISyntaxException {
super(initUri(request), "", initHeaders(request));
super(initUri(request), "", new NettyHeadersAdapter(request.requestHeaders()));
Assert.notNull(bufferFactory, "DataBufferFactory must not be null");
this.request = request;
this.bufferFactory = bufferFactory;
@@ -127,11 +126,6 @@ class ReactorServerHttpRequest extends AbstractServerHttpRequest {
return uri;
}
private static HttpHeaders initHeaders(HttpServerRequest channel) {
NettyHeadersAdapter headersMap = new NettyHeadersAdapter(channel.requestHeaders());
return new HttpHeaders(headersMap);
}
@Override
public String getMethodValue() {

View File

@@ -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.
@@ -23,6 +23,7 @@ import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import javax.servlet.AsyncContext;
@@ -44,6 +45,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -77,8 +79,8 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
this(createDefaultHttpHeaders(request), request, asyncContext, servletPath, bufferFactory, bufferSize);
}
public ServletServerHttpRequest(HttpHeaders headers, HttpServletRequest request, AsyncContext asyncContext,
String servletPath, DataBufferFactory bufferFactory, int bufferSize)
public ServletServerHttpRequest(MultiValueMap<String, String> headers, HttpServletRequest request,
AsyncContext asyncContext, String servletPath, DataBufferFactory bufferFactory, int bufferSize)
throws IOException, URISyntaxException {
super(initUri(request), request.getContextPath() + servletPath, initHeaders(headers, request));
@@ -99,8 +101,9 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
}
private static HttpHeaders createDefaultHttpHeaders(HttpServletRequest request) {
HttpHeaders headers = new HttpHeaders();
private static MultiValueMap<String, String> createDefaultHttpHeaders(HttpServletRequest request) {
MultiValueMap<String, String> headers =
CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH));
for (Enumeration<?> names = request.getHeaderNames(); names.hasMoreElements(); ) {
String name = (String) names.nextElement();
for (Enumeration<?> values = request.getHeaders(name); values.hasMoreElements(); ) {
@@ -120,34 +123,36 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
return new URI(url.toString());
}
private static HttpHeaders initHeaders(HttpHeaders headers, HttpServletRequest request) {
MediaType contentType = headers.getContentType();
if (contentType == null) {
private static MultiValueMap<String, String> initHeaders(
MultiValueMap<String, String> headerValues, HttpServletRequest request) {
HttpHeaders headers = null;
MediaType contentType = null;
if (!StringUtils.hasLength(headerValues.getFirst(HttpHeaders.CONTENT_TYPE))) {
String requestContentType = request.getContentType();
if (StringUtils.hasLength(requestContentType)) {
contentType = MediaType.parseMediaType(requestContentType);
headers = new HttpHeaders(headerValues);
headers.setContentType(contentType);
}
}
if (contentType != null && contentType.getCharset() == null) {
String encoding = request.getCharacterEncoding();
if (StringUtils.hasLength(encoding)) {
Charset charset = Charset.forName(encoding);
Map<String, String> params = new LinkedCaseInsensitiveMap<>();
params.putAll(contentType.getParameters());
params.put("charset", charset.toString());
headers.setContentType(
new MediaType(contentType.getType(), contentType.getSubtype(),
params));
params.put("charset", Charset.forName(encoding).toString());
headers.setContentType(new MediaType(contentType, params));
}
}
if (headers.getContentLength() == -1) {
if (headerValues.getFirst(HttpHeaders.CONTENT_TYPE) == null) {
int contentLength = request.getContentLength();
if (contentLength != -1) {
headers = (headers != null ? headers : new HttpHeaders(headerValues));
headers.setContentLength(contentLength);
}
}
return headers;
return (headers != null ? headers : headerValues);
}

View File

@@ -44,6 +44,7 @@ import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
/**
@@ -105,14 +106,13 @@ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
this.bufferSize = bufferSize;
}
private static HttpHeaders createTomcatHttpHeaders(HttpServletRequest request) {
private static MultiValueMap<String, String> createTomcatHttpHeaders(HttpServletRequest request) {
RequestFacade requestFacade = getRequestFacade(request);
org.apache.catalina.connector.Request connectorRequest = (org.apache.catalina.connector.Request)
ReflectionUtils.getField(COYOTE_REQUEST_FIELD, requestFacade);
Assert.state(connectorRequest != null, "No Tomcat connector request");
Request tomcatRequest = connectorRequest.getCoyoteRequest();
TomcatHeadersAdapter headers = new TomcatHeadersAdapter(tomcatRequest.getMimeHeaders());
return new HttpHeaders(headers);
return new TomcatHeadersAdapter(tomcatRequest.getMimeHeaders());
}
private static RequestFacade getRequestFacade(HttpServletRequest request) {

View File

@@ -38,7 +38,6 @@ import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DataBufferWrapper;
import org.springframework.core.io.buffer.PooledDataBuffer;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
@@ -63,7 +62,7 @@ class UndertowServerHttpRequest extends AbstractServerHttpRequest {
public UndertowServerHttpRequest(HttpServerExchange exchange, DataBufferFactory bufferFactory)
throws URISyntaxException {
super(initUri(exchange), "", initHeaders(exchange));
super(initUri(exchange), "", new UndertowHeadersAdapter(exchange.getRequestHeaders()));
this.exchange = exchange;
this.body = new RequestBodyPublisher(exchange, bufferFactory);
this.body.registerListeners(exchange);
@@ -77,10 +76,6 @@ class UndertowServerHttpRequest extends AbstractServerHttpRequest {
return new URI(requestUriAndQuery);
}
private static HttpHeaders initHeaders(HttpServerExchange exchange) {
return new HttpHeaders(new UndertowHeadersAdapter(exchange.getRequestHeaders()));
}
@Override
public String getMethodValue() {
return this.exchange.getRequestMethod().toString();

View File

@@ -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.
@@ -49,13 +49,13 @@ public class ServerHttpRequestTests {
@Test
public void queryParamsNone() throws Exception {
MultiValueMap<String, String> params = createHttpRequest("/path").getQueryParams();
MultiValueMap<String, String> params = createRequest("/path").getQueryParams();
assertThat(params.size()).isEqualTo(0);
}
@Test
public void queryParams() throws Exception {
MultiValueMap<String, String> params = createHttpRequest("/path?a=A&b=B").getQueryParams();
MultiValueMap<String, String> params = createRequest("/path?a=A&b=B").getQueryParams();
assertThat(params.size()).isEqualTo(2);
assertThat(params.get("a")).isEqualTo(Collections.singletonList("A"));
assertThat(params.get("b")).isEqualTo(Collections.singletonList("B"));
@@ -63,84 +63,87 @@ public class ServerHttpRequestTests {
@Test
public void queryParamsWithMultipleValues() throws Exception {
MultiValueMap<String, String> params = createHttpRequest("/path?a=1&a=2").getQueryParams();
MultiValueMap<String, String> params = createRequest("/path?a=1&a=2").getQueryParams();
assertThat(params.size()).isEqualTo(1);
assertThat(params.get("a")).isEqualTo(Arrays.asList("1", "2"));
}
@Test // SPR-15140
public void queryParamsWithEncodedValue() throws Exception {
MultiValueMap<String, String> params = createHttpRequest("/path?a=%20%2B+%C3%A0").getQueryParams();
MultiValueMap<String, String> params = createRequest("/path?a=%20%2B+%C3%A0").getQueryParams();
assertThat(params.size()).isEqualTo(1);
assertThat(params.get("a")).isEqualTo(Collections.singletonList(" + \u00e0"));
}
@Test
public void queryParamsWithEmptyValue() throws Exception {
MultiValueMap<String, String> params = createHttpRequest("/path?a=").getQueryParams();
MultiValueMap<String, String> params = createRequest("/path?a=").getQueryParams();
assertThat(params.size()).isEqualTo(1);
assertThat(params.get("a")).isEqualTo(Collections.singletonList(""));
}
@Test
public void queryParamsWithNoValue() throws Exception {
MultiValueMap<String, String> params = createHttpRequest("/path?a").getQueryParams();
MultiValueMap<String, String> params = createRequest("/path?a").getQueryParams();
assertThat(params.size()).isEqualTo(1);
assertThat(params.get("a")).isEqualTo(Collections.singletonList(null));
}
@Test
public void mutateRequest() throws Exception {
SslInfo sslInfo = mock(SslInfo.class);
ServerHttpRequest request = createHttpRequest("/").mutate().sslInfo(sslInfo).build();
assertThat(request.getSslInfo()).isSameAs(sslInfo);
request = createHttpRequest("/").mutate().method(HttpMethod.DELETE).build();
public void mutateRequestMethod() throws Exception {
ServerHttpRequest request = createRequest("/").mutate().method(HttpMethod.DELETE).build();
assertThat(request.getMethod()).isEqualTo(HttpMethod.DELETE);
}
@Test
public void mutateSslInfo() throws Exception {
SslInfo sslInfo = mock(SslInfo.class);
ServerHttpRequest request = createRequest("/").mutate().sslInfo(sslInfo).build();
assertThat(request.getSslInfo()).isSameAs(sslInfo);
}
@Test
public void mutateUriAndPath() throws Exception {
String baseUri = "https://aaa.org:8080/a";
request = createHttpRequest(baseUri).mutate().uri(URI.create("https://bbb.org:9090/b")).build();
ServerHttpRequest request = createRequest(baseUri).mutate().uri(URI.create("https://bbb.org:9090/b")).build();
assertThat(request.getURI().toString()).isEqualTo("https://bbb.org:9090/b");
request = createHttpRequest(baseUri).mutate().path("/b/c/d").build();
request = createRequest(baseUri).mutate().path("/b/c/d").build();
assertThat(request.getURI().toString()).isEqualTo("https://aaa.org:8080/b/c/d");
request = createHttpRequest(baseUri).mutate().path("/app/b/c/d").contextPath("/app").build();
request = createRequest(baseUri).mutate().path("/app/b/c/d").contextPath("/app").build();
assertThat(request.getURI().toString()).isEqualTo("https://aaa.org:8080/app/b/c/d");
assertThat(request.getPath().contextPath().value()).isEqualTo("/app");
}
@Test
public void mutateWithInvalidPath() throws Exception {
assertThatIllegalArgumentException().isThrownBy(() ->
createHttpRequest("/").mutate().path("foo-bar"));
}
@Test // SPR-16434
public void mutatePathWithEncodedQueryParams() throws Exception {
ServerHttpRequest request = createHttpRequest("/path?name=%E6%89%8E%E6%A0%B9");
ServerHttpRequest request = createRequest("/path?name=%E6%89%8E%E6%A0%B9");
request = request.mutate().path("/mutatedPath").build();
assertThat(request.getURI().getRawPath()).isEqualTo("/mutatedPath");
assertThat(request.getURI().getRawQuery()).isEqualTo("name=%E6%89%8E%E6%A0%B9");
}
@Test
public void mutateWithInvalidPath() {
assertThatIllegalArgumentException().isThrownBy(() -> createRequest("/").mutate().path("foo-bar"));
}
@Test
public void mutateHeadersViaConsumer() throws Exception {
String headerName = "key";
String headerValue1 = "value1";
String headerValue2 = "value2";
ServerHttpRequest request = createHttpRequest("/path");
ServerHttpRequest request = createRequest("/path");
assertThat(request.getHeaders().get(headerName)).isNull();
request = request.mutate().headers(headers -> headers.add(headerName, headerValue1)).build();
assertThat(request.getHeaders().get(headerName)).containsExactly(headerValue1);
request = request.mutate().headers(headers -> headers.add(headerName, headerValue2)).build();
assertThat(request.getHeaders().get(headerName)).containsExactly(headerValue1, headerValue2);
}
@@ -151,19 +154,17 @@ public class ServerHttpRequestTests {
String headerValue2 = "value2";
String headerValue3 = "value3";
ServerHttpRequest request = createHttpRequest("/path");
ServerHttpRequest request = createRequest("/path");
assertThat(request.getHeaders().get(headerName)).isNull();
request = request.mutate().header(headerName, headerValue1, headerValue2).build();
assertThat(request.getHeaders().get(headerName)).containsExactly(headerValue1, headerValue2);
request = request.mutate().header(headerName, headerValue3).build();
assertThat(request.getHeaders().get(headerName)).containsExactly(headerValue3);
}
private ServerHttpRequest createHttpRequest(String uriString) throws Exception {
private ServerHttpRequest createRequest(String uriString) throws Exception {
URI uri = URI.create(uriString);
MockHttpServletRequest request = new TestHttpServletRequest(uri);
AsyncContext asyncContext = new MockAsyncContext(request, new MockHttpServletResponse());

View File

@@ -21,6 +21,7 @@ import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.function.Consumer;
import java.util.function.Function;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -215,16 +216,32 @@ public interface ClientResponse {
*/
String logPrefix();
/**
* Return a builder to mutate the this response, for example to change
* the status, headers, cookies, and replace or transform the body.
* @return a builder to mutate the request with
* @since 5.3
*/
default Builder mutate() {
return new DefaultClientResponseBuilder(this, true);
}
// Static builder methods
/**
* Create a builder with the status, headers, and cookies of the given response.
* <p><strong>Note:</strong> Note that the body in the returned builder is
* {@link Flux#empty()} by default. To carry over the one from the original
* response, use {@code otherResponse.bodyToFlux(DataBuffer.class)} or
* simply use the instance based {@link #mutate()} method.
* @param other the response to copy the status, headers, and cookies from
* @return the created builder
* @deprecated as of 5.3 in favor of the instance based {@link #mutate()}.
*/
@Deprecated
static Builder from(ClientResponse other) {
return new DefaultClientResponseBuilder(other);
return new DefaultClientResponseBuilder(other, false);
}
/**
@@ -371,19 +388,26 @@ public interface ClientResponse {
Builder cookies(Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer);
/**
* Set the body of the response. Calling this methods will
* {@linkplain org.springframework.core.io.buffer.DataBufferUtils#release(DataBuffer) release}
* the existing body of the builder.
* @param body the new body.
* Transform the response body, if set in the builder.
* @param transformer the transformation function to use
* @return this builder
* @since 5.3
*/
Builder body(Function<Flux<DataBuffer>, Flux<DataBuffer>> transformer);
/**
* Set the body of the response.
* <p><strong>Note:</strong> This methods will drain the existing body,
* if set in the builder.
* @param body the new body to use
* @return this builder
*/
Builder body(Flux<DataBuffer> body);
/**
* Set the body of the response to the UTF-8 encoded bytes of the given string.
* Calling this methods will
* {@linkplain org.springframework.core.io.buffer.DataBufferUtils#release(DataBuffer) release}
* the existing body of the builder.
* <p><strong>Note:</strong> This methods will drain the existing body,
* if set in the builder.
* @param body the new body.
* @return this builder
*/

View File

@@ -30,6 +30,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.codec.Hints;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
@@ -66,6 +67,8 @@ class DefaultClientResponse implements ClientResponse {
private final Supplier<HttpRequest> requestSupplier;
private final BodyExtractor.Context bodyExtractorContext;
public DefaultClientResponse(ClientHttpResponse response, ExchangeStrategies strategies,
String logPrefix, String requestDescription, Supplier<HttpRequest> requestSupplier) {
@@ -76,6 +79,22 @@ class DefaultClientResponse implements ClientResponse {
this.logPrefix = logPrefix;
this.requestDescription = requestDescription;
this.requestSupplier = requestSupplier;
this.bodyExtractorContext = new BodyExtractor.Context() {
@Override
public List<HttpMessageReader<?>> messageReaders() {
return strategies.messageReaders();
}
@Override
public Optional<ServerHttpResponse> serverResponse() {
return Optional.empty();
}
@Override
public Map<String, Object> hints() {
return Hints.from(Hints.LOG_PREFIX_HINT, logPrefix);
}
};
}
@@ -107,22 +126,7 @@ class DefaultClientResponse implements ClientResponse {
@SuppressWarnings("unchecked")
@Override
public <T> T body(BodyExtractor<T, ? super ClientHttpResponse> extractor) {
T result = extractor.extract(this.response, new BodyExtractor.Context() {
@Override
public List<HttpMessageReader<?>> messageReaders() {
return strategies.messageReaders();
}
@Override
public Optional<ServerHttpResponse> serverResponse() {
return Optional.empty();
}
@Override
public Map<String, Object> hints() {
return Hints.from(Hints.LOG_PREFIX_HINT, logPrefix);
}
});
T result = extractor.extract(this.response, this.bodyExtractorContext);
String description = "Body from " + this.requestDescription + " [DefaultClientResponse]";
if (result instanceof Mono) {
return (T) ((Mono<?>) result).checkpoint(description);
@@ -146,8 +150,10 @@ class DefaultClientResponse implements ClientResponse {
}
@Override
@SuppressWarnings("unchecked")
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
return body(BodyExtractors.toFlux(elementClass));
return elementClass.equals(DataBuffer.class) ?
(Flux<T>) body(BodyExtractors.toDataBuffers()) : body(BodyExtractors.toFlux(elementClass));
}
@Override
@@ -234,29 +240,28 @@ class DefaultClientResponse implements ClientResponse {
private class DefaultHeaders implements Headers {
private HttpHeaders delegate() {
return response.getHeaders();
}
private final HttpHeaders httpHeaders =
HttpHeaders.readOnlyHttpHeaders(response.getHeaders());
@Override
public OptionalLong contentLength() {
return toOptionalLong(delegate().getContentLength());
return toOptionalLong(this.httpHeaders.getContentLength());
}
@Override
public Optional<MediaType> contentType() {
return Optional.ofNullable(delegate().getContentType());
return Optional.ofNullable(this.httpHeaders.getContentType());
}
@Override
public List<String> header(String headerName) {
List<String> headerValues = delegate().get(headerName);
List<String> headerValues = this.httpHeaders.get(headerName);
return (headerValues != null ? headerValues : Collections.emptyList());
}
@Override
public HttpHeaders asHttpHeaders() {
return HttpHeaders.readOnlyHttpHeaders(delegate());
return this.httpHeaders;
}
private OptionalLong toOptionalLong(long value) {

View File

@@ -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.
@@ -19,11 +19,11 @@ package org.springframework.web.reactive.function.client;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;
import java.util.function.Function;
import reactor.core.publisher.Flux;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
@@ -31,6 +31,7 @@ import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
@@ -69,33 +70,42 @@ final class DefaultClientResponseBuilder implements ClientResponse.Builder {
private int statusCode = 200;
private final HttpHeaders headers = new HttpHeaders();
@Nullable
private HttpHeaders headers;
private final MultiValueMap<String, ResponseCookie> cookies = new LinkedMultiValueMap<>();
@Nullable
private MultiValueMap<String, ResponseCookie> cookies;
private Flux<DataBuffer> body = Flux.empty();
@Nullable
private ClientResponse originalResponse;
private HttpRequest request;
public DefaultClientResponseBuilder(ExchangeStrategies strategies) {
Assert.notNull(strategies, "ExchangeStrategies must not be null");
this.strategies = strategies;
this.headers = new HttpHeaders();
this.cookies = new LinkedMultiValueMap<>();
this.request = EMPTY_REQUEST;
}
public DefaultClientResponseBuilder(ClientResponse other) {
public DefaultClientResponseBuilder(ClientResponse other, boolean mutate) {
Assert.notNull(other, "ClientResponse must not be null");
this.strategies = other.strategies();
this.statusCode = other.rawStatusCode();
headers(headers -> headers.addAll(other.headers().asHttpHeaders()));
cookies(cookies -> cookies.addAll(other.cookies()));
if (other instanceof DefaultClientResponse) {
this.request = ((DefaultClientResponse) other).request();
if (mutate) {
this.body = other.bodyToFlux(DataBuffer.class);
}
else {
this.request = EMPTY_REQUEST;
this.headers = new HttpHeaders();
this.headers.addAll(other.headers().asHttpHeaders());
}
this.originalResponse = other;
this.request = (other instanceof DefaultClientResponse ?
((DefaultClientResponse) other).request() : EMPTY_REQUEST);
}
@@ -114,28 +124,50 @@ final class DefaultClientResponseBuilder implements ClientResponse.Builder {
@Override
public ClientResponse.Builder header(String headerName, String... headerValues) {
for (String headerValue : headerValues) {
this.headers.add(headerName, headerValue);
getHeaders().add(headerName, headerValue);
}
return this;
}
@Override
public ClientResponse.Builder headers(Consumer<HttpHeaders> headersConsumer) {
headersConsumer.accept(this.headers);
headersConsumer.accept(getHeaders());
return this;
}
@SuppressWarnings("ConstantConditions")
private HttpHeaders getHeaders() {
if (this.headers == null) {
this.headers = HttpHeaders.writableHttpHeaders(this.originalResponse.headers().asHttpHeaders());
}
return this.headers;
}
@Override
public DefaultClientResponseBuilder cookie(String name, String... values) {
for (String value : values) {
this.cookies.add(name, ResponseCookie.from(name, value).build());
getCookies().add(name, ResponseCookie.from(name, value).build());
}
return this;
}
@Override
public ClientResponse.Builder cookies(Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer) {
cookiesConsumer.accept(this.cookies);
cookiesConsumer.accept(getCookies());
return this;
}
@SuppressWarnings("ConstantConditions")
private MultiValueMap<String, ResponseCookie> getCookies() {
if (this.cookies == null) {
this.cookies = new LinkedMultiValueMap<>(this.originalResponse.cookies());
}
return this.cookies;
}
@Override
public ClientResponse.Builder body(Function<Flux<DataBuffer>, Flux<DataBuffer>> transformer) {
this.body = transformer.apply(this.body);
return this;
}
@@ -151,11 +183,10 @@ final class DefaultClientResponseBuilder implements ClientResponse.Builder {
public ClientResponse.Builder body(String body) {
Assert.notNull(body, "Body must not be null");
releaseBody();
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
this.body = Flux.just(body).
map(s -> {
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
return dataBufferFactory.wrap(bytes);
return new DefaultDataBufferFactory().wrap(bytes);
});
return this;
}
@@ -173,12 +204,12 @@ final class DefaultClientResponseBuilder implements ClientResponse.Builder {
@Override
public ClientResponse build() {
ClientHttpResponse httpResponse =
new BuiltClientHttpResponse(this.statusCode, this.headers, this.cookies, this.body);
// When building ClientResponse manually, the ClientRequest.logPrefix() has to be passed,
// e.g. via ClientResponse.Builder, but this (builder) is not used currently.
return new DefaultClientResponse(httpResponse, this.strategies, "", "", () -> this.request);
ClientHttpResponse httpResponse = new BuiltClientHttpResponse(
this.statusCode, this.headers, this.cookies, this.body, this.originalResponse);
return new DefaultClientResponse(
httpResponse, this.strategies, "", "", () -> this.request);
}
@@ -186,19 +217,33 @@ final class DefaultClientResponseBuilder implements ClientResponse.Builder {
private final int statusCode;
@Nullable
private final HttpHeaders headers;
@Nullable
private final MultiValueMap<String, ResponseCookie> cookies;
private final Flux<DataBuffer> body;
public BuiltClientHttpResponse(int statusCode, HttpHeaders headers,
MultiValueMap<String, ResponseCookie> cookies, Flux<DataBuffer> body) {
@Nullable
private final ClientResponse originalResponse;
public BuiltClientHttpResponse(int statusCode, @Nullable HttpHeaders headers,
@Nullable MultiValueMap<String, ResponseCookie> cookies, Flux<DataBuffer> body,
@Nullable ClientResponse originalResponse) {
Assert.isTrue(headers != null || originalResponse != null,
"Expected either headers or an original response with headers.");
Assert.isTrue(cookies != null || originalResponse != null,
"Expected either cookies or an original response with cookies.");
this.statusCode = statusCode;
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
this.cookies = CollectionUtils.unmodifiableMultiValueMap(cookies);
this.headers = (headers != null ? HttpHeaders.readOnlyHttpHeaders(headers) : null);
this.cookies = (cookies != null ? CollectionUtils.unmodifiableMultiValueMap(cookies) : null);
this.body = body;
this.originalResponse = originalResponse;
}
@Override
@@ -212,13 +257,15 @@ final class DefaultClientResponseBuilder implements ClientResponse.Builder {
}
@Override
@SuppressWarnings("ConstantConditions")
public HttpHeaders getHeaders() {
return this.headers;
return (this.headers != null ? this.headers : this.originalResponse.headers().asHttpHeaders());
}
@Override
@SuppressWarnings("ConstantConditions")
public MultiValueMap<String, ResponseCookie> getCookies() {
return this.cookies;
return (this.cookies != null ? this.cookies : this.originalResponse.cookies());
}
@Override

View File

@@ -265,8 +265,8 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
.map(filter -> filter.apply(exchange))
.orElse(exchange) : exchange);
return new DefaultWebClient(filteredExchange, initUriBuilderFactory(),
this.defaultHeaders != null ? unmodifiableCopy(this.defaultHeaders) : null,
this.defaultCookies != null ? unmodifiableCopy(this.defaultCookies) : null,
this.defaultHeaders != null ? HttpHeaders.readOnlyHttpHeaders(this.defaultHeaders) : null,
this.defaultCookies != null ? HttpHeaders.readOnlyHttpHeaders(this.defaultCookies) : null,
this.defaultRequest, new DefaultWebClientBuilder(this));
}
@@ -308,10 +308,6 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
return factory;
}
private static HttpHeaders unmodifiableCopy(HttpHeaders headers) {
return HttpHeaders.readOnlyHttpHeaders(headers);
}
private static <K, V> MultiValueMap<K, V> unmodifiableCopy(MultiValueMap<K, V> map) {
return CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<>(map));
}

View File

@@ -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.
@@ -22,16 +22,13 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyExtractors;
/**
* Static factory methods providing access to built-in implementations of
@@ -64,11 +61,10 @@ public abstract class ExchangeFilterFunctions {
*/
public static ExchangeFilterFunction limitResponseSize(long maxByteCount) {
return (request, next) ->
next.exchange(request).map(response -> {
Flux<DataBuffer> body = response.body(BodyExtractors.toDataBuffers());
body = DataBufferUtils.takeUntilByteCount(body, maxByteCount);
return ClientResponse.from(response).body(body).build();
});
next.exchange(request).map(response ->
response.mutate()
.body(body -> DataBufferUtils.takeUntilByteCount(body, maxByteCount))
.build());
}
/**

View File

@@ -36,6 +36,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.codec.DecodingException;
import org.springframework.core.codec.Hints;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRange;
@@ -195,8 +196,10 @@ class DefaultServerRequest implements ServerRequest {
}
@Override
@SuppressWarnings("unchecked")
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
Flux<T> flux = body(BodyExtractors.toFlux(elementClass));
Flux<T> flux = (elementClass.equals(DataBuffer.class) ?
(Flux<T>) request().getBody() : body(BodyExtractors.toFlux(elementClass)));
return flux.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER)
.onErrorMap(DecodingException.class, DECODING_MAPPER);
}
@@ -261,60 +264,59 @@ class DefaultServerRequest implements ServerRequest {
private class DefaultHeaders implements Headers {
private HttpHeaders delegate() {
return request().getHeaders();
}
private final HttpHeaders httpHeaders =
HttpHeaders.readOnlyHttpHeaders(request().getHeaders());
@Override
public List<MediaType> accept() {
return delegate().getAccept();
return this.httpHeaders.getAccept();
}
@Override
public List<Charset> acceptCharset() {
return delegate().getAcceptCharset();
return this.httpHeaders.getAcceptCharset();
}
@Override
public List<Locale.LanguageRange> acceptLanguage() {
return delegate().getAcceptLanguage();
return this.httpHeaders.getAcceptLanguage();
}
@Override
public OptionalLong contentLength() {
long value = delegate().getContentLength();
long value = this.httpHeaders.getContentLength();
return (value != -1 ? OptionalLong.of(value) : OptionalLong.empty());
}
@Override
public Optional<MediaType> contentType() {
return Optional.ofNullable(delegate().getContentType());
return Optional.ofNullable(this.httpHeaders.getContentType());
}
@Override
public InetSocketAddress host() {
return delegate().getHost();
return this.httpHeaders.getHost();
}
@Override
public List<HttpRange> range() {
return delegate().getRange();
return this.httpHeaders.getRange();
}
@Override
public List<String> header(String headerName) {
List<String> headerValues = delegate().get(headerName);
List<String> headerValues = this.httpHeaders.get(headerName);
return (headerValues != null ? headerValues : Collections.emptyList());
}
@Override
public HttpHeaders asHttpHeaders() {
return HttpHeaders.readOnlyHttpHeaders(delegate());
return this.httpHeaders;
}
@Override
public String toString() {
return delegate().toString();
return this.httpHeaders.toString();
}
}

View File

@@ -32,7 +32,7 @@ import org.springframework.ui.Model;
*/
class DefaultRendering implements Rendering {
private static final HttpHeaders EMPTY_HEADERS = HttpHeaders.readOnlyHttpHeaders(new HttpHeaders());
private static final HttpHeaders EMPTY_HEADERS = HttpHeaders.EMPTY;
private final Object view;

View File

@@ -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.
@@ -64,42 +64,38 @@ public class DefaultClientResponseBuilderTests {
}
@Test
public void from() {
Flux<DataBuffer> otherBody = Flux.just("foo", "bar")
.map(s -> s.getBytes(StandardCharsets.UTF_8))
.map(dataBufferFactory::wrap);
public void mutate() {
ClientResponse other = ClientResponse.create(HttpStatus.BAD_REQUEST, ExchangeStrategies.withDefaults())
ClientResponse originalResponse = ClientResponse
.create(HttpStatus.BAD_REQUEST, ExchangeStrategies.withDefaults())
.header("foo", "bar")
.header("bar", "baz")
.cookie("baz", "qux")
.body(otherBody)
.body(Flux.just("foobar".getBytes(StandardCharsets.UTF_8)).map(dataBufferFactory::wrap))
.build();
Flux<DataBuffer> body = Flux.just("baz")
.map(s -> s.getBytes(StandardCharsets.UTF_8))
.map(dataBufferFactory::wrap);
ClientResponse result = ClientResponse.from(other)
.headers(httpHeaders -> httpHeaders.set("foo", "baar"))
ClientResponse result = originalResponse.mutate()
.statusCode(HttpStatus.OK)
.headers(headers -> headers.set("foo", "baar"))
.cookies(cookies -> cookies.set("baz", ResponseCookie.from("baz", "quux").build()))
.body(body)
.build();
assertThat(result.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
assertThat(result.headers().asHttpHeaders().size()).isEqualTo(1);
assertThat(result.statusCode()).isEqualTo(HttpStatus.OK);
assertThat(result.headers().asHttpHeaders().size()).isEqualTo(2);
assertThat(result.headers().asHttpHeaders().getFirst("foo")).isEqualTo("baar");
assertThat(result.headers().asHttpHeaders().getFirst("bar")).isEqualTo("baz");
assertThat(result.cookies().size()).isEqualTo(1);
assertThat(result.cookies().getFirst("baz").getValue()).isEqualTo("quux");
StepVerifier.create(result.bodyToFlux(String.class))
.expectNext("baz")
.expectNext("foobar")
.verifyComplete();
}
@Test
public void fromCustomStatus() {
public void mutateWithCustomStatus() {
ClientResponse other = ClientResponse.create(499, ExchangeStrategies.withDefaults()).build();
ClientResponse result = ClientResponse.from(other).build();
ClientResponse result = other.mutate().build();
assertThat(result.rawStatusCode()).isEqualTo(499);
assertThatIllegalArgumentException().isThrownBy(result::statusCode);

View File

@@ -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.
@@ -60,6 +60,8 @@ public class DefaultClientResponseTests {
private ClientHttpResponse mockResponse;
private final HttpHeaders httpHeaders = new HttpHeaders();
private ExchangeStrategies mockExchangeStrategies;
private DefaultClientResponse defaultClientResponse;
@@ -68,6 +70,7 @@ public class DefaultClientResponseTests {
@BeforeEach
public void createMocks() {
mockResponse = mock(ClientHttpResponse.class);
given(mockResponse.getHeaders()).willReturn(this.httpHeaders);
mockExchangeStrategies = mock(ExchangeStrategies.class);
defaultClientResponse = new DefaultClientResponse(mockResponse, mockExchangeStrategies, "", "", () -> null);
}
@@ -91,7 +94,6 @@ public class DefaultClientResponseTests {
@Test
public void header() {
HttpHeaders httpHeaders = new HttpHeaders();
long contentLength = 42L;
httpHeaders.setContentLength(contentLength);
MediaType contentType = MediaType.TEXT_PLAIN;
@@ -233,7 +235,6 @@ public class DefaultClientResponseTests {
= factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
given(mockResponse.getHeaders()).willReturn(httpHeaders);
given(mockResponse.getStatusCode()).willThrow(new IllegalArgumentException("999"));
@@ -293,13 +294,12 @@ public class DefaultClientResponseTests {
}
@Test
public void toEntityListWithUnknownStatusCode() throws Exception {
public void toEntityListWithUnknownStatusCode() {
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
given(mockResponse.getHeaders()).willReturn(httpHeaders);
given(mockResponse.getStatusCode()).willThrow(new IllegalArgumentException("999"));
@@ -321,8 +321,7 @@ public class DefaultClientResponseTests {
@Test
public void toEntityListTypeReference() {
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
DefaultDataBuffer dataBuffer = factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
mockTextPlainResponse(body);
@@ -332,8 +331,7 @@ public class DefaultClientResponseTests {
given(mockExchangeStrategies.messageReaders()).willReturn(messageReaders);
ResponseEntity<List<String>> result = defaultClientResponse.toEntityList(
new ParameterizedTypeReference<String>() {
}).block();
new ParameterizedTypeReference<String>() {}).block();
assertThat(result.getBody()).isEqualTo(Collections.singletonList("foo"));
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(result.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value());
@@ -342,9 +340,7 @@ public class DefaultClientResponseTests {
private void mockTextPlainResponse(Flux<DataBuffer> body) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
given(mockResponse.getHeaders()).willReturn(httpHeaders);
given(mockResponse.getStatusCode()).willReturn(HttpStatus.OK);
given(mockResponse.getRawStatusCode()).willReturn(HttpStatus.OK.value());
given(mockResponse.getBody()).willReturn(body);

View File

@@ -305,62 +305,62 @@ class DefaultServerRequest implements ServerRequest {
*/
static class DefaultRequestHeaders implements Headers {
private final HttpHeaders delegate;
private final HttpHeaders httpHeaders;
public DefaultRequestHeaders(HttpHeaders delegate) {
this.delegate = delegate;
public DefaultRequestHeaders(HttpHeaders httpHeaders) {
this.httpHeaders = HttpHeaders.readOnlyHttpHeaders(httpHeaders);
}
@Override
public List<MediaType> accept() {
return this.delegate.getAccept();
return this.httpHeaders.getAccept();
}
@Override
public List<Charset> acceptCharset() {
return this.delegate.getAcceptCharset();
return this.httpHeaders.getAcceptCharset();
}
@Override
public List<Locale.LanguageRange> acceptLanguage() {
return this.delegate.getAcceptLanguage();
return this.httpHeaders.getAcceptLanguage();
}
@Override
public OptionalLong contentLength() {
long value = this.delegate.getContentLength();
long value = this.httpHeaders.getContentLength();
return (value != -1 ? OptionalLong.of(value) : OptionalLong.empty());
}
@Override
public Optional<MediaType> contentType() {
return Optional.ofNullable(this.delegate.getContentType());
return Optional.ofNullable(this.httpHeaders.getContentType());
}
@Override
public InetSocketAddress host() {
return this.delegate.getHost();
return this.httpHeaders.getHost();
}
@Override
public List<HttpRange> range() {
return this.delegate.getRange();
return this.httpHeaders.getRange();
}
@Override
public List<String> header(String headerName) {
List<String> headerValues = this.delegate.get(headerName);
List<String> headerValues = this.httpHeaders.get(headerName);
return (headerValues != null ? headerValues : Collections.emptyList());
}
@Override
public HttpHeaders asHttpHeaders() {
return HttpHeaders.readOnlyHttpHeaders(this.delegate);
return this.httpHeaders;
}
@Override
public String toString() {
return this.delegate.toString();
return this.httpHeaders.toString();
}
}