Remove Netty 5 support
Closes gh-34345
This commit is contained in:
@@ -40,7 +40,7 @@ import org.springframework.util.MultiValueMap;
|
||||
/**
|
||||
* Benchmark for implementations of MultiValueMap adapters over native HTTP
|
||||
* headers implementations.
|
||||
* <p>Run JMH with {@code -p implementation=Netty,Netty5,HttpComponents,Jetty}
|
||||
* <p>Run JMH with {@code -p implementation=Netty,HttpComponents,Jetty}
|
||||
* to cover all implementations
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
@@ -83,7 +83,6 @@ public class HeadersAdapterBenchmark {
|
||||
this.headers = switch (this.implementation) {
|
||||
case "Netty" -> new Netty4HeadersAdapter(new DefaultHttpHeaders());
|
||||
case "HttpComponents" -> new HttpComponentsHeadersAdapter(new HttpGet("https://example.com"));
|
||||
case "Netty5" -> new Netty5HeadersAdapter(io.netty5.handler.codec.http.headers.HttpHeaders.newHeaders());
|
||||
case "Jetty" -> new JettyHeadersAdapter(HttpFields.build());
|
||||
// FIXME tomcat/undertow implementations (in another package)
|
||||
// case "Tomcat" -> new TomcatHeadersAdapter(new MimeHeaders());
|
||||
@@ -102,7 +101,6 @@ public class HeadersAdapterBenchmark {
|
||||
this.headers = switch (this.implementation) {
|
||||
case "Netty" -> new HeadersAdaptersBaseline.Netty4(new DefaultHttpHeaders());
|
||||
case "HttpComponents" -> new HeadersAdaptersBaseline.HttpComponents(new HttpGet("https://example.com"));
|
||||
case "Netty5" -> new HeadersAdaptersBaseline.Netty5(io.netty5.handler.codec.http.headers.HttpHeaders.newHeaders());
|
||||
case "Jetty" -> new HeadersAdaptersBaseline.Jetty(HttpFields.build());
|
||||
default -> throw new IllegalArgumentException("Unsupported implementation: " + this.implementation);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -27,7 +27,6 @@ import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpMessage;
|
||||
@@ -780,246 +779,4 @@ class HeadersAdaptersBaseline {
|
||||
|
||||
}
|
||||
|
||||
static final class Netty5 implements MultiValueMap<String, String> {
|
||||
|
||||
private final io.netty5.handler.codec.http.headers.HttpHeaders headers;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code Netty5HeadersAdapter} based on the given
|
||||
* {@code HttpHeaders}.
|
||||
*/
|
||||
public Netty5(io.netty5.handler.codec.http.headers.HttpHeaders headers) {
|
||||
Assert.notNull(headers, "Headers must not be null");
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public @Nullable 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<? extends @Nullable String> values) {
|
||||
this.headers.add(key, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(MultiValueMap<String, @Nullable String> 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<String, @Nullable String> values) {
|
||||
values.forEach(this.headers::set);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> toSingleValueMap() {
|
||||
Map<String, String> 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 String &&
|
||||
StreamSupport.stream(this.headers.spliterator(), false)
|
||||
.anyMatch(entry -> value.equals(entry.getValue())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> get(Object key) {
|
||||
Iterator<CharSequence> iterator = this.headers.valuesIterator((CharSequence) key);
|
||||
if (iterator.hasNext()) {
|
||||
List<String> result = new ArrayList<>();
|
||||
iterator.forEachRemaining(value -> result.add(value.toString()));
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> put(String key, @Nullable List<String> value) {
|
||||
List<String> previousValues = get(key);
|
||||
this.headers.set(key, value);
|
||||
return previousValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> remove(Object key) {
|
||||
if (key instanceof String headerName) {
|
||||
List<String> previousValues = get(headerName);
|
||||
this.headers.remove(headerName);
|
||||
return previousValues;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends List<String>> map) {
|
||||
map.forEach(this.headers::set);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.headers.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return new HeaderNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<List<String>> values() {
|
||||
List<List<String>> result = new ArrayList<>(this.headers.size());
|
||||
forEach((key, value) -> result.add(value));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, List<String>>> entrySet() {
|
||||
return new AbstractSet<>() {
|
||||
@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 final Iterator<CharSequence> 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 CharSequence key;
|
||||
|
||||
HeaderEntry(CharSequence key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return this.key.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getValue() {
|
||||
List<String> values = get(this.key);
|
||||
return (values != null ? values : Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> setValue(List<String> value) {
|
||||
List<String> previousValues = getValue();
|
||||
headers.set(this.key, value);
|
||||
return previousValues;
|
||||
}
|
||||
}
|
||||
|
||||
private class HeaderNames extends AbstractSet<String> {
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new HeaderNamesIterator(headers.names().iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return headers.names().size();
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderNamesIterator implements Iterator<String> {
|
||||
|
||||
private final Iterator<CharSequence> iterator;
|
||||
|
||||
private @Nullable CharSequence currentName;
|
||||
|
||||
private HeaderNamesIterator(Iterator<CharSequence> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,144 +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.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.netty5.util.AttributeKey;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.netty5.NettyOutbound;
|
||||
import reactor.netty5.http.client.HttpClient;
|
||||
import reactor.netty5.http.client.HttpClientRequest;
|
||||
import reactor.netty5.resources.ConnectionProvider;
|
||||
import reactor.netty5.resources.LoopResources;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Reactor Netty 2 (Netty 5) implementation of {@link ClientHttpConnector}.
|
||||
*
|
||||
* <p>This class is based on {@link ReactorClientHttpConnector}.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @since 6.0
|
||||
* @see HttpClient
|
||||
*/
|
||||
public class ReactorNetty2ClientHttpConnector implements ClientHttpConnector {
|
||||
|
||||
/**
|
||||
* Channel attribute key under which {@code WebClient} request attributes are stored as a Map.
|
||||
* @since 6.2
|
||||
*/
|
||||
public static final AttributeKey<Map<String, Object>> ATTRIBUTES_KEY =
|
||||
AttributeKey.valueOf(ReactorNetty2ClientHttpRequest.class.getName() + ".ATTRIBUTES");
|
||||
|
||||
private static final Function<HttpClient, HttpClient> defaultInitializer = client -> client.compress(true);
|
||||
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor. Initializes {@link HttpClient} via:
|
||||
* <pre class="code">
|
||||
* HttpClient.create().compress()
|
||||
* </pre>
|
||||
*/
|
||||
public ReactorNetty2ClientHttpConnector() {
|
||||
this.httpClient = defaultInitializer.apply(HttpClient.create().wiretap(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with externally managed Reactor Netty resources, including
|
||||
* {@link LoopResources} for event loop threads, and {@link ConnectionProvider}
|
||||
* for the connection pool.
|
||||
* <p>This constructor should be used only when you don't want the client
|
||||
* to participate in the Reactor Netty global resources. By default, the
|
||||
* client participates in the Reactor Netty global resources held in
|
||||
* {@link reactor.netty5.http.HttpResources}, which is recommended since
|
||||
* fixed, shared resources are favored for event loop concurrency. However,
|
||||
* consider declaring a {@link ReactorNetty2ResourceFactory} bean with
|
||||
* {@code globalResources=true} in order to ensure the Reactor Netty global
|
||||
* resources are shut down when the Spring ApplicationContext is closed.
|
||||
* @param factory the resource factory to obtain the resources from
|
||||
* @param mapper a mapper for further initialization of the created client
|
||||
* @since 5.1
|
||||
*/
|
||||
public ReactorNetty2ClientHttpConnector(ReactorNetty2ResourceFactory factory, Function<HttpClient, HttpClient> mapper) {
|
||||
ConnectionProvider provider = factory.getConnectionProvider();
|
||||
Assert.notNull(provider, "No ConnectionProvider: is ReactorNetty2ResourceFactory not initialized yet?");
|
||||
this.httpClient = defaultInitializer.andThen(mapper).andThen(applyLoopResources(factory))
|
||||
.apply(HttpClient.create(provider));
|
||||
}
|
||||
|
||||
private static Function<HttpClient, HttpClient> applyLoopResources(ReactorNetty2ResourceFactory factory) {
|
||||
return httpClient -> {
|
||||
LoopResources resources = factory.getLoopResources();
|
||||
Assert.notNull(resources, "No LoopResources: is ReactorNetty2ResourceFactory not initialized yet?");
|
||||
return httpClient.runOn(resources);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor with a pre-configured {@code HttpClient} instance.
|
||||
* @param httpClient the client to use
|
||||
* @since 5.1
|
||||
*/
|
||||
public ReactorNetty2ClientHttpConnector(HttpClient httpClient) {
|
||||
Assert.notNull(httpClient, "HttpClient is required");
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<ClientHttpResponse> connect(HttpMethod method, URI uri,
|
||||
Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
|
||||
|
||||
AtomicReference<ReactorNetty2ClientHttpResponse> responseRef = new AtomicReference<>();
|
||||
|
||||
HttpClient.RequestSender requestSender = this.httpClient
|
||||
.request(io.netty5.handler.codec.http.HttpMethod.valueOf(method.name()));
|
||||
|
||||
requestSender = (uri.isAbsolute() ? requestSender.uri(uri) : requestSender.uri(uri.toString()));
|
||||
|
||||
return requestSender
|
||||
.send((request, outbound) -> requestCallback.apply(adaptRequest(method, uri, request, outbound)))
|
||||
.responseConnection((response, connection) -> {
|
||||
responseRef.set(new ReactorNetty2ClientHttpResponse(response, connection));
|
||||
return Mono.just((ClientHttpResponse) responseRef.get());
|
||||
})
|
||||
.next()
|
||||
.doOnCancel(() -> {
|
||||
ReactorNetty2ClientHttpResponse response = responseRef.get();
|
||||
if (response != null) {
|
||||
response.releaseAfterCancel(method);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ReactorNetty2ClientHttpRequest adaptRequest(HttpMethod method, URI uri, HttpClientRequest request,
|
||||
NettyOutbound nettyOutbound) {
|
||||
|
||||
return new ReactorNetty2ClientHttpRequest(method, uri, request, nettyOutbound);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.net.URI;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import io.netty5.buffer.Buffer;
|
||||
import io.netty5.handler.codec.http.headers.DefaultHttpCookiePair;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.netty5.NettyOutbound;
|
||||
import reactor.netty5.channel.ChannelOperations;
|
||||
import reactor.netty5.http.client.HttpClientRequest;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
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.
|
||||
*
|
||||
* <p>This class is based on {@link ReactorClientHttpRequest}.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @since 6.0
|
||||
* @see reactor.netty5.http.client.HttpClient
|
||||
*/
|
||||
class ReactorNetty2ClientHttpRequest extends AbstractClientHttpRequest implements ZeroCopyHttpOutputMessage {
|
||||
|
||||
private final HttpMethod httpMethod;
|
||||
|
||||
private final URI uri;
|
||||
|
||||
private final HttpClientRequest request;
|
||||
|
||||
private final NettyOutbound outbound;
|
||||
|
||||
private final Netty5DataBufferFactory bufferFactory;
|
||||
|
||||
|
||||
public ReactorNetty2ClientHttpRequest(
|
||||
HttpMethod method, URI uri, HttpClientRequest request, NettyOutbound outbound) {
|
||||
|
||||
this.httpMethod = method;
|
||||
this.uri = uri;
|
||||
this.request = request;
|
||||
this.outbound = outbound;
|
||||
this.bufferFactory = new Netty5DataBufferFactory(outbound.alloc());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public HttpMethod getMethod() {
|
||||
return this.httpMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI() {
|
||||
return this.uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBufferFactory bufferFactory() {
|
||||
return this.bufferFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getNativeRequest() {
|
||||
return (T) this.request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
|
||||
return doCommit(() -> {
|
||||
// Send as Mono if possible as an optimization hint to Reactor Netty
|
||||
if (body instanceof Mono) {
|
||||
Mono<Buffer> bufferMono = Mono.from(body).map(Netty5DataBufferFactory::toBuffer);
|
||||
return this.outbound.send(bufferMono).then();
|
||||
|
||||
}
|
||||
else {
|
||||
Flux<Buffer> bufferFlux = Flux.from(body).map(Netty5DataBufferFactory::toBuffer);
|
||||
return this.outbound.send(bufferFlux).then();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
|
||||
Publisher<Publisher<Buffer>> buffers = Flux.from(body).map(ReactorNetty2ClientHttpRequest::toBuffers);
|
||||
return doCommit(() -> this.outbound.sendGroups(buffers).then());
|
||||
}
|
||||
|
||||
private static Publisher<Buffer> toBuffers(Publisher<? extends DataBuffer> dataBuffers) {
|
||||
return Flux.from(dataBuffers).map(Netty5DataBufferFactory::toBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeWith(Path file, long position, long count) {
|
||||
return doCommit(() -> this.outbound.sendFile(file, position, count).then());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> setComplete() {
|
||||
// NettyOutbound#then() expects a body
|
||||
// Use null as the write action for a more optimal send
|
||||
return doCommit(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyHeaders() {
|
||||
getHeaders().forEach((key, value) -> this.request.requestHeaders().set(key, value));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyCookies() {
|
||||
getCookies().values().forEach(values -> values.forEach(value -> {
|
||||
DefaultHttpCookiePair cookie = new DefaultHttpCookiePair(value.getName(), value.getValue());
|
||||
this.request.addCookie(cookie);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the {@link #getAttributes() request attributes} to the
|
||||
* {@link reactor.netty.channel.ChannelOperations#channel() channel} as a single map
|
||||
* attribute under the key {@link ReactorNetty2ClientHttpConnector#ATTRIBUTES_KEY}.
|
||||
*/
|
||||
@Override
|
||||
protected void applyAttributes() {
|
||||
if (!getAttributes().isEmpty()) {
|
||||
((ChannelOperations<?, ?>) this.request).channel()
|
||||
.attr(ReactorNetty2ClientHttpConnector.ATTRIBUTES_KEY).set(getAttributes());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpHeaders initReadOnlyHeaders() {
|
||||
return HttpHeaders.readOnlyHttpHeaders(new Netty5HeadersAdapter(this.request.requestHeaders()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.Collection;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import io.netty5.handler.codec.http.headers.DefaultHttpSetCookie;
|
||||
import io.netty5.handler.codec.http.headers.HttpSetCookie;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.netty5.ChannelOperationsId;
|
||||
import reactor.netty5.Connection;
|
||||
import reactor.netty5.NettyInbound;
|
||||
import reactor.netty5.http.client.HttpClientResponse;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.Netty5DataBufferFactory;
|
||||
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.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* {@link ClientHttpResponse} implementation for the Reactor Netty 2 (Netty 5) HTTP client.
|
||||
*
|
||||
* <p>This class is based on {@link ReactorClientHttpResponse}.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @since 6.0
|
||||
* @see reactor.netty5.http.client.HttpClient
|
||||
*/
|
||||
class ReactorNetty2ClientHttpResponse implements ClientHttpResponse {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ReactorNetty2ClientHttpResponse.class);
|
||||
|
||||
private final HttpClientResponse response;
|
||||
|
||||
private final HttpHeaders headers;
|
||||
|
||||
private final NettyInbound inbound;
|
||||
|
||||
private final Netty5DataBufferFactory bufferFactory;
|
||||
|
||||
// 0 - not subscribed, 1 - subscribed, 2 - cancelled via connector (before subscribe)
|
||||
private final AtomicInteger state = new AtomicInteger();
|
||||
|
||||
|
||||
/**
|
||||
* Constructor that matches the inputs from
|
||||
* {@link reactor.netty5.http.client.HttpClient.ResponseReceiver#responseConnection(BiFunction)}.
|
||||
* @since 5.2.8
|
||||
*/
|
||||
public ReactorNetty2ClientHttpResponse(HttpClientResponse response, Connection connection) {
|
||||
this.response = response;
|
||||
MultiValueMap<String, String> adapter = new Netty5HeadersAdapter(response.responseHeaders());
|
||||
this.headers = HttpHeaders.readOnlyHttpHeaders(adapter);
|
||||
this.inbound = connection.inbound();
|
||||
this.bufferFactory = new Netty5DataBufferFactory(connection.outbound().alloc());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
String id = null;
|
||||
if (this.response instanceof ChannelOperationsId operationsId) {
|
||||
id = (logger.isDebugEnabled() ? operationsId.asLongText() : operationsId.asShortText());
|
||||
}
|
||||
if (id == null && this.response instanceof Connection connection) {
|
||||
id = connection.channel().id().asShortText();
|
||||
}
|
||||
return (id != null ? id : ObjectUtils.getIdentityHexString(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<DataBuffer> getBody() {
|
||||
return this.inbound.receive()
|
||||
.doOnSubscribe(s -> {
|
||||
if (this.state.compareAndSet(0, 1)) {
|
||||
return;
|
||||
}
|
||||
if (this.state.get() == 2) {
|
||||
throw new IllegalStateException(
|
||||
"The client response body has been released already due to cancellation.");
|
||||
}
|
||||
})
|
||||
.map(buffer -> this.bufferFactory.wrap(buffer.split()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatusCode getStatusCode() {
|
||||
return HttpStatusCode.valueOf(this.response.status().code());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiValueMap<String, ResponseCookie> getCookies() {
|
||||
MultiValueMap<String, ResponseCookie> result = new LinkedMultiValueMap<>();
|
||||
this.response.cookies().values().stream()
|
||||
.flatMap(Collection::stream)
|
||||
.forEach(cookie -> result.add(cookie.name().toString(),
|
||||
ResponseCookie.fromClientResponse(cookie.name().toString(), cookie.value().toString())
|
||||
.domain(toString(cookie.domain()))
|
||||
.path(toString(cookie.path()))
|
||||
.maxAge(toLong(cookie.maxAge()))
|
||||
.secure(cookie.isSecure())
|
||||
.httpOnly(cookie.isHttpOnly())
|
||||
.sameSite(getSameSite(cookie))
|
||||
.build()));
|
||||
return CollectionUtils.unmodifiableMultiValueMap(result);
|
||||
}
|
||||
|
||||
private static @Nullable String toString(@Nullable CharSequence value) {
|
||||
return (value != null ? value.toString() : null);
|
||||
}
|
||||
|
||||
private static long toLong(@Nullable Long value) {
|
||||
return (value != null ? value : -1);
|
||||
}
|
||||
|
||||
private static @Nullable String getSameSite(HttpSetCookie cookie) {
|
||||
if (cookie instanceof DefaultHttpSetCookie defaultCookie && defaultCookie.sameSite() != null) {
|
||||
return defaultCookie.sameSite().name();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link ReactorNetty2ClientHttpConnector} when a cancellation is detected
|
||||
* but the content has not been subscribed to. If the subscription never
|
||||
* materializes then the content will remain not drained. Or it could still
|
||||
* materialize if the cancellation happened very early, or the response
|
||||
* reading was delayed for some reason.
|
||||
*/
|
||||
void releaseAfterCancel(HttpMethod method) {
|
||||
if (mayHaveBody(method) && this.state.compareAndSet(0, 2)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("[" + getId() + "]" + "Releasing body, not yet subscribed.");
|
||||
}
|
||||
this.inbound.receive().doOnNext(buffer -> {}).subscribe(buffer -> {}, ex -> {});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean mayHaveBody(HttpMethod method) {
|
||||
int code = getStatusCode().value();
|
||||
return !((code >= 100 && code < 200) || code == 204 || code == 205 ||
|
||||
method.equals(HttpMethod.HEAD) || getHeaders().getContentLength() == 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ReactorNetty2ClientHttpResponse{" +
|
||||
"request=[" + this.response.method().name() + " " + this.response.uri() + "]," +
|
||||
"status=" + getStatusCode() + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.time.Duration;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import reactor.netty5.http.HttpResources;
|
||||
import reactor.netty5.resources.ConnectionProvider;
|
||||
import reactor.netty5.resources.LoopResources;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.http.client.ReactorResourceFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Factory to manage Reactor Netty resources, i.e. {@link LoopResources} for
|
||||
* event loop threads, and {@link ConnectionProvider} for the connection pool,
|
||||
* within the lifecycle of a Spring {@code ApplicationContext}.
|
||||
*
|
||||
* <p>This factory implements {@link InitializingBean} and {@link DisposableBean}
|
||||
* and is expected typically to be declared as a Spring-managed bean.
|
||||
*
|
||||
* <p>This class is based on {@link ReactorResourceFactory}.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ReactorNetty2ResourceFactory implements InitializingBean, DisposableBean {
|
||||
|
||||
private boolean useGlobalResources = true;
|
||||
|
||||
private @Nullable Consumer<HttpResources> globalResourcesConsumer;
|
||||
|
||||
private Supplier<ConnectionProvider> connectionProviderSupplier = () -> ConnectionProvider.create("webflux", 500);
|
||||
|
||||
private @Nullable ConnectionProvider connectionProvider;
|
||||
|
||||
private Supplier<LoopResources> loopResourcesSupplier = () -> LoopResources.create("webflux-http");
|
||||
|
||||
private @Nullable LoopResources loopResources;
|
||||
|
||||
private boolean manageConnectionProvider = false;
|
||||
|
||||
private boolean manageLoopResources = false;
|
||||
|
||||
private Duration shutdownQuietPeriod = Duration.ofSeconds(LoopResources.DEFAULT_SHUTDOWN_QUIET_PERIOD);
|
||||
|
||||
private Duration shutdownTimeout = Duration.ofSeconds(LoopResources.DEFAULT_SHUTDOWN_TIMEOUT);
|
||||
|
||||
|
||||
/**
|
||||
* Whether to use global Reactor Netty resources via {@link HttpResources}.
|
||||
* <p>Default is "true" in which case this factory initializes and stops the
|
||||
* global Reactor Netty resources within Spring's {@code ApplicationContext}
|
||||
* lifecycle. If set to "false" the factory manages its resources independent
|
||||
* of the global ones.
|
||||
* @param useGlobalResources whether to expose and manage the global resources
|
||||
* @see #addGlobalResourcesConsumer(Consumer)
|
||||
*/
|
||||
public void setUseGlobalResources(boolean useGlobalResources) {
|
||||
this.useGlobalResources = useGlobalResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this factory exposes the global
|
||||
* {@link HttpResources HttpResources} holder.
|
||||
*/
|
||||
public boolean isUseGlobalResources() {
|
||||
return this.useGlobalResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Consumer for configuring the global Reactor Netty resources on
|
||||
* startup. When this option is used, {@link #setUseGlobalResources} is also
|
||||
* enabled.
|
||||
* @param consumer the consumer to apply
|
||||
* @see #setUseGlobalResources(boolean)
|
||||
*/
|
||||
public void addGlobalResourcesConsumer(Consumer<HttpResources> consumer) {
|
||||
this.useGlobalResources = true;
|
||||
this.globalResourcesConsumer = (this.globalResourcesConsumer != null ?
|
||||
this.globalResourcesConsumer.andThen(consumer) : consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this when you don't want to participate in global resources and
|
||||
* you want to customize the creation of the managed {@code ConnectionProvider}.
|
||||
* <p>By default, {@code ConnectionProvider.elastic("http")} is used.
|
||||
* <p>Note that this supplier is ignored if {@link #isUseGlobalResources()}
|
||||
* is {@code true} or once the {@link #setConnectionProvider(ConnectionProvider) ConnectionProvider}
|
||||
* is set.
|
||||
* @param supplier the supplier to use
|
||||
*/
|
||||
public void setConnectionProviderSupplier(Supplier<ConnectionProvider> supplier) {
|
||||
this.connectionProviderSupplier = supplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this when you want to provide an externally managed
|
||||
* {@link ConnectionProvider} instance.
|
||||
* @param connectionProvider the connection provider to use as is
|
||||
*/
|
||||
public void setConnectionProvider(ConnectionProvider connectionProvider) {
|
||||
this.connectionProvider = connectionProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@link ConnectionProvider}.
|
||||
*/
|
||||
public ConnectionProvider getConnectionProvider() {
|
||||
Assert.state(this.connectionProvider != null, "ConnectionProvider not initialized yet");
|
||||
return this.connectionProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this when you don't want to participate in global resources and
|
||||
* you want to customize the creation of the managed {@code LoopResources}.
|
||||
* <p>By default, {@code LoopResources.create("webflux-http")} is used.
|
||||
* <p>Note that this supplier is ignored if {@link #isUseGlobalResources()}
|
||||
* is {@code true} or once the {@link #setLoopResources(LoopResources) LoopResources}
|
||||
* is set.
|
||||
* @param supplier the supplier to use
|
||||
*/
|
||||
public void setLoopResourcesSupplier(Supplier<LoopResources> supplier) {
|
||||
this.loopResourcesSupplier = supplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this option when you want to provide an externally managed
|
||||
* {@link LoopResources} instance.
|
||||
* @param loopResources the loop resources to use as is
|
||||
*/
|
||||
public void setLoopResources(LoopResources loopResources) {
|
||||
this.loopResources = loopResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@link LoopResources}.
|
||||
*/
|
||||
public LoopResources getLoopResources() {
|
||||
Assert.state(this.loopResources != null, "LoopResources not initialized yet");
|
||||
return this.loopResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the amount of time we'll wait before shutting down resources.
|
||||
* If a task is submitted during the {@code shutdownQuietPeriod}, it is guaranteed
|
||||
* to be accepted and the {@code shutdownQuietPeriod} will start over.
|
||||
* <p>By default, this is set to
|
||||
* {@link LoopResources#DEFAULT_SHUTDOWN_QUIET_PERIOD} which is 2 seconds but
|
||||
* can also be overridden with the system property
|
||||
* {@link reactor.netty5.ReactorNetty#SHUTDOWN_QUIET_PERIOD
|
||||
* ReactorNetty.SHUTDOWN_QUIET_PERIOD}.
|
||||
* @see #setShutdownTimeout(Duration)
|
||||
*/
|
||||
public void setShutdownQuietPeriod(Duration shutdownQuietPeriod) {
|
||||
Assert.notNull(shutdownQuietPeriod, "shutdownQuietPeriod should not be null");
|
||||
this.shutdownQuietPeriod = shutdownQuietPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the maximum amount of time to wait until the disposal of the
|
||||
* underlying resources regardless if a task was submitted during the
|
||||
* {@code shutdownQuietPeriod}.
|
||||
* <p>By default, this is set to
|
||||
* {@link LoopResources#DEFAULT_SHUTDOWN_TIMEOUT} which is 15 seconds but
|
||||
* can also be overridden with the system property
|
||||
* {@link reactor.netty5.ReactorNetty#SHUTDOWN_TIMEOUT
|
||||
* ReactorNetty.SHUTDOWN_TIMEOUT}.
|
||||
* @see #setShutdownQuietPeriod(Duration)
|
||||
*/
|
||||
public void setShutdownTimeout(Duration shutdownTimeout) {
|
||||
Assert.notNull(shutdownTimeout, "shutdownTimeout should not be null");
|
||||
this.shutdownTimeout = shutdownTimeout;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (this.useGlobalResources) {
|
||||
Assert.isTrue(this.loopResources == null && this.connectionProvider == null,
|
||||
"'useGlobalResources' is mutually exclusive with explicitly configured resources");
|
||||
HttpResources httpResources = HttpResources.get();
|
||||
if (this.globalResourcesConsumer != null) {
|
||||
this.globalResourcesConsumer.accept(httpResources);
|
||||
}
|
||||
this.connectionProvider = httpResources;
|
||||
this.loopResources = httpResources;
|
||||
}
|
||||
else {
|
||||
if (this.loopResources == null) {
|
||||
this.manageLoopResources = true;
|
||||
this.loopResources = this.loopResourcesSupplier.get();
|
||||
}
|
||||
if (this.connectionProvider == null) {
|
||||
this.manageConnectionProvider = true;
|
||||
this.connectionProvider = this.connectionProviderSupplier.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (this.useGlobalResources) {
|
||||
HttpResources.disposeLoopsAndConnectionsLater(this.shutdownQuietPeriod, this.shutdownTimeout).block();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
ConnectionProvider provider = this.connectionProvider;
|
||||
if (provider != null && this.manageConnectionProvider) {
|
||||
provider.disposeLater().block();
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
LoopResources resources = this.loopResources;
|
||||
if (resources != null && this.manageLoopResources) {
|
||||
resources.disposeLater(this.shutdownQuietPeriod, this.shutdownTimeout).block();
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -35,8 +35,6 @@ import org.springframework.core.codec.DataBufferDecoder;
|
||||
import org.springframework.core.codec.DataBufferEncoder;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.codec.Netty5BufferDecoder;
|
||||
import org.springframework.core.codec.Netty5BufferEncoder;
|
||||
import org.springframework.core.codec.NettyByteBufDecoder;
|
||||
import org.springframework.core.codec.NettyByteBufEncoder;
|
||||
import org.springframework.core.codec.ResourceDecoder;
|
||||
@@ -98,8 +96,6 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
|
||||
|
||||
static final boolean nettyByteBufPresent;
|
||||
|
||||
static final boolean netty5BufferPresent;
|
||||
|
||||
static final boolean kotlinSerializationCborPresent;
|
||||
|
||||
static final boolean kotlinSerializationJsonPresent;
|
||||
@@ -115,7 +111,6 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
|
||||
protobufPresent = ClassUtils.isPresent("com.google.protobuf.Message", classLoader);
|
||||
synchronossMultipartPresent = ClassUtils.isPresent("org.synchronoss.cloud.nio.multipart.NioMultipartParser", classLoader);
|
||||
nettyByteBufPresent = ClassUtils.isPresent("io.netty.buffer.ByteBuf", classLoader);
|
||||
netty5BufferPresent = ClassUtils.isPresent("io.netty5.buffer.Buffer", classLoader);
|
||||
kotlinSerializationCborPresent = ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", classLoader);
|
||||
kotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader);
|
||||
kotlinSerializationProtobufPresent = ClassUtils.isPresent("kotlinx.serialization.protobuf.ProtoBuf", classLoader);
|
||||
@@ -410,9 +405,6 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
|
||||
if (nettyByteBufPresent) {
|
||||
addCodec(this.typedReaders, new DecoderHttpMessageReader<>(new NettyByteBufDecoder()));
|
||||
}
|
||||
if (netty5BufferPresent) {
|
||||
addCodec(this.typedReaders, new DecoderHttpMessageReader<>(new Netty5BufferDecoder()));
|
||||
}
|
||||
addCodec(this.typedReaders, new ResourceHttpMessageReader(new ResourceDecoder()));
|
||||
addCodec(this.typedReaders, new DecoderHttpMessageReader<>(StringDecoder.textPlainOnly()));
|
||||
if (protobufPresent) {
|
||||
@@ -660,9 +652,6 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
|
||||
if (nettyByteBufPresent) {
|
||||
addCodec(writers, new EncoderHttpMessageWriter<>(new NettyByteBufEncoder()));
|
||||
}
|
||||
if (netty5BufferPresent) {
|
||||
addCodec(writers, new EncoderHttpMessageWriter<>(new Netty5BufferEncoder()));
|
||||
}
|
||||
addCodec(writers, new ResourceHttpMessageWriter());
|
||||
addCodec(writers, new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly()));
|
||||
if (protobufPresent) {
|
||||
|
||||
@@ -1,79 +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.net.URISyntaxException;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import io.netty5.handler.codec.http.HttpResponseStatus;
|
||||
import org.apache.commons.logging.Log;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.netty5.http.server.HttpServerRequest;
|
||||
import reactor.netty5.http.server.HttpServerResponse;
|
||||
|
||||
import org.springframework.core.io.buffer.Netty5DataBufferFactory;
|
||||
import org.springframework.http.HttpLogging;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Adapt {@link HttpHandler} to the Reactor Netty 5 channel handling function.
|
||||
*
|
||||
* <p>This class is based on {@link ReactorHttpHandlerAdapter}.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ReactorNetty2HttpHandlerAdapter implements BiFunction<HttpServerRequest, HttpServerResponse, Mono<Void>> {
|
||||
|
||||
private static final Log logger = HttpLogging.forLogName(ReactorNetty2HttpHandlerAdapter.class);
|
||||
|
||||
|
||||
private final HttpHandler httpHandler;
|
||||
|
||||
|
||||
public ReactorNetty2HttpHandlerAdapter(HttpHandler httpHandler) {
|
||||
Assert.notNull(httpHandler, "HttpHandler must not be null");
|
||||
this.httpHandler = httpHandler;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Void> apply(HttpServerRequest reactorRequest, HttpServerResponse reactorResponse) {
|
||||
Netty5DataBufferFactory bufferFactory = new Netty5DataBufferFactory(reactorResponse.alloc());
|
||||
try {
|
||||
ReactorNetty2ServerHttpRequest request = new ReactorNetty2ServerHttpRequest(reactorRequest, bufferFactory);
|
||||
ServerHttpResponse response = new ReactorNetty2ServerHttpResponse(reactorResponse, bufferFactory);
|
||||
|
||||
if (request.getMethod() == HttpMethod.HEAD) {
|
||||
response = new HttpHeadResponseDecorator(response);
|
||||
}
|
||||
|
||||
return this.httpHandler.handle(request, response)
|
||||
.doOnError(ex -> logger.trace(request.getLogPrefix() + "Failed to complete: " + ex.getMessage()))
|
||||
.doOnSuccess(aVoid -> logger.trace(request.getLogPrefix() + "Handling completed"));
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to get request URI: " + ex.getMessage());
|
||||
}
|
||||
reactorResponse.status(HttpResponseStatus.BAD_REQUEST);
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,219 +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.server.reactive;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
import io.netty5.channel.Channel;
|
||||
import io.netty5.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty5.handler.codec.http.headers.HttpCookiePair;
|
||||
import io.netty5.handler.ssl.SslHandler;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.netty5.ChannelOperationsId;
|
||||
import reactor.netty5.Connection;
|
||||
import reactor.netty5.http.server.HttpServerRequest;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.Netty5DataBufferFactory;
|
||||
import org.springframework.http.HttpCookie;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpLogging;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.support.Netty5HeadersAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Adapt {@link ServerHttpRequest} to the Reactor {@link HttpServerRequest}.
|
||||
*
|
||||
* <p>This class is based on {@link ReactorServerHttpRequest}.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @author Sebastien Deleuze
|
||||
* @since 6.0
|
||||
*/
|
||||
class ReactorNetty2ServerHttpRequest extends AbstractServerHttpRequest {
|
||||
|
||||
private static final Log logger = HttpLogging.forLogName(ReactorNetty2ServerHttpRequest.class);
|
||||
|
||||
|
||||
private static final AtomicLong logPrefixIndex = new AtomicLong();
|
||||
|
||||
|
||||
private final HttpServerRequest request;
|
||||
|
||||
private final Netty5DataBufferFactory bufferFactory;
|
||||
|
||||
|
||||
public ReactorNetty2ServerHttpRequest(HttpServerRequest request, Netty5DataBufferFactory bufferFactory)
|
||||
throws URISyntaxException {
|
||||
|
||||
super(HttpMethod.valueOf(request.method().name()), initUri(request), "",
|
||||
new HttpHeaders(new Netty5HeadersAdapter(request.requestHeaders())));
|
||||
Assert.notNull(bufferFactory, "DataBufferFactory must not be null");
|
||||
this.request = request;
|
||||
this.bufferFactory = bufferFactory;
|
||||
}
|
||||
|
||||
private static URI initUri(HttpServerRequest request) throws URISyntaxException {
|
||||
Assert.notNull(request, "HttpServerRequest must not be null");
|
||||
return new URI(resolveBaseUrl(request) + resolveRequestUri(request));
|
||||
}
|
||||
|
||||
private static URI resolveBaseUrl(HttpServerRequest request) throws URISyntaxException {
|
||||
String scheme = getScheme(request);
|
||||
|
||||
InetSocketAddress hostAddress = request.hostAddress();
|
||||
if (hostAddress != null) {
|
||||
return new URI(scheme, null, hostAddress.getHostString(), hostAddress.getPort(), null, null, null);
|
||||
}
|
||||
|
||||
CharSequence charSequence = request.requestHeaders().get(HttpHeaderNames.HOST);
|
||||
if (charSequence != null) {
|
||||
String header = charSequence.toString();
|
||||
final int portIndex;
|
||||
if (header.startsWith("[")) {
|
||||
portIndex = header.indexOf(':', header.indexOf(']'));
|
||||
}
|
||||
else {
|
||||
portIndex = header.indexOf(':');
|
||||
}
|
||||
if (portIndex != -1) {
|
||||
try {
|
||||
return new URI(scheme, null, header.substring(0, portIndex),
|
||||
Integer.parseInt(header, portIndex + 1, header.length(), 10), null, null, null);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new URISyntaxException(header, "Unable to parse port", portIndex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new URI(scheme, header, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Neither local hostAddress nor HOST header available");
|
||||
}
|
||||
|
||||
private static String getScheme(HttpServerRequest request) {
|
||||
return request.scheme();
|
||||
}
|
||||
|
||||
private static String resolveRequestUri(HttpServerRequest request) {
|
||||
String uri = request.uri();
|
||||
for (int i = 0; i < uri.length(); i++) {
|
||||
char c = uri.charAt(i);
|
||||
if (c == '/' || c == '?' || c == '#') {
|
||||
break;
|
||||
}
|
||||
if (c == ':' && (i + 2 < uri.length())) {
|
||||
if (uri.charAt(i + 1) == '/' && uri.charAt(i + 2) == '/') {
|
||||
for (int j = i + 3; j < uri.length(); j++) {
|
||||
c = uri.charAt(j);
|
||||
if (c == '/' || c == '?' || c == '#') {
|
||||
return uri.substring(j);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MultiValueMap<String, HttpCookie> initCookies() {
|
||||
MultiValueMap<String, HttpCookie> cookies = new LinkedMultiValueMap<>();
|
||||
for (CharSequence name : this.request.allCookies().keySet()) {
|
||||
for (HttpCookiePair cookie : this.request.allCookies().get(name)) {
|
||||
CharSequence cookieValue = cookie.value();
|
||||
HttpCookie httpCookie = new HttpCookie(name.toString(), cookieValue != null ? cookieValue.toString() : null);
|
||||
cookies.add(name.toString(), httpCookie);
|
||||
}
|
||||
}
|
||||
return cookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InetSocketAddress getLocalAddress() {
|
||||
return this.request.hostAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InetSocketAddress getRemoteAddress() {
|
||||
return this.request.remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable SslInfo initSslInfo() {
|
||||
Channel channel = ((Connection) this.request).channel();
|
||||
SslHandler sslHandler = channel.pipeline().get(SslHandler.class);
|
||||
if (sslHandler == null && channel.parent() != null) { // HTTP/2
|
||||
sslHandler = channel.parent().pipeline().get(SslHandler.class);
|
||||
}
|
||||
if (sslHandler != null) {
|
||||
SSLSession session = sslHandler.engine().getSession();
|
||||
return new DefaultSslInfo(session);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<DataBuffer> getBody() {
|
||||
return this.request.receive().transferOwnership().map(this.bufferFactory::wrap);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T getNativeRequest() {
|
||||
return (T) this.request;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable String initId() {
|
||||
if (this.request instanceof Connection connection) {
|
||||
return connection.channel().id().asShortText() +
|
||||
"-" + logPrefixIndex.incrementAndGet();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String initLogPrefix() {
|
||||
String id = null;
|
||||
if (this.request instanceof ChannelOperationsId operationsId) {
|
||||
id = (logger.isDebugEnabled() ? operationsId.asLongText() : operationsId.asShortText());
|
||||
}
|
||||
if (id != null) {
|
||||
return id;
|
||||
}
|
||||
if (this.request instanceof Connection connection) {
|
||||
return connection.channel().id().asShortText() +
|
||||
"-" + logPrefixIndex.incrementAndGet();
|
||||
}
|
||||
return getId();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.nio.file.Path;
|
||||
|
||||
import io.netty5.buffer.Buffer;
|
||||
import io.netty5.channel.ChannelId;
|
||||
import io.netty5.handler.codec.http.headers.DefaultHttpSetCookie;
|
||||
import io.netty5.handler.codec.http.headers.HttpSetCookie;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.netty5.ChannelOperationsId;
|
||||
import reactor.netty5.http.server.HttpServerResponse;
|
||||
|
||||
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.Netty5DataBufferFactory;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Adapt {@link ServerHttpResponse} to the {@link HttpServerResponse}.
|
||||
*
|
||||
* <p>This class is based on {@link ReactorServerHttpResponse}.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @since 6.0
|
||||
*/
|
||||
class ReactorNetty2ServerHttpResponse extends AbstractServerHttpResponse implements ZeroCopyHttpOutputMessage {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ReactorNetty2ServerHttpResponse.class);
|
||||
|
||||
|
||||
private final HttpServerResponse response;
|
||||
|
||||
|
||||
public ReactorNetty2ServerHttpResponse(HttpServerResponse response, DataBufferFactory bufferFactory) {
|
||||
super(bufferFactory, new HttpHeaders(new Netty5HeadersAdapter(response.responseHeaders())));
|
||||
Assert.notNull(response, "HttpServerResponse must not be null");
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T getNativeResponse() {
|
||||
return (T) this.response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatusCode getStatusCode() {
|
||||
HttpStatusCode status = super.getStatusCode();
|
||||
return (status != null ? status : HttpStatusCode.valueOf(this.response.status().code()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyStatusCode() {
|
||||
HttpStatusCode status = super.getStatusCode();
|
||||
if (status != null) {
|
||||
this.response.status(status.value());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Void> writeWithInternal(Publisher<? extends DataBuffer> publisher) {
|
||||
return this.response.send(toByteBufs(publisher)).then();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Void> writeAndFlushWithInternal(Publisher<? extends Publisher<? extends DataBuffer>> publisher) {
|
||||
return this.response.sendGroups(Flux.from(publisher).map(this::toByteBufs)).then();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyHeaders() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyCookies() {
|
||||
for (String name : getCookies().keySet()) {
|
||||
for (ResponseCookie httpCookie : getCookies().get(name)) {
|
||||
Long maxAge = (!httpCookie.getMaxAge().isNegative()) ? httpCookie.getMaxAge().getSeconds() : null;
|
||||
HttpSetCookie.SameSite sameSite = (httpCookie.getSameSite() != null) ? HttpSetCookie.SameSite.valueOf(httpCookie.getSameSite()) : null;
|
||||
// TODO: support Partitioned attribute when available in Netty 5 API
|
||||
DefaultHttpSetCookie cookie = new DefaultHttpSetCookie(name, httpCookie.getValue(), httpCookie.getPath(),
|
||||
httpCookie.getDomain(), null, maxAge, sameSite, false, httpCookie.isSecure(), httpCookie.isHttpOnly());
|
||||
this.response.addCookie(cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeWith(Path file, long position, long count) {
|
||||
return doCommit(() -> this.response.sendFile(file, position, count).then());
|
||||
}
|
||||
|
||||
private Publisher<Buffer> toByteBufs(Publisher<? extends DataBuffer> dataBuffers) {
|
||||
return dataBuffers instanceof Mono ?
|
||||
Mono.from(dataBuffers).map(Netty5DataBufferFactory::toBuffer) :
|
||||
Flux.from(dataBuffers).map(Netty5DataBufferFactory::toBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void touchDataBuffer(DataBuffer buffer) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (this.response instanceof ChannelOperationsId operationsId) {
|
||||
DataBufferUtils.touch(buffer, "Channel id: " + operationsId.asLongText());
|
||||
}
|
||||
else {
|
||||
this.response.withConnection(connection -> {
|
||||
ChannelId id = connection.channel().id();
|
||||
DataBufferUtils.touch(buffer, "Channel id: " + id.asShortText());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.support;
|
||||
|
||||
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.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import io.netty5.handler.codec.http.headers.HttpHeaders;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* {@code MultiValueMap} implementation for wrapping Netty HTTP headers.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @author Simon Baslé
|
||||
* @since 6.1
|
||||
*/
|
||||
public final class Netty5HeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
private final 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;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public @Nullable 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<? 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) {
|
||||
if (value != null) {
|
||||
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 LinkedCaseInsensitiveMap<>(
|
||||
this.headers.size(), Locale.ROOT);
|
||||
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 String &&
|
||||
StreamSupport.stream(this.headers.spliterator(), false)
|
||||
.anyMatch(entry -> value.equals(entry.getValue())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> get(Object key) {
|
||||
Iterator<CharSequence> iterator = this.headers.valuesIterator((CharSequence) key);
|
||||
if (iterator.hasNext()) {
|
||||
List<String> result = new ArrayList<>();
|
||||
iterator.forEachRemaining(value -> result.add(value.toString()));
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> put(String key, @Nullable List<String> value) {
|
||||
List<String> previousValues = get(key);
|
||||
this.headers.set(key, value);
|
||||
return previousValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> remove(Object key) {
|
||||
if (key instanceof String headerName) {
|
||||
List<String> previousValues = get(headerName);
|
||||
this.headers.remove(headerName);
|
||||
return previousValues;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends List<String>> map) {
|
||||
map.forEach(this.headers::set);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.headers.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return new HeaderNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<List<String>> values() {
|
||||
List<List<String>> result = new ArrayList<>(this.headers.size());
|
||||
forEach((key, value) -> result.add(value));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, List<String>>> entrySet() {
|
||||
return new AbstractSet<>() {
|
||||
@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 final Iterator<CharSequence> 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 CharSequence key;
|
||||
|
||||
HeaderEntry(CharSequence key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return this.key.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getValue() {
|
||||
List<String> values = get(this.key);
|
||||
return (values != null ? values : Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> setValue(List<String> value) {
|
||||
List<String> previousValues = getValue();
|
||||
headers.set(this.key, value);
|
||||
return previousValues;
|
||||
}
|
||||
}
|
||||
|
||||
private class HeaderNames extends AbstractSet<String> {
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new HeaderNamesIterator(headers.names().iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return headers.names().size();
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderNamesIterator implements Iterator<String> {
|
||||
|
||||
private final Iterator<CharSequence> iterator;
|
||||
|
||||
private @Nullable CharSequence currentName;
|
||||
|
||||
private HeaderNamesIterator(Iterator<CharSequence> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -37,8 +37,6 @@ import org.springframework.core.codec.DataBufferDecoder;
|
||||
import org.springframework.core.codec.DataBufferEncoder;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.codec.Netty5BufferDecoder;
|
||||
import org.springframework.core.codec.Netty5BufferEncoder;
|
||||
import org.springframework.core.codec.NettyByteBufDecoder;
|
||||
import org.springframework.core.codec.NettyByteBufEncoder;
|
||||
import org.springframework.core.codec.ResourceDecoder;
|
||||
@@ -96,12 +94,11 @@ class ClientCodecConfigurerTests {
|
||||
@Test
|
||||
void defaultReaders() {
|
||||
List<HttpMessageReader<?>> readers = this.configurer.getReaders();
|
||||
assertThat(readers).hasSize(20);
|
||||
assertThat(readers).hasSize(19);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteArrayDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteBufferDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(DataBufferDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(NettyByteBufDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Netty5BufferDecoder.class);
|
||||
assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(ResourceHttpMessageReader.class);
|
||||
assertStringDecoder(getNextDecoder(readers), true);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ProtobufDecoder.class);
|
||||
@@ -123,12 +120,11 @@ class ClientCodecConfigurerTests {
|
||||
@Test
|
||||
void defaultWriters() {
|
||||
List<HttpMessageWriter<?>> writers = this.configurer.getWriters();
|
||||
assertThat(writers).hasSize(18);
|
||||
assertThat(writers).hasSize(17);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteArrayEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteBufferEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(DataBufferEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(NettyByteBufEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Netty5BufferEncoder.class);
|
||||
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ResourceHttpMessageWriter.class);
|
||||
assertStringEncoder(getNextEncoder(writers), true);
|
||||
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ProtobufHttpMessageWriter.class);
|
||||
@@ -192,12 +188,11 @@ class ClientCodecConfigurerTests {
|
||||
int size = 99;
|
||||
this.configurer.defaultCodecs().maxInMemorySize(size);
|
||||
List<HttpMessageReader<?>> readers = this.configurer.getReaders();
|
||||
assertThat(readers).hasSize(20);
|
||||
assertThat(readers).hasSize(19);
|
||||
assertThat(((ByteArrayDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
|
||||
assertThat(((ByteBufferDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
|
||||
assertThat(((DataBufferDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
|
||||
assertThat(((NettyByteBufDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
|
||||
assertThat(((Netty5BufferDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
|
||||
assertThat(((ResourceDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
|
||||
assertThat(((StringDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
|
||||
assertThat(((ProtobufDecoder) getNextDecoder(readers)).getMaxMessageSize()).isEqualTo(size);
|
||||
@@ -255,7 +250,7 @@ class ClientCodecConfigurerTests {
|
||||
writers = findCodec(this.configurer.getWriters(), MultipartHttpMessageWriter.class).getPartWriters();
|
||||
|
||||
assertThat(sseDecoder).isNotSameAs(jackson2Decoder);
|
||||
assertThat(writers).hasSize(18);
|
||||
assertThat(writers).hasSize(17);
|
||||
}
|
||||
|
||||
@Test // gh-24194
|
||||
@@ -265,7 +260,7 @@ class ClientCodecConfigurerTests {
|
||||
List<HttpMessageWriter<?>> writers =
|
||||
findCodec(clone.getWriters(), MultipartHttpMessageWriter.class).getPartWriters();
|
||||
|
||||
assertThat(writers).hasSize(18);
|
||||
assertThat(writers).hasSize(17);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -279,7 +274,7 @@ class ClientCodecConfigurerTests {
|
||||
List<HttpMessageWriter<?>> writers =
|
||||
findCodec(clone.getWriters(), MultipartHttpMessageWriter.class).getPartWriters();
|
||||
|
||||
assertThat(writers).hasSize(18);
|
||||
assertThat(writers).hasSize(17);
|
||||
}
|
||||
|
||||
private Decoder<?> getNextDecoder(List<HttpMessageReader<?>> readers) {
|
||||
|
||||
@@ -33,8 +33,6 @@ import org.springframework.core.codec.DataBufferDecoder;
|
||||
import org.springframework.core.codec.DataBufferEncoder;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.codec.Netty5BufferDecoder;
|
||||
import org.springframework.core.codec.Netty5BufferEncoder;
|
||||
import org.springframework.core.codec.NettyByteBufDecoder;
|
||||
import org.springframework.core.codec.NettyByteBufEncoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
@@ -92,12 +90,11 @@ class CodecConfigurerTests {
|
||||
@Test
|
||||
void defaultReaders() {
|
||||
List<HttpMessageReader<?>> readers = this.configurer.getReaders();
|
||||
assertThat(readers).hasSize(19);
|
||||
assertThat(readers).hasSize(18);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteArrayDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteBufferDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(DataBufferDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(NettyByteBufDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Netty5BufferDecoder.class);
|
||||
assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(ResourceHttpMessageReader.class);
|
||||
assertStringDecoder(getNextDecoder(readers), true);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ProtobufDecoder.class);
|
||||
@@ -117,12 +114,11 @@ class CodecConfigurerTests {
|
||||
@Test
|
||||
void defaultWriters() {
|
||||
List<HttpMessageWriter<?>> writers = this.configurer.getWriters();
|
||||
assertThat(writers).hasSize(18);
|
||||
assertThat(writers).hasSize(17);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteArrayEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteBufferEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(DataBufferEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(NettyByteBufEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Netty5BufferEncoder.class);
|
||||
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ResourceHttpMessageWriter.class);
|
||||
assertStringEncoder(getNextEncoder(writers), true);
|
||||
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ProtobufHttpMessageWriter.class);
|
||||
@@ -160,14 +156,13 @@ class CodecConfigurerTests {
|
||||
|
||||
List<HttpMessageReader<?>> readers = this.configurer.getReaders();
|
||||
|
||||
assertThat(readers).hasSize(23);
|
||||
assertThat(readers).hasSize(22);
|
||||
assertThat(getNextDecoder(readers)).isSameAs(customDecoder1);
|
||||
assertThat(readers.get(this.index.getAndIncrement())).isSameAs(customReader1);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteArrayDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteBufferDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(DataBufferDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(NettyByteBufDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Netty5BufferDecoder.class);
|
||||
assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(ResourceHttpMessageReader.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(StringDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ProtobufDecoder.class);
|
||||
@@ -208,14 +203,13 @@ class CodecConfigurerTests {
|
||||
|
||||
List<HttpMessageWriter<?>> writers = this.configurer.getWriters();
|
||||
|
||||
assertThat(writers).hasSize(22);
|
||||
assertThat(writers).hasSize(21);
|
||||
assertThat(getNextEncoder(writers)).isSameAs(customEncoder1);
|
||||
assertThat(writers.get(this.index.getAndIncrement())).isSameAs(customWriter1);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteArrayEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteBufferEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(DataBufferEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(NettyByteBufEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Netty5BufferEncoder.class);
|
||||
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ResourceHttpMessageWriter.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(CharSequenceEncoder.class);
|
||||
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ProtobufHttpMessageWriter.class);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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,8 +36,6 @@ import org.springframework.core.codec.DataBufferDecoder;
|
||||
import org.springframework.core.codec.DataBufferEncoder;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.codec.Netty5BufferDecoder;
|
||||
import org.springframework.core.codec.Netty5BufferEncoder;
|
||||
import org.springframework.core.codec.NettyByteBufDecoder;
|
||||
import org.springframework.core.codec.NettyByteBufEncoder;
|
||||
import org.springframework.core.codec.ResourceDecoder;
|
||||
@@ -94,12 +92,11 @@ class ServerCodecConfigurerTests {
|
||||
@Test
|
||||
void defaultReaders() {
|
||||
List<HttpMessageReader<?>> readers = this.configurer.getReaders();
|
||||
assertThat(readers).hasSize(19);
|
||||
assertThat(readers).hasSize(18);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteArrayDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteBufferDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(DataBufferDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(NettyByteBufDecoder.class);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Netty5BufferDecoder.class);
|
||||
assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(ResourceHttpMessageReader.class);
|
||||
assertStringDecoder(getNextDecoder(readers), true);
|
||||
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ProtobufDecoder.class);
|
||||
@@ -119,12 +116,11 @@ class ServerCodecConfigurerTests {
|
||||
@Test
|
||||
void defaultWriters() {
|
||||
List<HttpMessageWriter<?>> writers = this.configurer.getWriters();
|
||||
assertThat(writers).hasSize(19);
|
||||
assertThat(writers).hasSize(18);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteArrayEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteBufferEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(DataBufferEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(NettyByteBufEncoder.class);
|
||||
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Netty5BufferEncoder.class);
|
||||
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ResourceHttpMessageWriter.class);
|
||||
assertStringEncoder(getNextEncoder(writers), true);
|
||||
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ProtobufHttpMessageWriter.class);
|
||||
@@ -168,7 +164,6 @@ class ServerCodecConfigurerTests {
|
||||
assertThat(((ByteBufferDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
|
||||
assertThat(((DataBufferDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
|
||||
assertThat(((NettyByteBufDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
|
||||
assertThat(((Netty5BufferDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
|
||||
assertThat(((ResourceDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
|
||||
assertThat(((StringDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
|
||||
assertThat(((ProtobufDecoder) getNextDecoder(readers)).getMaxMessageSize()).isEqualTo(size);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -32,7 +32,6 @@ import org.mockito.BDDMockito;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
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;
|
||||
@@ -102,7 +101,6 @@ class DefaultServerHttpRequestBuilderTests {
|
||||
return Stream.of(
|
||||
initHeader("Map", CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH))),
|
||||
initHeader("Netty", new Netty4HeadersAdapter(new DefaultHttpHeaders())),
|
||||
initHeader("Netty5", new Netty5HeadersAdapter(io.netty5.handler.codec.http.headers.HttpHeaders.newHeaders())),
|
||||
initHeader("Tomcat", new TomcatHeadersAdapter(new MimeHeaders())),
|
||||
initHeader("Undertow", new UndertowHeadersAdapter(new HeaderMap())),
|
||||
initHeader("Jetty", new JettyHeadersAdapter(HttpFields.build())),
|
||||
|
||||
@@ -42,7 +42,6 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.support.HttpComponentsHeadersAdapter;
|
||||
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;
|
||||
@@ -273,7 +272,6 @@ class HeadersAdaptersTests {
|
||||
return Stream.of(
|
||||
argumentSet("Map", CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH))),
|
||||
argumentSet("Netty", new Netty4HeadersAdapter(new DefaultHttpHeaders())),
|
||||
argumentSet("Netty5", new Netty5HeadersAdapter(io.netty5.handler.codec.http.headers.HttpHeaders.newHeaders())),
|
||||
argumentSet("Tomcat", new TomcatHeadersAdapter(new MimeHeaders())),
|
||||
argumentSet("Undertow", new UndertowHeadersAdapter(new HeaderMap())),
|
||||
argumentSet("Jetty", new JettyHeadersAdapter(HttpFields.build())),
|
||||
@@ -291,8 +289,6 @@ class HeadersAdaptersTests {
|
||||
static Stream<Arguments> nativeHeadersWithCasedEntries() {
|
||||
return Stream.of(
|
||||
argumentSet("Netty", new Netty4HeadersAdapter(withHeaders(new DefaultHttpHeaders(), h -> h::add))),
|
||||
argumentSet("Netty5", new Netty5HeadersAdapter(withHeaders(io.netty5.handler.codec.http.headers.HttpHeaders.newHeaders(),
|
||||
h -> h::add))),
|
||||
argumentSet("Tomcat", new TomcatHeadersAdapter(withHeaders(new MimeHeaders(),
|
||||
h -> (k, v) -> h.addValue(k).setString(v)))),
|
||||
argumentSet("Undertow", new UndertowHeadersAdapter(withHeaders(new HeaderMap(),
|
||||
|
||||
@@ -1,71 +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.web.testfixture.http.server.reactive.bootstrap;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import reactor.netty5.DisposableServer;
|
||||
|
||||
import org.springframework.http.server.reactive.ReactorNetty2HttpHandlerAdapter;
|
||||
|
||||
/**
|
||||
* This class is copied from {@link ReactorHttpServer}.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ReactorNetty2HttpServer extends AbstractHttpServer {
|
||||
|
||||
private ReactorNetty2HttpHandlerAdapter reactorHandler;
|
||||
|
||||
private reactor.netty5.http.server.HttpServer reactorServer;
|
||||
|
||||
private AtomicReference<DisposableServer> serverRef = new AtomicReference<>();
|
||||
|
||||
|
||||
@Override
|
||||
protected void initServer() {
|
||||
this.reactorHandler = createHttpHandlerAdapter();
|
||||
this.reactorServer = reactor.netty5.http.server.HttpServer.create().wiretap(true)
|
||||
.host(getHost()).port(getPort());
|
||||
}
|
||||
|
||||
private ReactorNetty2HttpHandlerAdapter createHttpHandlerAdapter() {
|
||||
return new ReactorNetty2HttpHandlerAdapter(resolveHttpHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startInternal() {
|
||||
DisposableServer server = this.reactorServer.handle(this.reactorHandler).bind().block();
|
||||
setPort(((InetSocketAddress) server.address()).getPort());
|
||||
this.serverRef.set(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopInternal() {
|
||||
this.serverRef.get().dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetInternal() {
|
||||
this.reactorServer = null;
|
||||
this.reactorHandler = null;
|
||||
this.serverRef.set(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +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.web.testfixture.http.server.reactive.bootstrap;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||
import reactor.netty5.DisposableServer;
|
||||
import reactor.netty5.http.Http11SslContextSpec;
|
||||
|
||||
import org.springframework.http.server.reactive.ReactorNetty2HttpHandlerAdapter;
|
||||
|
||||
/**
|
||||
* This class is copied from {@link ReactorHttpsServer}.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ReactorNetty2HttpsServer extends AbstractHttpServer {
|
||||
|
||||
private ReactorNetty2HttpHandlerAdapter reactorHandler;
|
||||
|
||||
private reactor.netty5.http.server.HttpServer reactorServer;
|
||||
|
||||
private AtomicReference<DisposableServer> serverRef = new AtomicReference<>();
|
||||
|
||||
|
||||
@Override
|
||||
protected void initServer() throws Exception {
|
||||
SelfSignedCertificate cert = new SelfSignedCertificate();
|
||||
Http11SslContextSpec http11SslContextSpec = Http11SslContextSpec.forServer(cert.certificate(), cert.privateKey());
|
||||
|
||||
this.reactorHandler = createHttpHandlerAdapter();
|
||||
this.reactorServer = reactor.netty5.http.server.HttpServer.create()
|
||||
.host(getHost())
|
||||
.port(getPort())
|
||||
.secure(sslContextSpec -> sslContextSpec.sslContext(http11SslContextSpec));
|
||||
}
|
||||
|
||||
private ReactorNetty2HttpHandlerAdapter createHttpHandlerAdapter() {
|
||||
return new ReactorNetty2HttpHandlerAdapter(resolveHttpHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startInternal() {
|
||||
DisposableServer server = this.reactorServer.handle(this.reactorHandler).bind().block();
|
||||
setPort(((InetSocketAddress) server.address()).getPort());
|
||||
this.serverRef.set(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopInternal() {
|
||||
this.serverRef.get().dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetInternal() {
|
||||
this.reactorServer = null;
|
||||
this.reactorHandler = null;
|
||||
this.serverRef.set(null);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user