diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/AbstractClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/AbstractClientHttpRequest.java index 8734521f048..8a9e1a3f90b 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/AbstractClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/AbstractClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -81,7 +81,7 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest { return this.readOnlyHeaders; } else if (State.COMMITTED.equals(this.state.get())) { - this.readOnlyHeaders = HttpHeaders.readOnlyHttpHeaders(this.headers); + this.readOnlyHeaders = initReadOnlyHeaders(); return this.readOnlyHeaders; } else { @@ -89,6 +89,16 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest { } } + /** + * Initialize the read-only headers after the request is committed. + *

By default, this method simply applies a read-only wrapper. + * Subclasses can do the same for headers from the native request. + * @since 5.3.15 + */ + protected HttpHeaders initReadOnlyHeaders() { + return HttpHeaders.readOnlyHttpHeaders(this.headers); + } + @Override public MultiValueMap getCookies() { if (State.COMMITTED.equals(this.state.get())) { diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java index 8af84d6f0e0..c0b399d4eb4 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -61,6 +61,8 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest { @Nullable private Flux byteBufferFlux; + private transient long contentLength = -1; + public HttpComponentsClientHttpRequest(HttpMethod method, URI uri, HttpClientContext context, DataBufferFactory dataBufferFactory) { @@ -127,6 +129,8 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest { if (!this.httpRequest.containsHeader(HttpHeaders.ACCEPT)) { this.httpRequest.addHeader(HttpHeaders.ACCEPT, MediaType.ALL_VALUE); } + + this.contentLength = headers.getContentLength(); } @Override @@ -148,6 +152,11 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest { }); } + @Override + protected HttpHeaders initReadOnlyHeaders() { + return HttpHeaders.readOnlyHttpHeaders(new HttpComponentsHeadersAdapter(this.httpRequest)); + } + public AsyncRequestProducer toRequestProducer() { ReactiveEntityProducer reactiveEntityProducer = null; @@ -157,8 +166,8 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest { if (getHeaders().getContentType() != null) { contentType = ContentType.parse(getHeaders().getContentType().toString()); } - reactiveEntityProducer = new ReactiveEntityProducer(this.byteBufferFlux, getHeaders().getContentLength(), - contentType, contentEncoding); + reactiveEntityProducer = new ReactiveEntityProducer( + this.byteBufferFlux, this.contentLength, contentType, contentEncoding); } return new BasicRequestProducer(this.httpRequest, reactiveEntityProducer); diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsHeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsHeadersAdapter.java index 9b914c1a4e2..2b6a855dc3d 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsHeadersAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsHeadersAdapter.java @@ -28,7 +28,7 @@ import java.util.Map; import java.util.Set; import org.apache.hc.core5.http.Header; -import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.HttpMessage; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; @@ -44,23 +44,23 @@ import org.springframework.util.MultiValueMap; */ class HttpComponentsHeadersAdapter implements MultiValueMap { - private final HttpResponse response; + private final HttpMessage message; - HttpComponentsHeadersAdapter(HttpResponse response) { - this.response = response; + HttpComponentsHeadersAdapter(HttpMessage message) { + this.message = message; } @Override public String getFirst(String key) { - Header header = this.response.getFirstHeader(key); + Header header = this.message.getFirstHeader(key); return (header != null ? header.getValue() : null); } @Override public void add(String key, @Nullable String value) { - this.response.addHeader(key, value); + this.message.addHeader(key, value); } @Override @@ -75,7 +75,7 @@ class HttpComponentsHeadersAdapter implements MultiValueMap { @Override public void set(String key, @Nullable String value) { - this.response.setHeader(key, value); + this.message.setHeader(key, value); } @Override @@ -86,29 +86,29 @@ class HttpComponentsHeadersAdapter implements MultiValueMap { @Override public Map toSingleValueMap() { Map map = CollectionUtils.newLinkedHashMap(size()); - this.response.headerIterator().forEachRemaining(h -> map.putIfAbsent(h.getName(), h.getValue())); + this.message.headerIterator().forEachRemaining(h -> map.putIfAbsent(h.getName(), h.getValue())); return map; } @Override public int size() { - return this.response.getHeaders().length; + return this.message.getHeaders().length; } @Override public boolean isEmpty() { - return (this.response.getHeaders().length == 0); + return (this.message.getHeaders().length == 0); } @Override public boolean containsKey(Object key) { - return (key instanceof String headerName && this.response.containsHeader(headerName)); + return (key instanceof String headerName && this.message.containsHeader(headerName)); } @Override public boolean containsValue(Object value) { return (value instanceof String && - Arrays.stream(this.response.getHeaders()).anyMatch(h -> h.getValue().equals(value))); + Arrays.stream(this.message.getHeaders()).anyMatch(h -> h.getValue().equals(value))); } @Nullable @@ -116,7 +116,7 @@ class HttpComponentsHeadersAdapter implements MultiValueMap { public List get(Object key) { List values = null; if (containsKey(key)) { - Header[] headers = this.response.getHeaders((String) key); + Header[] headers = this.message.getHeaders((String) key); values = new ArrayList<>(headers.length); for (Header header : headers) { values.add(header.getValue()); @@ -138,7 +138,7 @@ class HttpComponentsHeadersAdapter implements MultiValueMap { public List remove(Object key) { if (key instanceof String headerName) { List oldValues = get(key); - this.response.removeHeaders(headerName); + this.message.removeHeaders(headerName); return oldValues; } return null; @@ -151,13 +151,13 @@ class HttpComponentsHeadersAdapter implements MultiValueMap { @Override public void clear() { - this.response.setHeaders(); + this.message.setHeaders(); } @Override public Set keySet() { Set keys = new LinkedHashSet<>(size()); - for (Header header : this.response.getHeaders()) { + for (Header header : this.message.getHeaders()) { keys.add(header.getName()); } return keys; @@ -166,7 +166,7 @@ class HttpComponentsHeadersAdapter implements MultiValueMap { @Override public Collection> values() { Collection> values = new ArrayList<>(size()); - for (Header header : this.response.getHeaders()) { + for (Header header : this.message.getHeaders()) { values.add(get(header.getName())); } return values; @@ -196,7 +196,7 @@ class HttpComponentsHeadersAdapter implements MultiValueMap { private class EntryIterator implements Iterator>> { - private final Iterator

iterator = response.headerIterator(); + private final Iterator
iterator = message.headerIterator(); @Override public boolean hasNext() { diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java index 80ad419ef49..3d6b6b14b5f 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -125,7 +125,6 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest { }); } - @Override protected void applyCookies() { getCookies().values().stream().flatMap(Collection::stream) @@ -144,9 +143,13 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest { }); } + @Override + protected HttpHeaders initReadOnlyHeaders() { + return HttpHeaders.readOnlyHttpHeaders(new JettyHeadersAdapter(this.jettyRequest.getHeaders())); + } + public ReactiveRequest toReactiveRequest() { return this.builder.build(); } - } diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java index f098f8594ba..332183457e6 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -31,6 +31,7 @@ import reactor.netty.http.client.HttpClientRequest; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.core.io.buffer.NettyDataBufferFactory; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ZeroCopyHttpOutputMessage; @@ -133,4 +134,9 @@ class ReactorClientHttpRequest extends AbstractClientHttpRequest implements Zero .forEach(this.request::addCookie); } + @Override + protected HttpHeaders initReadOnlyHeaders() { + return HttpHeaders.readOnlyHttpHeaders(new NettyHeadersAdapter(this.request.requestHeaders())); + } + }