From 3d2befc84a89f8d8a3e3834358e5b8c883052f63 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Wed, 5 Jul 2023 15:41:39 +0200 Subject: [PATCH] Rearrange HttpHeaders adapters This commit moves HttpHeaders that are used in multiple places (client and server, reactive and non-reactive) to a new, separate http.support package. Closes gh-30823 --- .../HttpComponentsClientHttpResponse.java | 9 +- .../http/client/JettyClientHttpResponse.java | 3 +- .../HttpComponentsClientHttpRequest.java | 1 + .../HttpComponentsClientHttpResponse.java | 1 + .../reactive/JettyClientHttpRequest.java | 2 +- .../reactive/JettyClientHttpResponse.java | 2 +- .../client/reactive/Netty5HeadersAdapter.java | 287 ------------------ .../reactive/ReactorClientHttpRequest.java | 3 +- .../reactive/ReactorClientHttpResponse.java | 5 +- .../ReactorNetty2ClientHttpRequest.java | 1 + .../ReactorNetty2ClientHttpResponse.java | 1 + .../server/reactive/JettyHeadersAdapter.java | 277 ----------------- .../reactive/JettyHttpHandlerAdapter.java | 1 + .../server/reactive/NettyHeadersAdapter.java | 279 ----------------- .../ReactorNetty2ServerHttpRequest.java | 1 + .../ReactorNetty2ServerHttpResponse.java | 1 + .../reactive/ReactorServerHttpRequest.java | 3 +- .../reactive/ReactorServerHttpResponse.java | 3 +- .../HttpComponentsHeadersAdapter.java | 14 +- .../JettyHeadersAdapter.java | 6 +- .../Netty4HeadersAdapter.java} | 20 +- .../Netty5HeadersAdapter.java | 16 +- .../http/support/package-info.java | 10 + .../server/reactive/HeadersAdaptersTests.java | 7 +- 24 files changed, 73 insertions(+), 880 deletions(-) delete mode 100644 spring-web/src/main/java/org/springframework/http/client/reactive/Netty5HeadersAdapter.java delete mode 100644 spring-web/src/main/java/org/springframework/http/server/reactive/JettyHeadersAdapter.java delete mode 100644 spring-web/src/main/java/org/springframework/http/server/reactive/NettyHeadersAdapter.java rename spring-web/src/main/java/org/springframework/http/{client/reactive => support}/HttpComponentsHeadersAdapter.java (93%) rename spring-web/src/main/java/org/springframework/http/{client => support}/JettyHeadersAdapter.java (98%) rename spring-web/src/main/java/org/springframework/http/{client/reactive/NettyHeadersAdapter.java => support/Netty4HeadersAdapter.java} (91%) rename spring-web/src/main/java/org/springframework/http/{server/reactive => support}/Netty5HeadersAdapter.java (94%) create mode 100644 spring-web/src/main/java/org/springframework/http/support/package-info.java diff --git a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpResponse.java index b4090bad14..a636deeaac 100644 --- a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpResponse.java @@ -20,13 +20,14 @@ import java.io.IOException; import java.io.InputStream; import org.apache.hc.core5.http.ClassicHttpResponse; -import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; +import org.springframework.http.support.HttpComponentsHeadersAdapter; import org.springframework.lang.Nullable; +import org.springframework.util.MultiValueMap; /** * {@link ClientHttpResponse} implementation based on @@ -65,10 +66,8 @@ final class HttpComponentsClientHttpResponse implements ClientHttpResponse { @Override public HttpHeaders getHeaders() { if (this.headers == null) { - this.headers = new HttpHeaders(); - for (Header header : this.httpResponse.getHeaders()) { - this.headers.add(header.getName(), header.getValue()); - } + MultiValueMap adapter = new HttpComponentsHeadersAdapter(this.httpResponse); + this.headers = HttpHeaders.readOnlyHttpHeaders(adapter); } return this.headers; } diff --git a/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpResponse.java index 67f6af1c07..5c59c05629 100644 --- a/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/JettyClientHttpResponse.java @@ -23,10 +23,11 @@ import org.eclipse.jetty.client.api.Response; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; +import org.springframework.http.support.JettyHeadersAdapter; import org.springframework.util.MultiValueMap; /** - * {@link ClientHttpResponse} implementation based on based on Jetty's + * {@link ClientHttpResponse} implementation based on Jetty's * {@link org.eclipse.jetty.client.HttpClient}. * * @author Arjen Poutsma 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 9a87c252ab..92e20d32c3 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 @@ -40,6 +40,7 @@ import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.http.support.HttpComponentsHeadersAdapter; import org.springframework.lang.Nullable; /** diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java index 45cd63bd79..3cec1371ac 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java @@ -31,6 +31,7 @@ import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseCookie; +import org.springframework.http.support.HttpComponentsHeadersAdapter; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; 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 6ef59caeda..dac63c668c 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 @@ -37,7 +37,7 @@ import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.http.client.JettyHeadersAdapter; +import org.springframework.http.support.JettyHeadersAdapter; /** * {@link ClientHttpRequest} implementation for the Jetty ReactiveStreams HTTP client. diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java index cc7dda7b0e..edfbe50fe1 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java @@ -29,7 +29,7 @@ import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseCookie; -import org.springframework.http.client.JettyHeadersAdapter; +import org.springframework.http.support.JettyHeadersAdapter; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/Netty5HeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/client/reactive/Netty5HeadersAdapter.java deleted file mode 100644 index ccb9438bcd..0000000000 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/Netty5HeadersAdapter.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.StreamSupport; - -import io.netty5.handler.codec.http.headers.HttpHeaders; - -import org.springframework.lang.Nullable; -import org.springframework.util.CollectionUtils; -import org.springframework.util.MultiValueMap; - -/** - * {@code MultiValueMap} implementation for wrapping Netty HTTP headers. - * - *

There is a duplicate of this class in the server package! - * - *

This class is based on {@link NettyHeadersAdapter}. - * - * @author Violeta Georgieva - * @since 6.0 - */ -class Netty5HeadersAdapter implements MultiValueMap { - - private final HttpHeaders headers; - - - Netty5HeadersAdapter(HttpHeaders headers) { - this.headers = headers; - } - - - @Override - @Nullable - public String getFirst(String key) { - CharSequence value = this.headers.get(key); - return (value != null ? value.toString() : null); - } - - @Override - public void add(String key, @Nullable String value) { - if (value != null) { - this.headers.add(key, value); - } - } - - @Override - public void addAll(String key, List values) { - this.headers.add(key, values); - } - - @Override - public void addAll(MultiValueMap values) { - values.forEach(this.headers::add); - } - - @Override - public void set(String key, @Nullable String value) { - if (value != null) { - this.headers.set(key, value); - } - } - - @Override - public void setAll(Map values) { - values.forEach(this.headers::set); - } - - @Override - public Map toSingleValueMap() { - Map singleValueMap = CollectionUtils.newLinkedHashMap(this.headers.size()); - this.headers.forEach(entry -> singleValueMap.putIfAbsent( - entry.getKey().toString(), entry.getValue().toString())); - 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 headerName && this.headers.contains(headerName)); - } - - @Override - public boolean containsValue(Object value) { - return (value instanceof CharSequence && - StreamSupport.stream(this.headers.spliterator(), false) - .anyMatch(entry -> value.equals(entry.getValue()))); - } - - @Override - @Nullable - public List get(Object key) { - Iterator iterator = this.headers.valuesIterator((CharSequence) key); - if (iterator.hasNext()) { - List result = new ArrayList<>(); - iterator.forEachRemaining(value -> result.add(value.toString())); - return result; - } - return null; - } - - @Nullable - @Override - public List put(String key, @Nullable List value) { - List previousValues = get(key); - this.headers.set(key, value); - return previousValues; - } - - @Nullable - @Override - public List remove(Object key) { - if (key instanceof String headerName) { - List previousValues = get(headerName); - this.headers.remove(headerName); - return previousValues; - } - return null; - } - - @Override - public void putAll(Map> map) { - map.forEach(this.headers::set); - } - - @Override - public void clear() { - this.headers.clear(); - } - - @Override - public Set keySet() { - return new HeaderNames(); - } - - @Override - public Collection> values() { - List> result = new ArrayList<>(this.headers.size()); - forEach((key, value) -> result.add(value)); - return result; - } - - @Override - public Set>> entrySet() { - return new AbstractSet<>() { - @Override - public Iterator>> 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>> { - - private final Iterator names = headers.names().iterator(); - - @Override - public boolean hasNext() { - return this.names.hasNext(); - } - - @Override - public Entry> next() { - return new HeaderEntry(this.names.next()); - } - } - - - private class HeaderEntry implements Entry> { - - private final CharSequence key; - - HeaderEntry(CharSequence key) { - this.key = key; - } - - @Override - public String getKey() { - return this.key.toString(); - } - - @Override - public List getValue() { - List values = get(this.key); - return (values != null ? values : Collections.emptyList()); - } - - @Override - public List setValue(List value) { - List previousValues = getValue(); - headers.set(this.key, value); - return previousValues; - } - } - - - private class HeaderNames extends AbstractSet { - - @Override - public Iterator iterator() { - return new HeaderNamesIterator(headers.names().iterator()); - } - - @Override - public int size() { - return headers.names().size(); - } - } - - private final class HeaderNamesIterator implements Iterator { - - private final Iterator iterator; - - @Nullable - private CharSequence currentName; - - private HeaderNamesIterator(Iterator iterator) { - this.iterator = iterator; - } - - @Override - public boolean hasNext() { - return this.iterator.hasNext(); - } - - @Override - public String next() { - this.currentName = this.iterator.next(); - return this.currentName.toString(); - } - - @Override - public void remove() { - if (this.currentName == null) { - throw new IllegalStateException("No current Header in iterator"); - } - if (!headers.contains(this.currentName)) { - throw new IllegalStateException("Header not present: " + this.currentName); - } - headers.remove(this.currentName); - } - } - -} 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 d681b88f1d..6895ad30a8 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 @@ -33,6 +33,7 @@ import org.springframework.core.io.buffer.NettyDataBufferFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ZeroCopyHttpOutputMessage; +import org.springframework.http.support.Netty4HeadersAdapter; /** * {@link ClientHttpRequest} implementation for the Reactor-Netty HTTP client. @@ -136,7 +137,7 @@ class ReactorClientHttpRequest extends AbstractClientHttpRequest implements Zero @Override protected HttpHeaders initReadOnlyHeaders() { - return HttpHeaders.readOnlyHttpHeaders(new NettyHeadersAdapter(this.request.requestHeaders())); + return HttpHeaders.readOnlyHttpHeaders(new Netty4HeadersAdapter(this.request.requestHeaders())); } } diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java index 0b1d4b67c0..411fda8f20 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java @@ -37,6 +37,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseCookie; +import org.springframework.http.support.Netty4HeadersAdapter; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; @@ -74,7 +75,7 @@ class ReactorClientHttpResponse implements ClientHttpResponse { */ public ReactorClientHttpResponse(HttpClientResponse response, Connection connection) { this.response = response; - MultiValueMap adapter = new NettyHeadersAdapter(response.responseHeaders()); + MultiValueMap adapter = new Netty4HeadersAdapter(response.responseHeaders()); this.headers = HttpHeaders.readOnlyHttpHeaders(adapter); this.inbound = connection.inbound(); this.bufferFactory = new NettyDataBufferFactory(connection.outbound().alloc()); @@ -87,7 +88,7 @@ class ReactorClientHttpResponse implements ClientHttpResponse { @Deprecated public ReactorClientHttpResponse(HttpClientResponse response, NettyInbound inbound, ByteBufAllocator alloc) { this.response = response; - MultiValueMap adapter = new NettyHeadersAdapter(response.responseHeaders()); + MultiValueMap adapter = new Netty4HeadersAdapter(response.responseHeaders()); this.headers = HttpHeaders.readOnlyHttpHeaders(adapter); this.inbound = inbound; this.bufferFactory = new NettyDataBufferFactory(alloc); diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java index 23f736fa46..749326fa5f 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java @@ -33,6 +33,7 @@ import org.springframework.core.io.buffer.Netty5DataBufferFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ZeroCopyHttpOutputMessage; +import org.springframework.http.support.Netty5HeadersAdapter; /** * {@link ClientHttpRequest} implementation for the Reactor Netty 2 (Netty 5) HTTP client. diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpResponse.java index 597f4809c8..a1b243d435 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpResponse.java @@ -36,6 +36,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseCookie; +import org.springframework.http.support.Netty5HeadersAdapter; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHeadersAdapter.java deleted file mode 100644 index e0dfecfd77..0000000000 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHeadersAdapter.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * 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. - * 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.server.reactive; - -import java.util.AbstractSet; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -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.CollectionUtils; -import org.springframework.util.MultiValueMap; - -/** - * {@code MultiValueMap} implementation for wrapping Jetty HTTP headers. - * - *

There is a duplicate of this class in the client package! - * - * @author Brian Clozel - * @author Juergen Hoeller - * @since 5.1.1 - */ -class JettyHeadersAdapter implements MultiValueMap { - - private final HttpFields.Mutable headers; - - - JettyHeadersAdapter(HttpFields.Mutable 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 values) { - values.forEach(value -> add(key, value)); - } - - @Override - public void addAll(MultiValueMap values) { - values.forEach(this::addAll); - } - - @Override - public void set(String key, @Nullable String value) { - this.headers.put(key, value); - } - - @Override - public void setAll(Map values) { - values.forEach(this::set); - } - - @Override - public Map toSingleValueMap() { - Map singleValueMap = CollectionUtils.newLinkedHashMap(this.headers.size()); - Iterator 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 headerName && this.headers.contains(headerName)); - } - - @Override - public boolean containsValue(Object value) { - return (value instanceof String searchString && - this.headers.stream().anyMatch(field -> field.contains(searchString))); - } - - @Nullable - @Override - public List get(Object key) { - if (containsKey(key)) { - return this.headers.getValuesList((String) key); - } - return null; - } - - @Nullable - @Override - public List put(String key, List value) { - List oldValues = get(key); - this.headers.put(key, value); - return oldValues; - } - - @Nullable - @Override - public List remove(Object key) { - if (key instanceof String headerName) { - List oldValues = get(key); - this.headers.remove(headerName); - return oldValues; - } - return null; - } - - @Override - public void putAll(Map> map) { - map.forEach(this::put); - } - - @Override - public void clear() { - this.headers.clear(); - } - - @Override - public Set keySet() { - return new HeaderNames(); - } - - @Override - public Collection> values() { - return this.headers.getFieldNamesCollection().stream() - .map(this.headers::getValuesList).toList(); - } - - @Override - public Set>> entrySet() { - return new AbstractSet<>() { - @Override - public Iterator>> iterator() { - return new EntryIterator(); - } - @Override - public int size() { - return headers.size(); - } - }; - } - - - @Override - public String toString() { - return HttpHeaders.formatHeaders(this); - } - - - private class EntryIterator implements Iterator>> { - - private final Iterator names = headers.getFieldNamesCollection().iterator(); - - @Override - public boolean hasNext() { - return this.names.hasNext(); - } - - @Override - public Entry> next() { - return new HeaderEntry(this.names.next()); - } - } - - - private class HeaderEntry implements Entry> { - - private final String key; - - HeaderEntry(String key) { - this.key = key; - } - - @Override - public String getKey() { - return this.key; - } - - @Override - public List getValue() { - return headers.getValuesList(this.key); - } - - @Override - public List setValue(List value) { - List previousValues = headers.getValuesList(this.key); - headers.put(this.key, value); - return previousValues; - } - } - - - private class HeaderNames extends AbstractSet { - - @Override - public Iterator iterator() { - return new HeaderNamesIterator(headers.getFieldNamesCollection().iterator()); - } - - @Override - public int size() { - return headers.getFieldNamesCollection().size(); - } - } - - - private final class HeaderNamesIterator implements Iterator { - - private final Iterator iterator; - - @Nullable - private String currentName; - - private HeaderNamesIterator(Iterator iterator) { - this.iterator = iterator; - } - - @Override - public boolean hasNext() { - return this.iterator.hasNext(); - } - - @Override - public String next() { - this.currentName = this.iterator.next(); - return this.currentName; - } - - @Override - public void remove() { - if (this.currentName == null) { - throw new IllegalStateException("No current Header in iterator"); - } - if (!headers.contains(this.currentName)) { - throw new IllegalStateException("Header not present: " + this.currentName); - } - headers.remove(this.currentName); - } - } - -} diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java index e9c8503453..5742e210d5 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java @@ -34,6 +34,7 @@ import org.eclipse.jetty.server.Response; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.HttpHeaders; +import org.springframework.http.support.JettyHeadersAdapter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.MultiValueMap; diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/NettyHeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/NettyHeadersAdapter.java deleted file mode 100644 index 86bbd7dae9..0000000000 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/NettyHeadersAdapter.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * 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. - * 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.server.reactive; - -import java.util.AbstractSet; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import io.netty.handler.codec.http.HttpHeaders; - -import org.springframework.lang.Nullable; -import org.springframework.util.CollectionUtils; -import org.springframework.util.MultiValueMap; - -/** - * {@code MultiValueMap} implementation for wrapping Netty HTTP headers. - * - *

There is a duplicate of this class in the client package! - * - * @author Brian Clozel - * @since 5.1.1 - */ -final class NettyHeadersAdapter implements MultiValueMap { - - 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) { - if (value != null) { - this.headers.add(key, value); - } - } - - @Override - public void addAll(String key, List values) { - this.headers.add(key, values); - } - - @Override - public void addAll(MultiValueMap values) { - values.forEach(this.headers::add); - } - - @Override - public void set(String key, @Nullable String value) { - if (value != null) { - this.headers.set(key, value); - } - } - - @Override - public void setAll(Map values) { - values.forEach(this.headers::set); - } - - @Override - public Map toSingleValueMap() { - Map singleValueMap = CollectionUtils.newLinkedHashMap(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 headerName && this.headers.contains(headerName)); - } - - @Override - public boolean containsValue(Object value) { - return (value instanceof String && - this.headers.entries().stream() - .anyMatch(entry -> value.equals(entry.getValue()))); - } - - @Override - @Nullable - public List get(Object key) { - if (containsKey(key)) { - return this.headers.getAll((String) key); - } - return null; - } - - @Nullable - @Override - public List put(String key, @Nullable List value) { - List previousValues = this.headers.getAll(key); - this.headers.set(key, value); - return previousValues; - } - - @Nullable - @Override - public List remove(Object key) { - if (key instanceof String headerName) { - List previousValues = this.headers.getAll(headerName); - this.headers.remove(headerName); - return previousValues; - } - return null; - } - - @Override - public void putAll(Map> map) { - map.forEach(this.headers::set); - } - - @Override - public void clear() { - this.headers.clear(); - } - - @Override - public Set keySet() { - return new HeaderNames(); - } - - @Override - public Collection> values() { - return this.headers.names().stream() - .map(this.headers::getAll).toList(); - } - - @Override - public Set>> entrySet() { - return new AbstractSet<>() { - @Override - public Iterator>> 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>> { - - private final Iterator names = headers.names().iterator(); - - @Override - public boolean hasNext() { - return this.names.hasNext(); - } - - @Override - public Entry> next() { - return new HeaderEntry(this.names.next()); - } - } - - - private class HeaderEntry implements Entry> { - - private final String key; - - HeaderEntry(String key) { - this.key = key; - } - - @Override - public String getKey() { - return this.key; - } - - @Override - public List getValue() { - return headers.getAll(this.key); - } - - @Override - public List setValue(List value) { - List previousValues = headers.getAll(this.key); - headers.set(this.key, value); - return previousValues; - } - } - - private class HeaderNames extends AbstractSet { - - @Override - public Iterator iterator() { - return new HeaderNamesIterator(headers.names().iterator()); - } - - @Override - public int size() { - return headers.names().size(); - } - } - - private final class HeaderNamesIterator implements Iterator { - - private final Iterator iterator; - - @Nullable - private String currentName; - - private HeaderNamesIterator(Iterator iterator) { - this.iterator = iterator; - } - - @Override - public boolean hasNext() { - return this.iterator.hasNext(); - } - - @Override - public String next() { - this.currentName = this.iterator.next(); - return this.currentName; - } - - @Override - public void remove() { - if (this.currentName == null) { - throw new IllegalStateException("No current Header in iterator"); - } - if (!headers.contains(this.currentName)) { - throw new IllegalStateException("Header not present: " + this.currentName); - } - headers.remove(this.currentName); - } - } - -} diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java index 9784109d03..5ae5b41fbf 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java @@ -38,6 +38,7 @@ import org.springframework.core.io.buffer.Netty5DataBufferFactory; import org.springframework.http.HttpCookie; import org.springframework.http.HttpLogging; import org.springframework.http.HttpMethod; +import org.springframework.http.support.Netty5HeadersAdapter; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java index 46ba38e6fb..ae4f77d61b 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java @@ -37,6 +37,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseCookie; import org.springframework.http.ZeroCopyHttpOutputMessage; +import org.springframework.http.support.Netty5HeadersAdapter; import org.springframework.util.Assert; /** diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java index 9f24e3b1f2..ff056e0cf5 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java @@ -36,6 +36,7 @@ import org.springframework.core.io.buffer.NettyDataBufferFactory; import org.springframework.http.HttpCookie; import org.springframework.http.HttpLogging; import org.springframework.http.HttpMethod; +import org.springframework.http.support.Netty4HeadersAdapter; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; @@ -65,7 +66,7 @@ class ReactorServerHttpRequest extends AbstractServerHttpRequest { throws URISyntaxException { super(HttpMethod.valueOf(request.method().name()), ReactorUriHelper.createUri(request), "", - new NettyHeadersAdapter(request.requestHeaders())); + new Netty4HeadersAdapter(request.requestHeaders())); Assert.notNull(bufferFactory, "DataBufferFactory must not be null"); this.request = request; this.bufferFactory = bufferFactory; diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java index 125db60e6d..c3e5f1d84f 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java @@ -38,6 +38,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseCookie; import org.springframework.http.ZeroCopyHttpOutputMessage; +import org.springframework.http.support.Netty4HeadersAdapter; /** * Adapt {@link ServerHttpResponse} to the {@link HttpServerResponse}. @@ -55,7 +56,7 @@ class ReactorServerHttpResponse extends AbstractServerHttpResponse implements Ze public ReactorServerHttpResponse(HttpServerResponse response, DataBufferFactory bufferFactory) { - super(bufferFactory, new HttpHeaders(new NettyHeadersAdapter(Objects.requireNonNull(response, + super(bufferFactory, new HttpHeaders(new Netty4HeadersAdapter(Objects.requireNonNull(response, "HttpServerResponse must not be null").responseHeaders()))); this.response = response; } diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsHeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/support/HttpComponentsHeadersAdapter.java similarity index 93% rename from spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsHeadersAdapter.java rename to spring-web/src/main/java/org/springframework/http/support/HttpComponentsHeadersAdapter.java index 261e97e846..9fd80f6816 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsHeadersAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/support/HttpComponentsHeadersAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.http.client.reactive; +package org.springframework.http.support; import java.util.AbstractSet; import java.util.ArrayList; @@ -32,6 +32,7 @@ import org.apache.hc.core5.http.HttpMessage; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.MultiValueMap; @@ -40,14 +41,19 @@ import org.springframework.util.MultiValueMap; * HttpClient headers. * * @author Rossen Stoyanchev - * @since 5.3 + * @since 6.1 */ -class HttpComponentsHeadersAdapter implements MultiValueMap { +public final class HttpComponentsHeadersAdapter implements MultiValueMap { private final HttpMessage message; - HttpComponentsHeadersAdapter(HttpMessage message) { + /** + * Create a new {@code HttpComponentsHeadersAdapter} based on the given + * {@code HttpMessage}. + */ + public HttpComponentsHeadersAdapter(HttpMessage message) { + Assert.notNull(message, "Message must not be null"); this.message = message; } diff --git a/spring-web/src/main/java/org/springframework/http/client/JettyHeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/support/JettyHeadersAdapter.java similarity index 98% rename from spring-web/src/main/java/org/springframework/http/client/JettyHeadersAdapter.java rename to spring-web/src/main/java/org/springframework/http/support/JettyHeadersAdapter.java index f6a06ad9f2..9a7da7170f 100644 --- a/spring-web/src/main/java/org/springframework/http/client/JettyHeadersAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/support/JettyHeadersAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.http.client; +package org.springframework.http.support; import java.util.AbstractSet; import java.util.Collection; @@ -35,12 +35,10 @@ import org.springframework.util.MultiValueMap; /** * {@code MultiValueMap} implementation for wrapping Jetty HTTP headers. * - *

There is a duplicate of this class in the server package! - * * @author Rossen Stoyanchev * @author Juergen Hoeller * @author Sam Brannen - * @since 5.3 + * @since 6.1 */ public final class JettyHeadersAdapter implements MultiValueMap { diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/NettyHeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/support/Netty4HeadersAdapter.java similarity index 91% rename from spring-web/src/main/java/org/springframework/http/client/reactive/NettyHeadersAdapter.java rename to spring-web/src/main/java/org/springframework/http/support/Netty4HeadersAdapter.java index 3f1661153a..97e853d4b7 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/NettyHeadersAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/support/Netty4HeadersAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.http.client.reactive; +package org.springframework.http.support; import java.util.AbstractSet; import java.util.Collection; @@ -26,24 +26,28 @@ import java.util.Set; import io.netty.handler.codec.http.HttpHeaders; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.MultiValueMap; /** - * {@code MultiValueMap} implementation for wrapping Netty HTTP headers. - * - *

There is a duplicate of this class in the server package! + * {@code MultiValueMap} implementation for wrapping Netty 4 HTTP headers. * * @author Rossen Stoyanchev * @author Sam Brannen - * @since 5.3 + * @since 6.1 */ -class NettyHeadersAdapter implements MultiValueMap { +public final class Netty4HeadersAdapter implements MultiValueMap { private final HttpHeaders headers; - NettyHeadersAdapter(HttpHeaders headers) { + /** + * Creates a new {@code Netty4HeadersAdapter} based on the given + * {@code HttpHeaders}. + */ + public Netty4HeadersAdapter(HttpHeaders headers) { + Assert.notNull(headers, "Headers must not be null"); this.headers = headers; } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/Netty5HeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/support/Netty5HeadersAdapter.java similarity index 94% rename from spring-web/src/main/java/org/springframework/http/server/reactive/Netty5HeadersAdapter.java rename to spring-web/src/main/java/org/springframework/http/support/Netty5HeadersAdapter.java index 34dda49e7d..26d7b7fd7c 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/Netty5HeadersAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/support/Netty5HeadersAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.http.server.reactive; +package org.springframework.http.support; import java.util.AbstractSet; import java.util.ArrayList; @@ -29,23 +29,27 @@ import java.util.stream.StreamSupport; import io.netty5.handler.codec.http.headers.HttpHeaders; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.MultiValueMap; /** * {@code MultiValueMap} implementation for wrapping Netty HTTP headers. * - *

This class is based on {@link NettyHeadersAdapter}. - * * @author Violeta Georgieva - * @since 6.0 + * @since 6.1 */ -final class Netty5HeadersAdapter implements MultiValueMap { +public final class Netty5HeadersAdapter implements MultiValueMap { private final HttpHeaders headers; - Netty5HeadersAdapter(HttpHeaders headers) { + /** + * Create a new {@code Netty5HeadersAdapter} based on the given + * {@code HttpHeaders}. + */ + public Netty5HeadersAdapter(HttpHeaders headers) { + Assert.notNull(headers, "Headers must not be null"); this.headers = headers; } diff --git a/spring-web/src/main/java/org/springframework/http/support/package-info.java b/spring-web/src/main/java/org/springframework/http/support/package-info.java new file mode 100644 index 0000000000..1dbbc6c43e --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/support/package-info.java @@ -0,0 +1,10 @@ +/** + * This package provides internal HTTP support classes, + * to be used by higher-level client and server classes. + */ +@NonNullApi +@NonNullFields +package org.springframework.http.support; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/HeadersAdaptersTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/HeadersAdaptersTests.java index 8fdfc3c5ef..1020e244f4 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/HeadersAdaptersTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/HeadersAdaptersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,9 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.http.support.JettyHeadersAdapter; +import org.springframework.http.support.Netty4HeadersAdapter; +import org.springframework.http.support.Netty5HeadersAdapter; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.util.MultiValueMap; @@ -134,7 +137,7 @@ class HeadersAdaptersTests { static Stream headers() { return Stream.of( arguments(named("Map", CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH)))), - arguments(named("Netty", new NettyHeadersAdapter(new DefaultHttpHeaders()))), + arguments(named("Netty", new Netty4HeadersAdapter(new DefaultHttpHeaders()))), arguments(named("Netty", new Netty5HeadersAdapter(io.netty5.handler.codec.http.headers.HttpHeaders.newHeaders()))), arguments(named("Tomcat", new TomcatHeadersAdapter(new MimeHeaders()))), arguments(named("Undertow", new UndertowHeadersAdapter(new HeaderMap()))),