From e9d16da6330ad0e6a52c8cb02359e4310e2781d1 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 11 Feb 2025 12:27:33 +0000 Subject: [PATCH] Remove Netty 5 support Closes gh-34345 --- .../build/TestConventions.java | 7 +- framework-platform/framework-platform.gradle | 2 - spring-core/spring-core.gradle | 1 - .../core/codec/Netty5BufferDecoder.java | 68 --- .../core/codec/Netty5BufferEncoder.java | 77 ---- .../core/io/buffer/Netty5DataBuffer.java | 401 ------------------ .../io/buffer/Netty5DataBufferFactory.java | 141 ------ .../core/codec/Netty5BufferDecoderTests.java | 97 ----- .../core/codec/Netty5BufferEncoderTests.java | 72 ---- .../core/io/buffer/DataBufferTests.java | 31 +- .../codec/AbstractDecoderTests.java | 18 +- .../AbstractDataBufferAllocatingTests.java | 35 +- spring-messaging/spring-messaging.gradle | 1 - .../stomp/ReactorNettyTcpStompClient.java | 32 +- .../stomp/StompBrokerRelayMessageHandler.java | 33 +- .../tcp/reactor/ReactorNetty2TcpClient.java | 349 --------------- .../reactor/ReactorNetty2TcpConnection.java | 88 ---- ...etty2StompBrokerRelayIntegrationTests.java | 38 -- .../ReactorNettyTcpStompClientTests.java | 15 +- .../server/DefaultWebTestClientBuilder.java | 7 - spring-web/spring-web.gradle | 5 - .../http/support/HeadersAdapterBenchmark.java | 4 +- .../http/support/HeadersAdaptersBaseline.java | 245 +---------- .../ReactorNetty2ClientHttpConnector.java | 144 ------- .../ReactorNetty2ClientHttpRequest.java | 162 ------- .../ReactorNetty2ClientHttpResponse.java | 183 -------- .../ReactorNetty2ResourceFactory.java | 248 ----------- .../http/codec/support/BaseDefaultCodecs.java | 13 +- .../ReactorNetty2HttpHandlerAdapter.java | 79 ---- .../ReactorNetty2ServerHttpRequest.java | 219 ---------- .../ReactorNetty2ServerHttpResponse.java | 141 ------ .../http/support/Netty5HeadersAdapter.java | 286 ------------- .../support/ClientCodecConfigurerTests.java | 19 +- .../codec/support/CodecConfigurerTests.java | 14 +- .../support/ServerCodecConfigurerTests.java | 11 +- .../DefaultServerHttpRequestBuilderTests.java | 4 +- .../server/reactive/HeadersAdaptersTests.java | 4 - .../bootstrap/ReactorNetty2HttpServer.java | 71 ---- .../bootstrap/ReactorNetty2HttpsServer.java | 78 ---- spring-webflux/spring-webflux.gradle | 2 - .../client/DefaultWebClientBuilder.java | 7 - .../web/reactive/socket/WebSocketMessage.java | 27 +- .../Netty5WebSocketSessionSupport.java | 107 ----- .../ReactorNetty2WebSocketSession.java | 173 -------- .../client/ReactorNetty2WebSocketClient.java | 159 ------- .../support/HandshakeWebSocketService.java | 27 +- .../ReactorNetty2RequestUpgradeStrategy.java | 113 ----- .../DelegatingWebFluxConfigurationTests.java | 4 +- .../WebFluxConfigurationSupportTests.java | 8 +- .../client/WebClientIntegrationTests.java | 17 - ...ractReactiveWebSocketIntegrationTests.java | 13 +- 51 files changed, 48 insertions(+), 4052 deletions(-) delete mode 100644 spring-core/src/main/java/org/springframework/core/codec/Netty5BufferDecoder.java delete mode 100644 spring-core/src/main/java/org/springframework/core/codec/Netty5BufferEncoder.java delete mode 100644 spring-core/src/main/java/org/springframework/core/io/buffer/Netty5DataBuffer.java delete mode 100644 spring-core/src/main/java/org/springframework/core/io/buffer/Netty5DataBufferFactory.java delete mode 100644 spring-core/src/test/java/org/springframework/core/codec/Netty5BufferDecoderTests.java delete mode 100644 spring-core/src/test/java/org/springframework/core/codec/Netty5BufferEncoderTests.java delete mode 100644 spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNetty2TcpClient.java delete mode 100644 spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNetty2TcpConnection.java delete mode 100644 spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNetty2StompBrokerRelayIntegrationTests.java delete mode 100644 spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpConnector.java delete mode 100644 spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java delete mode 100644 spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpResponse.java delete mode 100644 spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ResourceFactory.java delete mode 100644 spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2HttpHandlerAdapter.java delete mode 100644 spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java delete mode 100644 spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java delete mode 100644 spring-web/src/main/java/org/springframework/http/support/Netty5HeadersAdapter.java delete mode 100644 spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/ReactorNetty2HttpServer.java delete mode 100644 spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/ReactorNetty2HttpsServer.java delete mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/Netty5WebSocketSessionSupport.java delete mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/ReactorNetty2WebSocketSession.java delete mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/ReactorNetty2WebSocketClient.java delete mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNetty2RequestUpgradeStrategy.java diff --git a/buildSrc/src/main/java/org/springframework/build/TestConventions.java b/buildSrc/src/main/java/org/springframework/build/TestConventions.java index 715abe0d702..0f1bd09ead2 100644 --- a/buildSrc/src/main/java/org/springframework/build/TestConventions.java +++ b/buildSrc/src/main/java/org/springframework/build/TestConventions.java @@ -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. @@ -54,10 +54,7 @@ class TestConventions { test.include("**/*Tests.class", "**/*Test.class"); test.setSystemProperties(Map.of( "java.awt.headless", "true", - "io.netty.leakDetection.level", "paranoid", - "io.netty5.leakDetectionLevel", "paranoid", - "io.netty5.leakDetection.targetRecords", "32", - "io.netty5.buffer.lifecycleTracingEnabled", "true" + "io.netty.leakDetection.level", "paranoid" )); if (project.hasProperty("testGroups")) { test.systemProperty("testGroups", project.getProperties().get("testGroups")); diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index b3d88e364b1..5cdec26e7bd 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -10,7 +10,6 @@ dependencies { api(platform("com.fasterxml.jackson:jackson-bom:2.18.2")) api(platform("io.micrometer:micrometer-bom:1.14.4")) api(platform("io.netty:netty-bom:4.1.117.Final")) - api(platform("io.netty:netty5-bom:5.0.0.Alpha5")) api(platform("io.projectreactor:reactor-bom:2024.0.3")) api(platform("io.rsocket:rsocket-bom:1.1.5")) api(platform("org.apache.groovy:groovy-bom:4.0.24")) @@ -49,7 +48,6 @@ dependencies { api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2") api("io.micrometer:context-propagation:1.1.1") api("io.mockk:mockk:1.13.4") - api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3") api("io.projectreactor.tools:blockhound:1.0.8.RELEASE") api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE") api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE") diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index 71d5ab6d8f2..0c2fd914632 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -77,7 +77,6 @@ dependencies { compileOnly("org.graalvm.sdk:graal-sdk") optional("io.micrometer:context-propagation") optional("io.netty:netty-buffer") - optional("io.netty:netty5-buffer") optional("io.projectreactor:reactor-core") optional("io.reactivex.rxjava3:rxjava") optional("io.smallrye.reactive:mutiny") diff --git a/spring-core/src/main/java/org/springframework/core/codec/Netty5BufferDecoder.java b/spring-core/src/main/java/org/springframework/core/codec/Netty5BufferDecoder.java deleted file mode 100644 index 9b81fa50951..00000000000 --- a/spring-core/src/main/java/org/springframework/core/codec/Netty5BufferDecoder.java +++ /dev/null @@ -1,68 +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.core.codec; - -import java.util.Map; - -import io.netty5.buffer.Buffer; -import io.netty5.buffer.DefaultBufferAllocators; -import org.jspecify.annotations.Nullable; - -import org.springframework.core.ResolvableType; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.core.io.buffer.Netty5DataBuffer; -import org.springframework.util.MimeType; -import org.springframework.util.MimeTypeUtils; - -/** - * Decoder for {@link Buffer Buffers}. - * - * @author Violeta Georgieva - * @since 6.0 - */ -public class Netty5BufferDecoder extends AbstractDataBufferDecoder { - - public Netty5BufferDecoder() { - super(MimeTypeUtils.ALL); - } - - - @Override - public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) { - return (Buffer.class.isAssignableFrom(elementType.toClass()) && - super.canDecode(elementType, mimeType)); - } - - @Override - public Buffer decode(DataBuffer dataBuffer, ResolvableType elementType, - @Nullable MimeType mimeType, @Nullable Map hints) { - - if (logger.isDebugEnabled()) { - logger.debug(Hints.getLogPrefix(hints) + "Read " + dataBuffer.readableByteCount() + " bytes"); - } - if (dataBuffer instanceof Netty5DataBuffer netty5DataBuffer) { - return netty5DataBuffer.getNativeBuffer(); - } - byte[] bytes = new byte[dataBuffer.readableByteCount()]; - dataBuffer.read(bytes); - Buffer buffer = DefaultBufferAllocators.preferredAllocator().copyOf(bytes); - DataBufferUtils.release(dataBuffer); - return buffer; - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/codec/Netty5BufferEncoder.java b/spring-core/src/main/java/org/springframework/core/codec/Netty5BufferEncoder.java deleted file mode 100644 index 63a0a5f21a0..00000000000 --- a/spring-core/src/main/java/org/springframework/core/codec/Netty5BufferEncoder.java +++ /dev/null @@ -1,77 +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.core.codec; - -import java.util.Map; - -import io.netty5.buffer.Buffer; -import org.jspecify.annotations.Nullable; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; - -import org.springframework.core.ResolvableType; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.core.io.buffer.Netty5DataBufferFactory; -import org.springframework.util.MimeType; -import org.springframework.util.MimeTypeUtils; - -/** - * Encoder for {@link Buffer Buffers}. - * - * @author Violeta Georgieva - * @since 6.0 - */ -public class Netty5BufferEncoder extends AbstractEncoder { - - public Netty5BufferEncoder() { - super(MimeTypeUtils.ALL); - } - - - @Override - public boolean canEncode(ResolvableType type, @Nullable MimeType mimeType) { - Class clazz = type.toClass(); - return super.canEncode(type, mimeType) && Buffer.class.isAssignableFrom(clazz); - } - - @Override - public Flux encode(Publisher inputStream, - DataBufferFactory bufferFactory, ResolvableType elementType, @Nullable MimeType mimeType, - @Nullable Map hints) { - - return Flux.from(inputStream).map(byteBuffer -> - encodeValue(byteBuffer, bufferFactory, elementType, mimeType, hints)); - } - - @Override - public DataBuffer encodeValue(Buffer buffer, DataBufferFactory bufferFactory, ResolvableType valueType, - @Nullable MimeType mimeType, @Nullable Map hints) { - - if (logger.isDebugEnabled() && !Hints.isLoggingSuppressed(hints)) { - String logPrefix = Hints.getLogPrefix(hints); - logger.debug(logPrefix + "Writing " + buffer.readableBytes() + " bytes"); - } - if (bufferFactory instanceof Netty5DataBufferFactory netty5DataBufferFactory) { - return netty5DataBufferFactory.wrap(buffer); - } - byte[] bytes = new byte[buffer.readableBytes()]; - buffer.readBytes(bytes, 0, bytes.length); - buffer.close(); - return bufferFactory.wrap(bytes); - } -} diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/Netty5DataBuffer.java b/spring-core/src/main/java/org/springframework/core/io/buffer/Netty5DataBuffer.java deleted file mode 100644 index 418938eaa58..00000000000 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/Netty5DataBuffer.java +++ /dev/null @@ -1,401 +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.core.io.buffer; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.NoSuchElementException; -import java.util.function.IntPredicate; - -import io.netty5.buffer.Buffer; -import io.netty5.buffer.BufferComponent; -import io.netty5.buffer.ComponentIterator; -import org.jspecify.annotations.Nullable; - -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; - -/** - * Implementation of the {@code DataBuffer} interface that wraps a Netty 5 - * {@link Buffer}. Typically constructed with {@link Netty5DataBufferFactory}. - * - * @author Violeta Georgieva - * @author Arjen Poutsma - * @since 6.0 - */ -public final class Netty5DataBuffer implements CloseableDataBuffer, TouchableDataBuffer { - - private final Buffer buffer; - - private final Netty5DataBufferFactory dataBufferFactory; - - - /** - * Create a new {@code Netty5DataBuffer} based on the given {@code Buffer}. - * @param buffer the buffer to base this buffer on - */ - Netty5DataBuffer(Buffer buffer, Netty5DataBufferFactory dataBufferFactory) { - Assert.notNull(buffer, "Buffer must not be null"); - Assert.notNull(dataBufferFactory, "Netty5DataBufferFactory must not be null"); - this.buffer = buffer; - this.dataBufferFactory = dataBufferFactory; - } - - /** - * Directly exposes the native {@code Buffer} that this buffer is based on. - * @return the wrapped buffer - */ - public Buffer getNativeBuffer() { - return this.buffer; - } - - @Override - public DataBufferFactory factory() { - return this.dataBufferFactory; - } - - @Override - public int indexOf(IntPredicate predicate, int fromIndex) { - Assert.notNull(predicate, "IntPredicate must not be null"); - if (fromIndex < 0) { - fromIndex = 0; - } - else if (fromIndex >= this.buffer.writerOffset()) { - return -1; - } - int length = this.buffer.writerOffset() - fromIndex; - int bytes = this.buffer.openCursor(fromIndex, length).process(predicate.negate()::test); - return bytes == -1 ? -1 : fromIndex + bytes; - } - - @Override - public int lastIndexOf(IntPredicate predicate, int fromIndex) { - Assert.notNull(predicate, "IntPredicate must not be null"); - if (fromIndex < 0) { - return -1; - } - fromIndex = Math.min(fromIndex, this.buffer.writerOffset() - 1); - return this.buffer.openCursor(0, fromIndex + 1).process(predicate.negate()::test); - } - - @Override - public int readableByteCount() { - return this.buffer.readableBytes(); - } - - @Override - public int writableByteCount() { - return this.buffer.writableBytes(); - } - - @Override - public int readPosition() { - return this.buffer.readerOffset(); - } - - @Override - public Netty5DataBuffer readPosition(int readPosition) { - this.buffer.readerOffset(readPosition); - return this; - } - - @Override - public int writePosition() { - return this.buffer.writerOffset(); - } - - @Override - public Netty5DataBuffer writePosition(int writePosition) { - this.buffer.writerOffset(writePosition); - return this; - } - - @Override - public byte getByte(int index) { - return this.buffer.getByte(index); - } - - @Override - public int capacity() { - return this.buffer.capacity(); - } - - @Override - @Deprecated - public Netty5DataBuffer capacity(int capacity) { - if (capacity <= 0) { - throw new IllegalArgumentException(String.format("'newCapacity' %d must be higher than 0", capacity)); - } - int diff = capacity - capacity(); - if (diff > 0) { - this.buffer.ensureWritable(this.buffer.writableBytes() + diff); - } - return this; - } - - @Override - public DataBuffer ensureWritable(int capacity) { - Assert.isTrue(capacity >= 0, "Capacity must be >= 0"); - this.buffer.ensureWritable(capacity); - return this; - } - - @Override - public byte read() { - return this.buffer.readByte(); - } - - @Override - public Netty5DataBuffer read(byte[] destination) { - return read(destination, 0, destination.length); - } - - @Override - public Netty5DataBuffer read(byte[] destination, int offset, int length) { - this.buffer.readBytes(destination, offset, length); - return this; - } - - @Override - public Netty5DataBuffer write(byte b) { - this.buffer.writeByte(b); - return this; - } - - @Override - public Netty5DataBuffer write(byte[] source) { - this.buffer.writeBytes(source); - return this; - } - - @Override - public Netty5DataBuffer write(byte[] source, int offset, int length) { - this.buffer.writeBytes(source, offset, length); - return this; - } - - @Override - public Netty5DataBuffer write(DataBuffer... dataBuffers) { - if (!ObjectUtils.isEmpty(dataBuffers)) { - if (hasNetty5DataBuffers(dataBuffers)) { - Buffer[] nativeBuffers = new Buffer[dataBuffers.length]; - for (int i = 0; i < dataBuffers.length; i++) { - nativeBuffers[i] = ((Netty5DataBuffer) dataBuffers[i]).getNativeBuffer(); - } - return write(nativeBuffers); - } - else { - ByteBuffer[] byteBuffers = new ByteBuffer[dataBuffers.length]; - for (int i = 0; i < dataBuffers.length; i++) { - byteBuffers[i] = ByteBuffer.allocate(dataBuffers[i].readableByteCount()); - dataBuffers[i].toByteBuffer(byteBuffers[i]); - } - return write(byteBuffers); - } - } - return this; - } - - private static boolean hasNetty5DataBuffers(DataBuffer[] buffers) { - for (DataBuffer buffer : buffers) { - if (!(buffer instanceof Netty5DataBuffer)) { - return false; - } - } - return true; - } - - @Override - public Netty5DataBuffer write(ByteBuffer... buffers) { - if (!ObjectUtils.isEmpty(buffers)) { - for (ByteBuffer buffer : buffers) { - this.buffer.writeBytes(buffer); - } - } - return this; - } - - /** - * Writes one or more Netty 5 {@link Buffer Buffers} to this buffer, - * starting at the current writing position. - * @param buffers the buffers to write into this buffer - * @return this buffer - */ - public Netty5DataBuffer write(Buffer... buffers) { - if (!ObjectUtils.isEmpty(buffers)) { - for (Buffer buffer : buffers) { - this.buffer.writeBytes(buffer); - } - } - return this; - } - - @Override - public DataBuffer write(CharSequence charSequence, Charset charset) { - Assert.notNull(charSequence, "CharSequence must not be null"); - Assert.notNull(charset, "Charset must not be null"); - - this.buffer.writeCharSequence(charSequence, charset); - return this; - } - - /** - * {@inheritDoc} - *

Note that due to the lack of a {@code slice} method - * in Netty 5's {@link Buffer}, this implementation returns a copy that - * does not share its contents with this buffer. - */ - @Override - @Deprecated - public DataBuffer slice(int index, int length) { - Buffer copy = this.buffer.copy(index, length); - return new Netty5DataBuffer(copy, this.dataBufferFactory); - } - - @Override - public DataBuffer split(int index) { - Buffer split = this.buffer.split(index); - return new Netty5DataBuffer(split, this.dataBufferFactory); - } - - @Override - @Deprecated - public ByteBuffer asByteBuffer() { - return toByteBuffer(); - } - - @Override - @Deprecated - public ByteBuffer asByteBuffer(int index, int length) { - return toByteBuffer(index, length); - } - - @Override - @Deprecated - public ByteBuffer toByteBuffer(int index, int length) { - ByteBuffer copy = this.buffer.isDirect() ? - ByteBuffer.allocateDirect(length) : - ByteBuffer.allocate(length); - - this.buffer.copyInto(index, copy, 0, length); - return copy; - } - - @Override - public void toByteBuffer(int srcPos, ByteBuffer dest, int destPos, int length) { - this.buffer.copyInto(srcPos, dest, destPos, length); - } - - @Override - public ByteBufferIterator readableByteBuffers() { - return new BufferComponentIterator<>(this.buffer.forEachComponent(), true); - } - - @Override - public ByteBufferIterator writableByteBuffers() { - return new BufferComponentIterator<>(this.buffer.forEachComponent(), false); - } - - @Override - public String toString(Charset charset) { - Assert.notNull(charset, "Charset must not be null"); - return this.buffer.toString(charset); - } - - @Override - public String toString(int index, int length, Charset charset) { - Assert.notNull(charset, "Charset must not be null"); - byte[] data = new byte[length]; - this.buffer.copyInto(index, data, 0, length); - return new String(data, 0, length, charset); - } - - @Override - public Netty5DataBuffer touch(Object hint) { - this.buffer.touch(hint); - return this; - } - - @Override - public void close() { - this.buffer.close(); - } - - - @Override - public boolean equals(@Nullable Object other) { - return (this == other || (other instanceof Netty5DataBuffer that && this.buffer.equals(that.buffer))); - } - - @Override - public int hashCode() { - return this.buffer.hashCode(); - } - - @Override - public String toString() { - return this.buffer.toString(); - } - - - private static final class BufferComponentIterator - implements ByteBufferIterator { - - private final ComponentIterator delegate; - - private final boolean readable; - - private @Nullable T next; - - public BufferComponentIterator(ComponentIterator delegate, boolean readable) { - Assert.notNull(delegate, "Delegate must not be null"); - this.delegate = delegate; - this.readable = readable; - this.next = readable ? this.delegate.firstReadable() : this.delegate.firstWritable(); - } - - @Override - public boolean hasNext() { - return this.next != null; - } - - @Override - public ByteBuffer next() { - if (this.next != null) { - ByteBuffer result; - if (this.readable) { - result = this.next.readableBuffer(); - this.next = this.next.nextReadable(); - } - else { - result = this.next.writableBuffer(); - this.next = this.next.nextWritable(); - } - return result; - } - else { - throw new NoSuchElementException(); - } - } - - @Override - public void close() { - this.delegate.close(); - } - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/Netty5DataBufferFactory.java b/spring-core/src/main/java/org/springframework/core/io/buffer/Netty5DataBufferFactory.java deleted file mode 100644 index 7163274b61c..00000000000 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/Netty5DataBufferFactory.java +++ /dev/null @@ -1,141 +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.core.io.buffer; - -import java.nio.ByteBuffer; -import java.util.List; - -import io.netty5.buffer.Buffer; -import io.netty5.buffer.BufferAllocator; -import io.netty5.buffer.CompositeBuffer; -import io.netty5.buffer.DefaultBufferAllocators; - -import org.springframework.util.Assert; - -/** - * Implementation of the {@code DataBufferFactory} interface based on a - * Netty 5 {@link BufferAllocator}. - * - * @author Violeta Georgieva - * @author Arjen Poutsma - * @since 6.0 - */ -public class Netty5DataBufferFactory implements DataBufferFactory { - - private final BufferAllocator bufferAllocator; - - - /** - * Create a new {@code Netty5DataBufferFactory} based on the given factory. - * @param bufferAllocator the factory to use - */ - public Netty5DataBufferFactory(BufferAllocator bufferAllocator) { - Assert.notNull(bufferAllocator, "BufferAllocator must not be null"); - this.bufferAllocator = bufferAllocator; - } - - - /** - * Return the {@code BufferAllocator} used by this factory. - */ - public BufferAllocator getBufferAllocator() { - return this.bufferAllocator; - } - - @Override - @Deprecated - public Netty5DataBuffer allocateBuffer() { - Buffer buffer = this.bufferAllocator.allocate(256); - return new Netty5DataBuffer(buffer, this); - } - - @Override - public Netty5DataBuffer allocateBuffer(int initialCapacity) { - Buffer buffer = this.bufferAllocator.allocate(initialCapacity); - return new Netty5DataBuffer(buffer, this); - } - - @Override - public Netty5DataBuffer wrap(ByteBuffer byteBuffer) { - Buffer buffer = this.bufferAllocator.copyOf(byteBuffer); - return new Netty5DataBuffer(buffer, this); - } - - @Override - public Netty5DataBuffer wrap(byte[] bytes) { - Buffer buffer = this.bufferAllocator.copyOf(bytes); - return new Netty5DataBuffer(buffer, this); - } - - /** - * Wrap the given Netty {@link Buffer} in a {@code Netty5DataBuffer}. - * @param buffer the Netty buffer to wrap - * @return the wrapped buffer - */ - public Netty5DataBuffer wrap(Buffer buffer) { - buffer.touch("Wrap buffer"); - return new Netty5DataBuffer(buffer, this); - } - - /** - * {@inheritDoc} - *

This implementation uses Netty's {@link CompositeBuffer}. - */ - @Override - public DataBuffer join(List dataBuffers) { - Assert.notEmpty(dataBuffers, "DataBuffer List must not be empty"); - if (dataBuffers.size() == 1) { - return dataBuffers.get(0); - } - CompositeBuffer composite = this.bufferAllocator.compose(); - for (DataBuffer dataBuffer : dataBuffers) { - Assert.isInstanceOf(Netty5DataBuffer.class, dataBuffer); - composite.extendWith(((Netty5DataBuffer) dataBuffer).getNativeBuffer().send()); - } - return new Netty5DataBuffer(composite, this); - } - - @Override - public boolean isDirect() { - return this.bufferAllocator.getAllocationType().isDirect(); - } - - /** - * Return the given Netty {@link DataBuffer} as a {@link Buffer}. - *

Returns the {@linkplain Netty5DataBuffer#getNativeBuffer() native buffer} - * if {@code buffer} is a {@link Netty5DataBuffer}; returns - * {@link BufferAllocator#copyOf(ByteBuffer)} otherwise. - * @param buffer the {@code DataBuffer} to return a {@code Buffer} for - * @return the netty {@code Buffer} - */ - public static Buffer toBuffer(DataBuffer buffer) { - if (buffer instanceof Netty5DataBuffer netty5DataBuffer) { - return netty5DataBuffer.getNativeBuffer(); - } - else { - ByteBuffer byteBuffer = ByteBuffer.allocate(buffer.readableByteCount()); - buffer.toByteBuffer(byteBuffer); - return DefaultBufferAllocators.preferredAllocator().copyOf(byteBuffer); - } - } - - - @Override - public String toString() { - return "Netty5DataBufferFactory (" + this.bufferAllocator + ")"; - } -} diff --git a/spring-core/src/test/java/org/springframework/core/codec/Netty5BufferDecoderTests.java b/spring-core/src/test/java/org/springframework/core/codec/Netty5BufferDecoderTests.java deleted file mode 100644 index a1220d099cc..00000000000 --- a/spring-core/src/test/java/org/springframework/core/codec/Netty5BufferDecoderTests.java +++ /dev/null @@ -1,97 +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.core.codec; - -import java.nio.charset.StandardCharsets; -import java.util.function.Consumer; - -import io.netty5.buffer.Buffer; -import io.netty5.buffer.DefaultBufferAllocators; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - -import org.springframework.core.ResolvableType; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.testfixture.codec.AbstractDecoderTests; -import org.springframework.util.MimeTypeUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Arjen Poutsma - */ -class Netty5BufferDecoderTests extends AbstractDecoderTests { - - private final byte[] fooBytes = "foo".getBytes(StandardCharsets.UTF_8); - - private final byte[] barBytes = "bar".getBytes(StandardCharsets.UTF_8); - - - Netty5BufferDecoderTests() { - super(new Netty5BufferDecoder()); - } - - @Override - @Test - protected void canDecode() { - assertThat(this.decoder.canDecode(ResolvableType.forClass(Buffer.class), - MimeTypeUtils.TEXT_PLAIN)).isTrue(); - assertThat(this.decoder.canDecode(ResolvableType.forClass(Integer.class), - MimeTypeUtils.TEXT_PLAIN)).isFalse(); - assertThat(this.decoder.canDecode(ResolvableType.forClass(Buffer.class), - MimeTypeUtils.APPLICATION_JSON)).isTrue(); - } - - @Override - @Test - protected void decode() { - Flux input = Flux.concat( - dataBuffer(this.fooBytes), - dataBuffer(this.barBytes)); - - testDecodeAll(input, Buffer.class, step -> step - .consumeNextWith(expectByteBuffer(DefaultBufferAllocators.preferredAllocator().copyOf(this.fooBytes))) - .consumeNextWith(expectByteBuffer(DefaultBufferAllocators.preferredAllocator().copyOf(this.barBytes))) - .verifyComplete()); - } - - @Override - @Test - protected void decodeToMono() { - Flux input = Flux.concat( - dataBuffer(this.fooBytes), - dataBuffer(this.barBytes)); - - Buffer expected = DefaultBufferAllocators.preferredAllocator().allocate(this.fooBytes.length + this.barBytes.length) - .writeBytes(this.fooBytes) - .writeBytes(this.barBytes) - .readerOffset(0); - - testDecodeToMonoAll(input, Buffer.class, step -> step - .consumeNextWith(expectByteBuffer(expected)) - .verifyComplete()); - } - - private Consumer expectByteBuffer(Buffer expected) { - return actual -> { - try (actual; expected) { - assertThat(actual).isEqualTo(expected); - } - }; - } - -} diff --git a/spring-core/src/test/java/org/springframework/core/codec/Netty5BufferEncoderTests.java b/spring-core/src/test/java/org/springframework/core/codec/Netty5BufferEncoderTests.java deleted file mode 100644 index 2312971622a..00000000000 --- a/spring-core/src/test/java/org/springframework/core/codec/Netty5BufferEncoderTests.java +++ /dev/null @@ -1,72 +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.core.codec; - -import java.nio.charset.StandardCharsets; - -import io.netty5.buffer.Buffer; -import io.netty5.buffer.DefaultBufferAllocators; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - -import org.springframework.core.ResolvableType; -import org.springframework.core.testfixture.codec.AbstractEncoderTests; -import org.springframework.util.MimeTypeUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Arjen Poutsma - */ -class Netty5BufferEncoderTests extends AbstractEncoderTests { - - private final byte[] fooBytes = "foo".getBytes(StandardCharsets.UTF_8); - - private final byte[] barBytes = "bar".getBytes(StandardCharsets.UTF_8); - - Netty5BufferEncoderTests() { - super(new Netty5BufferEncoder()); - } - - @Test - @Override - public void canEncode() { - assertThat(this.encoder.canEncode(ResolvableType.forClass(Buffer.class), - MimeTypeUtils.TEXT_PLAIN)).isTrue(); - assertThat(this.encoder.canEncode(ResolvableType.forClass(Integer.class), - MimeTypeUtils.TEXT_PLAIN)).isFalse(); - assertThat(this.encoder.canEncode(ResolvableType.forClass(Buffer.class), - MimeTypeUtils.APPLICATION_JSON)).isTrue(); - - // gh-20024 - assertThat(this.encoder.canEncode(ResolvableType.NONE, null)).isFalse(); - } - - @Test - @Override - @SuppressWarnings("resource") - public void encode() { - Flux input = Flux.just(this.fooBytes, this.barBytes) - .map(DefaultBufferAllocators.preferredAllocator()::copyOf); - - testEncodeAll(input, Buffer.class, step -> step - .consumeNextWith(expectBytes(this.fooBytes)) - .consumeNextWith(expectBytes(this.barBytes)) - .verifyComplete()); - } - -} diff --git a/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java b/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java index a425f389b0d..a91aab01d37 100644 --- a/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java @@ -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. @@ -28,7 +28,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatException; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.junit.jupiter.api.Assumptions.assumeFalse; /** * @author Arjen Poutsma @@ -414,9 +413,6 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests { @ParameterizedDataBufferAllocatingTest @SuppressWarnings("deprecation") void decreaseCapacityLowReadPosition(DataBufferFactory bufferFactory) { - assumeFalse(bufferFactory instanceof Netty5DataBufferFactory, - "Netty 5 does not support decreasing the capacity"); - super.bufferFactory = bufferFactory; DataBuffer buffer = createDataBuffer(2); @@ -430,9 +426,6 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests { @ParameterizedDataBufferAllocatingTest @SuppressWarnings("deprecation") void decreaseCapacityHighReadPosition(DataBufferFactory bufferFactory) { - assumeFalse(bufferFactory instanceof Netty5DataBufferFactory, - "Netty 5 does not support decreasing the capacity"); - super.bufferFactory = bufferFactory; DataBuffer buffer = createDataBuffer(2); @@ -543,11 +536,6 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests { ByteBuffer result = buffer.asByteBuffer(1, 2); assertThat(result.capacity()).isEqualTo(2); - assumeFalse(bufferFactory instanceof Netty5DataBufferFactory, () -> { - DataBufferUtils.release(buffer); - return "Netty 5 does share the internal buffer"; - }); - buffer.write((byte) 'c'); assertThat(result.remaining()).isEqualTo(2); @@ -561,9 +549,6 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests { @ParameterizedDataBufferAllocatingTest @SuppressWarnings("deprecation") void byteBufferContainsDataBufferChanges(DataBufferFactory bufferFactory) { - assumeFalse(bufferFactory instanceof Netty5DataBufferFactory, - "Netty 5 does not support sharing data between buffers"); - super.bufferFactory = bufferFactory; DataBuffer dataBuffer = createDataBuffer(1); @@ -581,9 +566,6 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests { @ParameterizedDataBufferAllocatingTest @SuppressWarnings("deprecation") void dataBufferContainsByteBufferChanges(DataBufferFactory bufferFactory) { - assumeFalse(bufferFactory instanceof Netty5DataBufferFactory, - "Netty 5 does not support sharing data between buffers"); - super.bufferFactory = bufferFactory; DataBuffer dataBuffer = createDataBuffer(1); @@ -833,18 +815,13 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests { result = new byte[2]; slice.read(result); - if (!(bufferFactory instanceof Netty5DataBufferFactory)) { - assertThat(result).isEqualTo(new byte[]{'b', 'c'}); - } + assertThat(result).isEqualTo(new byte[]{'b', 'c'}); release(buffer); } @ParameterizedDataBufferAllocatingTest @SuppressWarnings("deprecation") void retainedSlice(DataBufferFactory bufferFactory) { - assumeFalse(bufferFactory instanceof Netty5DataBufferFactory, - "Netty 5 does not support retainedSlice"); - super.bufferFactory = bufferFactory; DataBuffer buffer = createDataBuffer(3); @@ -887,9 +864,7 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests { assertThat(result).isEqualTo(bytes); - if (bufferFactory instanceof Netty5DataBufferFactory) { - release(slice); - } + release(slice); release(buffer); } diff --git a/spring-core/src/testFixtures/java/org/springframework/core/testfixture/codec/AbstractDecoderTests.java b/spring-core/src/testFixtures/java/org/springframework/core/testfixture/codec/AbstractDecoderTests.java index 4e6aec96de5..5020eecac32 100644 --- a/spring-core/src/testFixtures/java/org/springframework/core/testfixture/codec/AbstractDecoderTests.java +++ b/spring-core/src/testFixtures/java/org/springframework/core/testfixture/codec/AbstractDecoderTests.java @@ -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. @@ -20,7 +20,6 @@ import java.time.Duration; import java.util.Map; import java.util.function.Consumer; -import io.netty5.buffer.Buffer; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; @@ -210,13 +209,7 @@ public abstract class AbstractDecoderTests> extends Abstrac Flux flux = Mono.from(input).concatWith(Flux.error(new InputException())); assertThatExceptionOfType(InputException.class).isThrownBy(() -> - this.decoder.decode(flux, outputType, mimeType, hints) - .doOnNext(object -> { - if (object instanceof Buffer buffer) { - buffer.close(); - } - }) - .blockLast(Duration.ofSeconds(5))); + this.decoder.decode(flux, outputType, mimeType, hints).blockLast(Duration.ofSeconds(5))); } /** @@ -233,12 +226,7 @@ public abstract class AbstractDecoderTests> extends Abstrac protected void testDecodeCancel(Publisher input, ResolvableType outputType, @Nullable MimeType mimeType, @Nullable Map hints) { - Flux result = this.decoder.decode(input, outputType, mimeType, hints) - .doOnNext(object -> { - if (object instanceof Buffer buffer) { - buffer.close(); - } - }); + Flux result = this.decoder.decode(input, outputType, mimeType, hints); StepVerifier.create(result).expectNextCount(1).thenCancel().verify(); } diff --git a/spring-core/src/testFixtures/java/org/springframework/core/testfixture/io/buffer/AbstractDataBufferAllocatingTests.java b/spring-core/src/testFixtures/java/org/springframework/core/testfixture/io/buffer/AbstractDataBufferAllocatingTests.java index d6ff6b14b86..81895f5c7f8 100644 --- a/spring-core/src/testFixtures/java/org/springframework/core/testfixture/io/buffer/AbstractDataBufferAllocatingTests.java +++ b/spring-core/src/testFixtures/java/org/springframework/core/testfixture/io/buffer/AbstractDataBufferAllocatingTests.java @@ -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. @@ -34,8 +34,6 @@ import io.netty.buffer.PoolArenaMetric; import io.netty.buffer.PooledByteBufAllocator; import io.netty.buffer.PooledByteBufAllocatorMetric; import io.netty.buffer.UnpooledByteBufAllocator; -import io.netty5.buffer.BufferAllocator; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.RegisterExtension; @@ -48,7 +46,6 @@ import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBufferFactory; -import org.springframework.core.io.buffer.Netty5DataBufferFactory; import org.springframework.core.io.buffer.NettyDataBufferFactory; import static java.nio.charset.StandardCharsets.UTF_8; @@ -65,14 +62,6 @@ import static org.junit.jupiter.params.provider.Arguments.argumentSet; */ public abstract class AbstractDataBufferAllocatingTests { - private static BufferAllocator netty5OnHeapUnpooled; - - private static BufferAllocator netty5OffHeapUnpooled; - - private static BufferAllocator netty5OffHeapPooled; - - private static BufferAllocator netty5OnHeapPooled; - private static UnpooledByteBufAllocator netty4OffHeapUnpooled; private static UnpooledByteBufAllocator netty4OnHeapUnpooled; @@ -178,19 +167,6 @@ public abstract class AbstractDataBufferAllocatingTests { netty4OffHeapUnpooled = new UnpooledByteBufAllocator(true); netty4OnHeapPooled = new PooledByteBufAllocator(false, 1, 1, 4096, 4, 0, 0, 0, true); netty4OffHeapPooled = new PooledByteBufAllocator(true, 1, 1, 4096, 4, 0, 0, 0, true); - - netty5OnHeapUnpooled = BufferAllocator.onHeapUnpooled(); - netty5OffHeapUnpooled = BufferAllocator.offHeapUnpooled(); - netty5OnHeapPooled = BufferAllocator.onHeapPooled(); - netty5OffHeapPooled = BufferAllocator.offHeapPooled(); - } - - @AfterAll - static void closeAllocators() { - netty5OnHeapUnpooled.close(); - netty5OffHeapUnpooled.close(); - netty5OnHeapPooled.close(); - netty5OffHeapPooled.close(); } @@ -212,15 +188,6 @@ public abstract class AbstractDataBufferAllocatingTests { new NettyDataBufferFactory(netty4OffHeapPooled)), argumentSet("NettyDataBufferFactory - PooledByteBufAllocator - preferDirect = false", new NettyDataBufferFactory(netty4OnHeapPooled)), - // Netty 5 - argumentSet("Netty5DataBufferFactory - BufferAllocator.onHeapUnpooled()", - new Netty5DataBufferFactory(netty5OnHeapUnpooled)), - argumentSet("Netty5DataBufferFactory - BufferAllocator.offHeapUnpooled()", - new Netty5DataBufferFactory(netty5OffHeapUnpooled)), - argumentSet("Netty5DataBufferFactory - BufferAllocator.onHeapPooled()", - new Netty5DataBufferFactory(netty5OnHeapPooled)), - argumentSet("Netty5DataBufferFactory - BufferAllocator.offHeapPooled()", - new Netty5DataBufferFactory(netty5OffHeapPooled)), // Default argumentSet("DefaultDataBufferFactory - preferDirect = true", new DefaultDataBufferFactory(true)), diff --git a/spring-messaging/spring-messaging.gradle b/spring-messaging/spring-messaging.gradle index b3f920c4a58..6440084bc22 100644 --- a/spring-messaging/spring-messaging.gradle +++ b/spring-messaging/spring-messaging.gradle @@ -14,7 +14,6 @@ dependencies { optional("com.google.code.gson:gson") optional("com.google.protobuf:protobuf-java-util") optional("io.projectreactor.netty:reactor-netty-http") - optional("io.projectreactor.netty:reactor-netty5-http") optional("io.rsocket:rsocket-core") optional("io.rsocket:rsocket-transport-netty") optional("jakarta.json.bind:jakarta.json.bind-api") diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClient.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClient.java index 8548c41a276..44a203393d0 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClient.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClient.java @@ -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. @@ -22,31 +22,17 @@ import org.jspecify.annotations.Nullable; import org.springframework.messaging.simp.SimpLogging; import org.springframework.messaging.tcp.TcpOperations; -import org.springframework.messaging.tcp.reactor.ReactorNetty2TcpClient; import org.springframework.messaging.tcp.reactor.ReactorNettyTcpClient; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; /** - * A STOMP over TCP client, configurable with either - * {@link ReactorNettyTcpClient} or {@link ReactorNetty2TcpClient}. + * A STOMP over TCP client, configurable with {@link ReactorNettyTcpClient}. * * @author Rossen Stoyanchev * @since 5.0 */ public class ReactorNettyTcpStompClient extends StompClientSupport { - private static final boolean reactorNettyClientPresent; - - private static final boolean reactorNetty2ClientPresent; - - static { - ClassLoader classLoader = StompBrokerRelayMessageHandler.class.getClassLoader(); - reactorNettyClientPresent = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", classLoader); - reactorNetty2ClientPresent = ClassUtils.isPresent("reactor.netty5.http.client.HttpClient", classLoader); - } - - private final TcpOperations tcpClient; @@ -76,17 +62,9 @@ public class ReactorNettyTcpStompClient extends StompClientSupport { } private static TcpOperations initTcpClient(String host, int port) { - if (reactorNettyClientPresent) { - ReactorNettyTcpClient client = new ReactorNettyTcpClient<>(host, port, new StompReactorNettyCodec()); - client.setLogger(SimpLogging.forLog(client.getLogger())); - return client; - } - else if (reactorNetty2ClientPresent) { - ReactorNetty2TcpClient client = new ReactorNetty2TcpClient<>(host, port, new StompTcpMessageCodec()); - client.setLogger(SimpLogging.forLog(client.getLogger())); - return client; - } - throw new IllegalStateException("No compatible version of Reactor Netty"); + ReactorNettyTcpClient client = new ReactorNettyTcpClient<>(host, port, new StompReactorNettyCodec()); + client.setLogger(SimpLogging.forLog(client.getLogger())); + return client; } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java index f8a1daaed96..e2697d464b8 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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. @@ -44,13 +44,10 @@ import org.springframework.messaging.support.MessageHeaderInitializer; import org.springframework.messaging.tcp.FixedIntervalReconnectStrategy; import org.springframework.messaging.tcp.TcpConnection; import org.springframework.messaging.tcp.TcpOperations; -import org.springframework.messaging.tcp.reactor.ReactorNetty2TcpClient; import org.springframework.messaging.tcp.reactor.ReactorNettyCodec; import org.springframework.messaging.tcp.reactor.ReactorNettyTcpClient; -import org.springframework.messaging.tcp.reactor.TcpMessageCodec; import org.springframework.scheduling.TaskScheduler; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; /** * A {@link org.springframework.messaging.MessageHandler} that handles messages by @@ -105,18 +102,10 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler private static final Message HEARTBEAT_MESSAGE; - private static final boolean reactorNettyClientPresent; - - private static final boolean reactorNetty2ClientPresent; - static { HEART_BEAT_ACCESSOR = StompHeaderAccessor.createForHeartbeat(); HEARTBEAT_MESSAGE = MessageBuilder.createMessage( StompDecoder.HEARTBEAT_PAYLOAD, HEART_BEAT_ACCESSOR.getMessageHeaders()); - - ClassLoader classLoader = StompBrokerRelayMessageHandler.class.getClassLoader(); - reactorNettyClientPresent = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", classLoader); - reactorNetty2ClientPresent = ClassUtils.isPresent("reactor.netty5.http.client.HttpClient", classLoader); } @@ -352,8 +341,7 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler /** * Configure a TCP client for managing TCP connections to the STOMP broker. - *

By default {@link ReactorNettyTcpClient} or - * {@link ReactorNetty2TcpClient} is used. + *

By default {@link ReactorNettyTcpClient} is used. *

Note: when this property is used, any * {@link #setRelayHost(String) host} or {@link #setRelayPort(int) port} * specified are effectively ignored. @@ -468,19 +456,10 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler if (this.headerInitializer != null) { decoder.setHeaderInitializer(this.headerInitializer); } - if (reactorNettyClientPresent) { - ReactorNettyCodec codec = new StompReactorNettyCodec(decoder); - ReactorNettyTcpClient client = new ReactorNettyTcpClient<>(this.relayHost, this.relayPort, codec); - client.setLogger(SimpLogging.forLog(client.getLogger())); - return client; - } - else if (reactorNetty2ClientPresent) { - TcpMessageCodec codec = new StompTcpMessageCodec(decoder); - ReactorNetty2TcpClient client = new ReactorNetty2TcpClient<>(this.relayHost, this.relayPort, codec); - client.setLogger(SimpLogging.forLog(client.getLogger())); - return client; - } - throw new IllegalStateException("No compatible version of Reactor Netty"); + ReactorNettyCodec codec = new StompReactorNettyCodec(decoder); + ReactorNettyTcpClient client = new ReactorNettyTcpClient<>(this.relayHost, this.relayPort, codec); + client.setLogger(SimpLogging.forLog(client.getLogger())); + return client; } @Override diff --git a/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNetty2TcpClient.java b/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNetty2TcpClient.java deleted file mode 100644 index f8cace98efb..00000000000 --- a/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNetty2TcpClient.java +++ /dev/null @@ -1,349 +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.messaging.tcp.reactor; - -import java.nio.ByteBuffer; -import java.time.Duration; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.function.BiFunction; -import java.util.function.Function; - -import io.netty5.buffer.Buffer; -import io.netty5.channel.ChannelHandlerContext; -import io.netty5.channel.group.ChannelGroup; -import io.netty5.channel.group.DefaultChannelGroup; -import io.netty5.handler.codec.ByteToMessageDecoder; -import io.netty5.util.concurrent.ImmediateEventExecutor; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; -import reactor.core.scheduler.Scheduler; -import reactor.core.scheduler.Schedulers; -import reactor.netty5.Connection; -import reactor.netty5.NettyInbound; -import reactor.netty5.NettyOutbound; -import reactor.netty5.resources.ConnectionProvider; -import reactor.netty5.resources.LoopResources; -import reactor.netty5.tcp.TcpClient; -import reactor.util.retry.Retry; - -import org.springframework.messaging.Message; -import org.springframework.messaging.tcp.ReconnectStrategy; -import org.springframework.messaging.tcp.TcpConnection; -import org.springframework.messaging.tcp.TcpConnectionHandler; -import org.springframework.messaging.tcp.TcpOperations; -import org.springframework.util.Assert; - -/** - * Reactor Netty based implementation of {@link TcpOperations}. - * - *

This class is based on {@link ReactorNettyTcpClient}. - * - * @author Rossen Stoyanchev - * @since 6.0 - * @param

the type of payload for in and outbound messages - */ -public class ReactorNetty2TcpClient

implements TcpOperations

{ - - private static final int PUBLISH_ON_BUFFER_SIZE = 16; - - - private final TcpClient tcpClient; - - private final TcpMessageCodec

codec; - - private final @Nullable ChannelGroup channelGroup; - - private final @Nullable LoopResources loopResources; - - private final @Nullable ConnectionProvider poolResources; - - private final Scheduler scheduler = Schedulers.newParallel("tcp-client-scheduler"); - - private Log logger = LogFactory.getLog(ReactorNetty2TcpClient.class); - - private volatile boolean stopping; - - - /** - * Simple constructor with the host and port to use to connect to. - *

This constructor manages the lifecycle of the {@link TcpClient} and - * underlying resources such as {@link ConnectionProvider}, - * {@link LoopResources}, and {@link ChannelGroup}. - *

For full control over the initialization and lifecycle of the - * TcpClient, use {@link #ReactorNetty2TcpClient(TcpClient, TcpMessageCodec)}. - * @param host the host to connect to - * @param port the port to connect to - * @param codec for encoding and decoding the input/output byte streams - * @see org.springframework.messaging.simp.stomp.StompReactorNettyCodec - */ - public ReactorNetty2TcpClient(String host, int port, TcpMessageCodec

codec) { - Assert.notNull(host, "host is required"); - Assert.notNull(codec, "ReactorNettyCodec is required"); - - this.channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE); - this.loopResources = LoopResources.create("tcp-client-loop"); - this.poolResources = ConnectionProvider.create("tcp-client-pool", 10000); - this.codec = codec; - - this.tcpClient = TcpClient.create(this.poolResources) - .host(host).port(port) - .runOn(this.loopResources, false) - .doOnConnected(conn -> this.channelGroup.add(conn.channel())); - } - - /** - * A variant of {@link #ReactorNetty2TcpClient(String, int, TcpMessageCodec)} - * that still manages the lifecycle of the {@link TcpClient} and underlying - * resources, but allows for direct configuration of other properties of the - * client through a {@code Function}. - * @param clientConfigurer the configurer function - * @param codec for encoding and decoding the input/output byte streams - * @since 5.1.3 - * @see org.springframework.messaging.simp.stomp.StompReactorNettyCodec - */ - public ReactorNetty2TcpClient(Function clientConfigurer, TcpMessageCodec

codec) { - Assert.notNull(codec, "ReactorNettyCodec is required"); - - this.channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE); - this.loopResources = LoopResources.create("tcp-client-loop"); - this.poolResources = ConnectionProvider.create("tcp-client-pool", 10000); - this.codec = codec; - - this.tcpClient = clientConfigurer.apply(TcpClient - .create(this.poolResources) - .runOn(this.loopResources, false) - .doOnConnected(conn -> this.channelGroup.add(conn.channel()))); - } - - /** - * Constructor with an externally created {@link TcpClient} instance whose - * lifecycle is expected to be managed externally. - * @param tcpClient the TcpClient instance to use - * @param codec for encoding and decoding the input/output byte streams - * @see org.springframework.messaging.simp.stomp.StompReactorNettyCodec - */ - public ReactorNetty2TcpClient(TcpClient tcpClient, TcpMessageCodec

codec) { - Assert.notNull(tcpClient, "TcpClient is required"); - Assert.notNull(codec, "ReactorNettyCodec is required"); - this.tcpClient = tcpClient; - this.codec = codec; - - this.channelGroup = null; - this.loopResources = null; - this.poolResources = null; - } - - - /** - * Set an alternative logger to use than the one based on the class name. - * @param logger the logger to use - * @since 5.1 - */ - public void setLogger(Log logger) { - this.logger = logger; - } - - /** - * Return the currently configured Logger. - * @since 5.1 - */ - public Log getLogger() { - return logger; - } - - - @Override - public CompletableFuture connectAsync(TcpConnectionHandler

handler) { - Assert.notNull(handler, "TcpConnectionHandler is required"); - - if (this.stopping) { - return handleShuttingDownConnectFailure(handler); - } - - return extendTcpClient(this.tcpClient, handler) - .handle(new ReactorNettyHandler(handler)) - .connect() - .doOnError(handler::afterConnectFailure) - .then().toFuture(); - } - - /** - * Provides an opportunity to initialize the {@link TcpClient} for the given - * {@link TcpConnectionHandler} which may implement sub-interfaces such as - * {@link org.springframework.messaging.simp.stomp.StompTcpConnectionHandler} - * that expose further information. - * @param tcpClient the candidate TcpClient - * @param handler the handler for the TCP connection - * @return the same handler or an updated instance - */ - protected TcpClient extendTcpClient(TcpClient tcpClient, TcpConnectionHandler

handler) { - return tcpClient; - } - - @Override - public CompletableFuture connectAsync(TcpConnectionHandler

handler, ReconnectStrategy strategy) { - Assert.notNull(handler, "TcpConnectionHandler is required"); - Assert.notNull(strategy, "ReconnectStrategy is required"); - - if (this.stopping) { - return handleShuttingDownConnectFailure(handler); - } - - // Report first connect to the ListenableFuture - CompletableFuture connectFuture = new CompletableFuture<>(); - - extendTcpClient(this.tcpClient, handler) - .handle(new ReactorNettyHandler(handler)) - .connect() - .doOnNext(conn -> connectFuture.complete(null)) - .doOnError(connectFuture::completeExceptionally) - .doOnError(handler::afterConnectFailure) // report all connect failures to the handler - .flatMap(Connection::onDispose) // post-connect issues - .retryWhen(Retry.from(signals -> signals - .map(retrySignal -> (int) retrySignal.totalRetriesInARow()) - .flatMap(attempt -> reconnect(attempt, strategy)))) - .repeatWhen(flux -> flux - .scan(1, (count, element) -> count++) - .flatMap(attempt -> reconnect(attempt, strategy))) - .subscribe(); - return connectFuture; - } - - private CompletableFuture handleShuttingDownConnectFailure(TcpConnectionHandler

handler) { - IllegalStateException ex = new IllegalStateException("Shutting down."); - handler.afterConnectFailure(ex); - return Mono.error(ex).toFuture(); - } - - private Publisher reconnect(Integer attempt, ReconnectStrategy reconnectStrategy) { - Long time = reconnectStrategy.getTimeToNextAttempt(attempt); - return (time != null ? Mono.delay(Duration.ofMillis(time), this.scheduler) : Mono.empty()); - } - - @Override - public CompletableFuture shutdownAsync() { - if (this.stopping) { - return CompletableFuture.completedFuture(null); - } - - this.stopping = true; - - Mono result; - if (this.channelGroup != null) { - Sinks.Empty channnelGroupCloseSink = Sinks.empty(); - this.channelGroup.close().addListener(future -> channnelGroupCloseSink.tryEmitEmpty()); - result = channnelGroupCloseSink.asMono(); - if (this.loopResources != null) { - result = result.onErrorComplete().then(this.loopResources.disposeLater()); - } - if (this.poolResources != null) { - result = result.onErrorComplete().then(this.poolResources.disposeLater()); - } - result = result.onErrorComplete().then(stopScheduler()); - } - else { - result = stopScheduler(); - } - - return result.toFuture(); - } - - private Mono stopScheduler() { - return Mono.fromRunnable(() -> { - this.scheduler.dispose(); - for (int i = 0; i < 20; i++) { - if (this.scheduler.isDisposed()) { - break; - } - try { - Thread.sleep(100); - } - catch (Throwable ex) { - break; - } - } - }); - } - - @Override - public String toString() { - return "ReactorNetty2TcpClient[" + this.tcpClient + "]"; - } - - - private class ReactorNettyHandler implements BiFunction> { - - private final TcpConnectionHandler

connectionHandler; - - ReactorNettyHandler(TcpConnectionHandler

handler) { - this.connectionHandler = handler; - } - - @Override - @SuppressWarnings("unchecked") - public Publisher apply(NettyInbound inbound, NettyOutbound outbound) { - inbound.withConnection(conn -> { - if (logger.isDebugEnabled()) { - logger.debug("Connected to " + conn.address()); - } - }); - Sinks.Empty completionSink = Sinks.empty(); - TcpConnection

connection = new ReactorNetty2TcpConnection<>(inbound, outbound, codec, completionSink); - scheduler.schedule(() -> this.connectionHandler.afterConnected(connection)); - - inbound.withConnection(conn -> conn.addHandlerFirst(new StompMessageDecoder<>(codec))); - - inbound.receiveObject() - .cast(Message.class) - .publishOn(scheduler, PUBLISH_ON_BUFFER_SIZE) - .subscribe( - this.connectionHandler::handleMessage, - this.connectionHandler::handleFailure, - this.connectionHandler::afterConnectionClosed); - - return completionSink.asMono(); - } - } - - - private static class StompMessageDecoder

extends ByteToMessageDecoder { - - private final TcpMessageCodec

codec; - - StompMessageDecoder(TcpMessageCodec

codec) { - this.codec = codec; - } - - @Override - protected void decode(ChannelHandlerContext ctx, Buffer buffer) throws Exception { - ByteBuffer byteBuffer = ByteBuffer.allocate(buffer.readableBytes()); - buffer.readBytes(byteBuffer); - byteBuffer.flip(); - List> messages = this.codec.decode(byteBuffer); - for (Message

message : messages) { - ctx.fireChannelRead(message); - } - } - - } - -} diff --git a/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNetty2TcpConnection.java b/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNetty2TcpConnection.java deleted file mode 100644 index 12da763ab6d..00000000000 --- a/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNetty2TcpConnection.java +++ /dev/null @@ -1,88 +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.messaging.tcp.reactor; - -import java.nio.ByteBuffer; -import java.util.concurrent.CompletableFuture; - -import io.netty5.buffer.Buffer; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; -import reactor.netty5.NettyInbound; -import reactor.netty5.NettyOutbound; - -import org.springframework.messaging.Message; -import org.springframework.messaging.tcp.TcpConnection; - -/** - * Reactor Netty based implementation of {@link TcpConnection}. - * - *

This class is based on {@link ReactorNettyTcpConnection}. - * - * @author Rossen Stoyanchev - * @since 6.0 - * @param

the type of payload for outbound messages - */ -public class ReactorNetty2TcpConnection

implements TcpConnection

{ - - private final NettyInbound inbound; - - private final NettyOutbound outbound; - - private final TcpMessageCodec

codec; - - private final Sinks.Empty completionSink; - - - public ReactorNetty2TcpConnection(NettyInbound inbound, NettyOutbound outbound, - TcpMessageCodec

codec, Sinks.Empty completionSink) { - - this.inbound = inbound; - this.outbound = outbound; - this.codec = codec; - this.completionSink = completionSink; - } - - - @Override - public CompletableFuture sendAsync(Message

message) { - ByteBuffer byteBuffer = this.codec.encode(message); - Buffer buffer = this.outbound.alloc().copyOf(byteBuffer); - return this.outbound.send(Mono.just(buffer)).then().toFuture(); - } - - @Override - public void onReadInactivity(Runnable runnable, long inactivityDuration) { - this.inbound.withConnection(conn -> conn.onReadIdle(inactivityDuration, runnable)); - } - - @Override - public void onWriteInactivity(Runnable runnable, long inactivityDuration) { - this.inbound.withConnection(conn -> conn.onWriteIdle(inactivityDuration, runnable)); - } - - @Override - public void close() { - // Ignore result: concurrent attempts to complete are ok - this.completionSink.tryEmitEmpty(); - } - - @Override - public String toString() { - return "ReactorNetty2TcpConnection[inbound=" + this.inbound + "]"; - } -} diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNetty2StompBrokerRelayIntegrationTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNetty2StompBrokerRelayIntegrationTests.java deleted file mode 100644 index f3b502f53f6..00000000000 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNetty2StompBrokerRelayIntegrationTests.java +++ /dev/null @@ -1,38 +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.messaging.simp.stomp; - -import org.junit.jupiter.api.Disabled; - -import org.springframework.messaging.tcp.TcpOperations; -import org.springframework.messaging.tcp.reactor.ReactorNetty2TcpClient; - -/** - * Integration tests for {@link StompBrokerRelayMessageHandler} running against - * ActiveMQ with {@link ReactorNetty2TcpClient}. - * - * @author Rossen Stoyanchev - */ -@Disabled("gh-29287 :: Disabled because they fail too frequently") -public class ReactorNetty2StompBrokerRelayIntegrationTests extends AbstractStompBrokerRelayIntegrationTests { - - @Override - protected TcpOperations initTcpClient(int port) { - return new ReactorNetty2TcpClient<>("127.0.0.1", port, new StompTcpMessageCodec()); - } - -} diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClientTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClientTests.java index 36648bea3f2..be9bd4d67fa 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClientTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClientTests.java @@ -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,7 +37,6 @@ import org.junit.jupiter.api.TestInfo; import org.springframework.messaging.converter.StringMessageConverter; import org.springframework.messaging.simp.stomp.StompSession.Subscription; -import org.springframework.messaging.tcp.reactor.ReactorNetty2TcpClient; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.util.Assert; @@ -58,8 +57,6 @@ class ReactorNettyTcpStompClientTests { private ReactorNettyTcpStompClient client; - private ReactorNettyTcpStompClient client2; - @BeforeEach void setup(TestInfo testInfo) throws Exception { @@ -84,10 +81,6 @@ class ReactorNettyTcpStompClientTests { this.client = new ReactorNettyTcpStompClient(host, port); this.client.setMessageConverter(new StringMessageConverter()); this.client.setTaskScheduler(taskScheduler); - - this.client2 = new ReactorNettyTcpStompClient(new ReactorNetty2TcpClient<>(host, port, new StompTcpMessageCodec())); - this.client2.setMessageConverter(new StringMessageConverter()); - this.client2.setTaskScheduler(taskScheduler); } private TransportConnector createStompConnector() throws Exception { @@ -100,7 +93,6 @@ class ReactorNettyTcpStompClientTests { void shutdown() throws Exception { try { this.client.shutdown(); - this.client2.shutdown(); } catch (Throwable ex) { logger.error("Failed to shut client", ex); @@ -120,11 +112,6 @@ class ReactorNettyTcpStompClientTests { testPublishSubscribe(this.client); } - @Test - void publishSubscribeOnReactorNetty2() throws Exception { - testPublishSubscribe(this.client2); - } - private void testPublishSubscribe(ReactorNettyTcpStompClient clientToUse) throws Exception { String destination = "/topic/foo"; ConsumingHandler consumingHandler1 = new ConsumingHandler(destination); diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java index 8e563a1f2cd..daab08e7538 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java @@ -31,7 +31,6 @@ import org.springframework.http.client.reactive.HttpComponentsClientHttpConnecto import org.springframework.http.client.reactive.JdkClientHttpConnector; import org.springframework.http.client.reactive.JettyClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.http.client.reactive.ReactorNetty2ClientHttpConnector; import org.springframework.http.codec.ClientCodecConfigurer; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -56,8 +55,6 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { private static final boolean reactorNettyClientPresent; - private static final boolean reactorNetty2ClientPresent; - private static final boolean jettyClientPresent; private static final boolean httpComponentsClientPresent; @@ -67,7 +64,6 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { static { ClassLoader loader = DefaultWebTestClientBuilder.class.getClassLoader(); reactorNettyClientPresent = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", loader); - reactorNetty2ClientPresent = ClassUtils.isPresent("reactor.netty5.http.client.HttpClient", loader); jettyClientPresent = ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient", loader); httpComponentsClientPresent = ClassUtils.isPresent("org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient", loader) && @@ -297,9 +293,6 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { if (reactorNettyClientPresent) { return new ReactorClientHttpConnector(); } - else if (reactorNetty2ClientPresent) { - return new ReactorNetty2ClientHttpConnector(); - } else if (jettyClientPresent) { return new JettyClientHttpConnector(); } diff --git a/spring-web/spring-web.gradle b/spring-web/spring-web.gradle index 61dfa7fd56b..30d45a1ed5f 100644 --- a/spring-web/spring-web.gradle +++ b/spring-web/spring-web.gradle @@ -26,12 +26,7 @@ dependencies { optional("io.netty:netty-handler") optional("io.netty:netty-codec-http") optional("io.netty:netty-transport") - optional("io.netty:netty5-buffer") - optional("io.netty:netty5-handler") - optional("io.netty:netty5-codec-http") - optional("io.netty:netty5-transport") optional("io.projectreactor.netty:reactor-netty-http") - optional("io.projectreactor.netty:reactor-netty5-http") optional("io.reactivex.rxjava3:rxjava") optional("io.undertow:undertow-core") optional("jakarta.el:jakarta.el-api") diff --git a/spring-web/src/jmh/java/org/springframework/http/support/HeadersAdapterBenchmark.java b/spring-web/src/jmh/java/org/springframework/http/support/HeadersAdapterBenchmark.java index 876572d8772..011bad51415 100644 --- a/spring-web/src/jmh/java/org/springframework/http/support/HeadersAdapterBenchmark.java +++ b/spring-web/src/jmh/java/org/springframework/http/support/HeadersAdapterBenchmark.java @@ -40,7 +40,7 @@ import org.springframework.util.MultiValueMap; /** * Benchmark for implementations of MultiValueMap adapters over native HTTP * headers implementations. - *

Run JMH with {@code -p implementation=Netty,Netty5,HttpComponents,Jetty} + *

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); }; diff --git a/spring-web/src/jmh/java/org/springframework/http/support/HeadersAdaptersBaseline.java b/spring-web/src/jmh/java/org/springframework/http/support/HeadersAdaptersBaseline.java index 22a29939932..29c8ae37c0b 100644 --- a/spring-web/src/jmh/java/org/springframework/http/support/HeadersAdaptersBaseline.java +++ b/spring-web/src/jmh/java/org/springframework/http/support/HeadersAdaptersBaseline.java @@ -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 { - - 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 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 String && - StreamSupport.stream(this.headers.spliterator(), false) - .anyMatch(entry -> value.equals(entry.getValue()))); - } - - @Override - public @Nullable 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; - } - - @Override - public @Nullable List put(String key, @Nullable List value) { - List previousValues = get(key); - this.headers.set(key, value); - return previousValues; - } - - @Override - public @Nullable 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 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; - - private @Nullable 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/ReactorNetty2ClientHttpConnector.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpConnector.java deleted file mode 100644 index 6a910ba0552..00000000000 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpConnector.java +++ /dev/null @@ -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}. - * - *

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> ATTRIBUTES_KEY = - AttributeKey.valueOf(ReactorNetty2ClientHttpRequest.class.getName() + ".ATTRIBUTES"); - - private static final Function defaultInitializer = client -> client.compress(true); - - - private final HttpClient httpClient; - - - /** - * Default constructor. Initializes {@link HttpClient} via: - *

-	 * HttpClient.create().compress()
-	 * 
- */ - 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. - *

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 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 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 connect(HttpMethod method, URI uri, - Function> requestCallback) { - - AtomicReference 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); - } - -} 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 deleted file mode 100644 index f8fab77ba67..00000000000 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java +++ /dev/null @@ -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. - * - *

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 getNativeRequest() { - return (T) this.request; - } - - @Override - public Mono writeWith(Publisher body) { - return doCommit(() -> { - // Send as Mono if possible as an optimization hint to Reactor Netty - if (body instanceof Mono) { - Mono bufferMono = Mono.from(body).map(Netty5DataBufferFactory::toBuffer); - return this.outbound.send(bufferMono).then(); - - } - else { - Flux bufferFlux = Flux.from(body).map(Netty5DataBufferFactory::toBuffer); - return this.outbound.send(bufferFlux).then(); - } - }); - } - - @Override - public Mono writeAndFlushWith(Publisher> body) { - Publisher> buffers = Flux.from(body).map(ReactorNetty2ClientHttpRequest::toBuffers); - return doCommit(() -> this.outbound.sendGroups(buffers).then()); - } - - private static Publisher toBuffers(Publisher dataBuffers) { - return Flux.from(dataBuffers).map(Netty5DataBufferFactory::toBuffer); - } - - @Override - public Mono writeWith(Path file, long position, long count) { - return doCommit(() -> this.outbound.sendFile(file, position, count).then()); - } - - @Override - public Mono 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())); - } - -} 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 deleted file mode 100644 index 623561cfa4a..00000000000 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpResponse.java +++ /dev/null @@ -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. - * - *

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 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 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 getCookies() { - MultiValueMap 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() + '}'; - } - -} diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ResourceFactory.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ResourceFactory.java deleted file mode 100644 index 9eb8b723849..00000000000 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ResourceFactory.java +++ /dev/null @@ -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}. - * - *

This factory implements {@link InitializingBean} and {@link DisposableBean} - * and is expected typically to be declared as a Spring-managed bean. - * - *

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 globalResourcesConsumer; - - private Supplier connectionProviderSupplier = () -> ConnectionProvider.create("webflux", 500); - - private @Nullable ConnectionProvider connectionProvider; - - private Supplier 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}. - *

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 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}. - *

By default, {@code ConnectionProvider.elastic("http")} is used. - *

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 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}. - *

By default, {@code LoopResources.create("webflux-http")} is used. - *

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 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. - *

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}. - *

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 - } - } - } - -} diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java index 85896edc310..766e2b3f0a1 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java @@ -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) { diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2HttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2HttpHandlerAdapter.java deleted file mode 100644 index 10c15167392..00000000000 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2HttpHandlerAdapter.java +++ /dev/null @@ -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. - * - *

This class is based on {@link ReactorHttpHandlerAdapter}. - * - * @author Violeta Georgieva - * @since 6.0 - */ -public class ReactorNetty2HttpHandlerAdapter implements BiFunction> { - - 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 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(); - } - } - -} 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 deleted file mode 100644 index 833e67aaa0e..00000000000 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java +++ /dev/null @@ -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}. - * - *

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 initCookies() { - MultiValueMap 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 getBody() { - return this.request.receive().transferOwnership().map(this.bufferFactory::wrap); - } - - @SuppressWarnings("unchecked") - @Override - public 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(); - } - -} 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 deleted file mode 100644 index 3d89eaad7a4..00000000000 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java +++ /dev/null @@ -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}. - * - *

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 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 writeWithInternal(Publisher publisher) { - return this.response.send(toByteBufs(publisher)).then(); - } - - @Override - protected Mono writeAndFlushWithInternal(Publisher> 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 writeWith(Path file, long position, long count) { - return doCommit(() -> this.response.sendFile(file, position, count).then()); - } - - private Publisher toByteBufs(Publisher 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()); - }); - } - } - } - -} diff --git a/spring-web/src/main/java/org/springframework/http/support/Netty5HeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/support/Netty5HeadersAdapter.java deleted file mode 100644 index 8b54f470fa6..00000000000 --- a/spring-web/src/main/java/org/springframework/http/support/Netty5HeadersAdapter.java +++ /dev/null @@ -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 { - - 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 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 = 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 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; - } - - @Override - public @Nullable List put(String key, @Nullable List value) { - List previousValues = get(key); - this.headers.set(key, value); - return previousValues; - } - - @Override - public @Nullable 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; - - private @Nullable 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/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java index 59ee9c5496a..feddf42d628 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java @@ -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> 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> 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> 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> writers = findCodec(clone.getWriters(), MultipartHttpMessageWriter.class).getPartWriters(); - assertThat(writers).hasSize(18); + assertThat(writers).hasSize(17); } @Test @@ -279,7 +274,7 @@ class ClientCodecConfigurerTests { List> writers = findCodec(clone.getWriters(), MultipartHttpMessageWriter.class).getPartWriters(); - assertThat(writers).hasSize(18); + assertThat(writers).hasSize(17); } private Decoder getNextDecoder(List> readers) { diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java index 9d21ac2732d..d0174ae237e 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java @@ -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> 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> 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> 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> 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); diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java index 1915ff3e866..1fc00e56397 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java @@ -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> 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> 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); diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/DefaultServerHttpRequestBuilderTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/DefaultServerHttpRequestBuilderTests.java index f7621a5268b..72f8568f9a9 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/DefaultServerHttpRequestBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/DefaultServerHttpRequestBuilderTests.java @@ -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())), 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 e3d5ebb6c96..92df0df7fcb 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 @@ -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 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(), diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/ReactorNetty2HttpServer.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/ReactorNetty2HttpServer.java deleted file mode 100644 index 3d968b5bfe1..00000000000 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/ReactorNetty2HttpServer.java +++ /dev/null @@ -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 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); - } - -} diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/ReactorNetty2HttpsServer.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/ReactorNetty2HttpsServer.java deleted file mode 100644 index 80cd9e47f63..00000000000 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/ReactorNetty2HttpsServer.java +++ /dev/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 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); - } - -} diff --git a/spring-webflux/spring-webflux.gradle b/spring-webflux/spring-webflux.gradle index 9ba939e787d..a8d0550e9f4 100644 --- a/spring-webflux/spring-webflux.gradle +++ b/spring-webflux/spring-webflux.gradle @@ -14,7 +14,6 @@ dependencies { optional("com.fasterxml.jackson.dataformat:jackson-dataformat-smile") optional("com.google.protobuf:protobuf-java-util") optional("io.projectreactor.netty:reactor-netty-http") - optional("io.projectreactor.netty:reactor-netty5-http") optional("io.undertow:undertow-websockets-jsr") optional("jakarta.servlet:jakarta.servlet-api") optional("jakarta.validation:jakarta.validation-api") @@ -57,7 +56,6 @@ dependencies { testRuntimeOnly("com.sun.xml.bind:jaxb-core") testRuntimeOnly("com.sun.xml.bind:jaxb-impl") testRuntimeOnly("com.sun.activation:jakarta.activation") - testRuntimeOnly("io.netty:netty5-buffer") testRuntimeOnly("org.glassfish:jakarta.el") testRuntimeOnly("org.jruby:jruby") testRuntimeOnly("org.python:jython-standalone") diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java index 6318e3d739f..002e9446815 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java @@ -36,7 +36,6 @@ import org.springframework.http.client.reactive.HttpComponentsClientHttpConnecto import org.springframework.http.client.reactive.JdkClientHttpConnector; import org.springframework.http.client.reactive.JettyClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.http.client.reactive.ReactorNetty2ClientHttpConnector; import org.springframework.http.codec.ClientCodecConfigurer; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -57,8 +56,6 @@ final class DefaultWebClientBuilder implements WebClient.Builder { private static final boolean reactorNettyClientPresent; - private static final boolean reactorNetty2ClientPresent; - private static final boolean jettyClientPresent; private static final boolean httpComponentsClientPresent; @@ -66,7 +63,6 @@ final class DefaultWebClientBuilder implements WebClient.Builder { static { ClassLoader loader = DefaultWebClientBuilder.class.getClassLoader(); reactorNettyClientPresent = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", loader); - reactorNetty2ClientPresent = ClassUtils.isPresent("reactor.netty5.http.client.HttpClient", loader); jettyClientPresent = ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient", loader); httpComponentsClientPresent = ClassUtils.isPresent("org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient", loader) && @@ -311,9 +307,6 @@ final class DefaultWebClientBuilder implements WebClient.Builder { if (reactorNettyClientPresent) { return new ReactorClientHttpConnector(); } - else if (reactorNetty2ClientPresent) { - return new ReactorNetty2ClientHttpConnector(); - } else if (jettyClientPresent) { return new JettyClientHttpConnector(); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/WebSocketMessage.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/WebSocketMessage.java index 00593dab86f..3e5918fd5c5 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/WebSocketMessage.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/WebSocketMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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. @@ -23,9 +23,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.core.io.buffer.Netty5DataBufferFactory; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; /** @@ -39,10 +37,6 @@ import org.springframework.util.ObjectUtils; */ public class WebSocketMessage { - private static final boolean reactorNetty2Present = ClassUtils.isPresent( - "io.netty5.handler.codec.http.websocketx.WebSocketFrame", WebSocketMessage.class.getClassLoader()); - - private final Type type; private final DataBuffer payload; @@ -133,9 +127,6 @@ public class WebSocketMessage { * @see DataBufferUtils#retain(DataBuffer) */ public WebSocketMessage retain() { - if (reactorNetty2Present) { - return ReactorNetty2Helper.retain(this); - } DataBufferUtils.retain(this.payload); return this; } @@ -194,20 +185,4 @@ public class WebSocketMessage { PONG } - - private static class ReactorNetty2Helper { - - static WebSocketMessage retain(WebSocketMessage message) { - if (message.nativeMessage instanceof io.netty5.handler.codec.http.websocketx.WebSocketFrame netty5Frame) { - io.netty5.handler.codec.http.websocketx.WebSocketFrame frame = netty5Frame.send().receive(); - DataBuffer payload = ((Netty5DataBufferFactory) message.payload.factory()).wrap(frame.binaryData()); - return new WebSocketMessage(message.type, payload, frame); - } - else { - DataBufferUtils.retain(message.payload); - return message; - } - } - } - } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/Netty5WebSocketSessionSupport.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/Netty5WebSocketSessionSupport.java deleted file mode 100644 index 3bfc7715baf..00000000000 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/Netty5WebSocketSessionSupport.java +++ /dev/null @@ -1,107 +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.reactive.socket.adapter; - -import java.util.HashMap; -import java.util.Map; - -import io.netty5.buffer.Buffer; -import io.netty5.handler.codec.http.websocketx.BinaryWebSocketFrame; -import io.netty5.handler.codec.http.websocketx.PingWebSocketFrame; -import io.netty5.handler.codec.http.websocketx.PongWebSocketFrame; -import io.netty5.handler.codec.http.websocketx.TextWebSocketFrame; -import io.netty5.handler.codec.http.websocketx.WebSocketFrame; - -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.Netty5DataBufferFactory; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; -import org.springframework.web.reactive.socket.HandshakeInfo; -import org.springframework.web.reactive.socket.WebSocketMessage; -import org.springframework.web.reactive.socket.WebSocketSession; - -/** - * Base class for Netty-based {@link WebSocketSession} adapters that provides - * convenience methods to convert Netty {@link WebSocketFrame WebSocketFrames} to and from - * {@link WebSocketMessage WebSocketMessages}. - * - *

This class is based on {@link NettyWebSocketSessionSupport}. - * - * @author Violeta Georgieva - * @since 6.0 - * @param the native delegate type - */ -public abstract class Netty5WebSocketSessionSupport extends AbstractWebSocketSession { - - /** - * The default max size for inbound WebSocket frames. - */ - public static final int DEFAULT_FRAME_MAX_SIZE = 64 * 1024; - - - private static final Map, WebSocketMessage.Type> messageTypes; - - static { - messageTypes = new HashMap<>(8); - messageTypes.put(TextWebSocketFrame.class, WebSocketMessage.Type.TEXT); - messageTypes.put(BinaryWebSocketFrame.class, WebSocketMessage.Type.BINARY); - messageTypes.put(PingWebSocketFrame.class, WebSocketMessage.Type.PING); - messageTypes.put(PongWebSocketFrame.class, WebSocketMessage.Type.PONG); - } - - - protected Netty5WebSocketSessionSupport(T delegate, HandshakeInfo info, Netty5DataBufferFactory factory) { - super(delegate, ObjectUtils.getIdentityHexString(delegate), info, factory); - } - - - @Override - public Netty5DataBufferFactory bufferFactory() { - return (Netty5DataBufferFactory) super.bufferFactory(); - } - - - protected WebSocketMessage toMessage(WebSocketFrame frame) { - DataBuffer payload = bufferFactory().wrap(frame.binaryData()); - WebSocketMessage.Type messageType = messageTypes.get(frame.getClass()); - Assert.state(messageType != null, "Unexpected message type"); - return new WebSocketMessage(messageType, payload, frame); - } - - protected WebSocketFrame toFrame(WebSocketMessage message) { - if (message.getNativeMessage() != null) { - return message.getNativeMessage(); - } - Buffer buffer = Netty5DataBufferFactory.toBuffer(message.getPayload()); - if (WebSocketMessage.Type.TEXT.equals(message.getType())) { - return new TextWebSocketFrame(buffer); - } - else if (WebSocketMessage.Type.BINARY.equals(message.getType())) { - return new BinaryWebSocketFrame(buffer); - } - else if (WebSocketMessage.Type.PING.equals(message.getType())) { - return new PingWebSocketFrame(buffer); - } - else if (WebSocketMessage.Type.PONG.equals(message.getType())) { - return new PongWebSocketFrame(buffer); - } - else { - throw new IllegalArgumentException("Unexpected message type: " + message.getType()); - } - } - -} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/ReactorNetty2WebSocketSession.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/ReactorNetty2WebSocketSession.java deleted file mode 100644 index bf0fc539f05..00000000000 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/ReactorNetty2WebSocketSession.java +++ /dev/null @@ -1,173 +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.reactive.socket.adapter; - -import java.util.function.Consumer; - -import io.netty5.channel.ChannelId; -import io.netty5.handler.codec.http.websocketx.WebSocketFrame; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.netty5.Connection; -import reactor.netty5.NettyInbound; -import reactor.netty5.NettyOutbound; -import reactor.netty5.channel.ChannelOperations; -import reactor.netty5.http.websocket.WebsocketInbound; -import reactor.netty5.http.websocket.WebsocketOutbound; - -import org.springframework.core.io.buffer.Netty5DataBufferFactory; -import org.springframework.web.reactive.socket.CloseStatus; -import org.springframework.web.reactive.socket.HandshakeInfo; -import org.springframework.web.reactive.socket.WebSocketMessage; -import org.springframework.web.reactive.socket.WebSocketSession; - -/** - * {@link WebSocketSession} implementation for use with the Reactor Netty's (Netty 5) - * {@link NettyInbound} and {@link NettyOutbound}. - * This class is based on {@link ReactorNettyWebSocketSession}. - * - * @author Violeta Georgieva - * @since 6.0 - */ -public class ReactorNetty2WebSocketSession - extends Netty5WebSocketSessionSupport { - - private final int maxFramePayloadLength; - - private final ChannelId channelId; - - - /** - * Constructor for the session, using the {@link #DEFAULT_FRAME_MAX_SIZE} value. - */ - public ReactorNetty2WebSocketSession(WebsocketInbound inbound, WebsocketOutbound outbound, - HandshakeInfo info, Netty5DataBufferFactory bufferFactory) { - - this(inbound, outbound, info, bufferFactory, DEFAULT_FRAME_MAX_SIZE); - } - - /** - * Constructor with an additional maxFramePayloadLength argument. - * @since 5.1 - */ - @SuppressWarnings("rawtypes") - public ReactorNetty2WebSocketSession(WebsocketInbound inbound, WebsocketOutbound outbound, - HandshakeInfo info, Netty5DataBufferFactory bufferFactory, - int maxFramePayloadLength) { - - super(new WebSocketConnection(inbound, outbound), info, bufferFactory); - this.maxFramePayloadLength = maxFramePayloadLength; - this.channelId = ((ChannelOperations) inbound).channel().id(); - } - - - /** - * Return the id of the underlying Netty channel. - * @since 5.3.4 - */ - public ChannelId getChannelId() { - return this.channelId; - } - - - @Override - public Flux receive() { - return getDelegate().getInbound() - .aggregateFrames(this.maxFramePayloadLength) - .receiveFrames() - .map(super::toMessage) - .doOnNext(message -> { - if (logger.isTraceEnabled()) { - logger.trace(getLogPrefix() + "Received " + message); - } - }); - } - - @Override - public Mono send(Publisher messages) { - Flux frames = Flux.from(messages) - .doOnNext(message -> { - if (logger.isTraceEnabled()) { - logger.trace(getLogPrefix() + "Sending " + message); - } - }) - .map(this::toFrame); - return getDelegate().getOutbound() - .sendObject(frames) - .then(); - } - - @Override - public boolean isOpen() { - DisposedCallback callback = new DisposedCallback(); - getDelegate().getInbound().withConnection(callback); - return !callback.isDisposed(); - } - - @Override - public Mono close(CloseStatus status) { - // this will notify WebSocketInbound.receiveCloseStatus() - return getDelegate().getOutbound().sendClose(status.getCode(), status.getReason()); - } - - @Override - public Mono closeStatus() { - return getDelegate().getInbound().receiveCloseStatus() - .map(status -> CloseStatus.create(status.code(), status.reasonText())); - } - - /** - * Simple container for {@link NettyInbound} and {@link NettyOutbound}. - */ - public static class WebSocketConnection { - - private final WebsocketInbound inbound; - - private final WebsocketOutbound outbound; - - - public WebSocketConnection(WebsocketInbound inbound, WebsocketOutbound outbound) { - this.inbound = inbound; - this.outbound = outbound; - } - - public WebsocketInbound getInbound() { - return this.inbound; - } - - public WebsocketOutbound getOutbound() { - return this.outbound; - } - } - - - private static class DisposedCallback implements Consumer { - - private boolean disposed; - - public boolean isDisposed() { - return this.disposed; - } - - @Override - public void accept(Connection connection) { - this.disposed = connection.isDisposed(); - } - } - -} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/ReactorNetty2WebSocketClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/ReactorNetty2WebSocketClient.java deleted file mode 100644 index ff3df926b7b..00000000000 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/ReactorNetty2WebSocketClient.java +++ /dev/null @@ -1,159 +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.reactive.socket.client; - -import java.net.URI; -import java.util.function.Supplier; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; -import reactor.core.publisher.Mono; -import reactor.netty5.http.client.HttpClient; -import reactor.netty5.http.client.WebsocketClientSpec; -import reactor.netty5.http.websocket.WebsocketInbound; - -import org.springframework.core.io.buffer.Netty5DataBufferFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.reactive.socket.HandshakeInfo; -import org.springframework.web.reactive.socket.WebSocketHandler; -import org.springframework.web.reactive.socket.WebSocketSession; -import org.springframework.web.reactive.socket.adapter.ReactorNetty2WebSocketSession; - -/** - * {@link WebSocketClient} implementation for use with Reactor Netty for Netty 5. - * - *

This class is based on {@link ReactorNettyWebSocketClient}. - * - * @author Violeta Georgieva - * @since 6.0 - */ -public class ReactorNetty2WebSocketClient implements WebSocketClient { - - private static final Log logger = LogFactory.getLog(ReactorNetty2WebSocketClient.class); - - - private final HttpClient httpClient; - - private final Supplier specBuilderSupplier; - - private @Nullable Boolean handlePing; - - - /** - * Default constructor. - */ - public ReactorNetty2WebSocketClient() { - this(HttpClient.create()); - } - - /** - * Constructor that accepts an existing {@link HttpClient} builder - * with a default {@link WebsocketClientSpec.Builder}. - * @since 5.1 - */ - public ReactorNetty2WebSocketClient(HttpClient httpClient) { - this(httpClient, WebsocketClientSpec.builder()); - } - - /** - * Constructor that accepts an existing {@link HttpClient} builder - * and a pre-configured {@link WebsocketClientSpec.Builder}. - */ - public ReactorNetty2WebSocketClient( - HttpClient httpClient, Supplier builderSupplier) { - - Assert.notNull(httpClient, "HttpClient is required"); - Assert.notNull(builderSupplier, "WebsocketClientSpec.Builder is required"); - this.httpClient = httpClient; - this.specBuilderSupplier = builderSupplier; - } - - - /** - * Return the configured {@link HttpClient}. - */ - public HttpClient getHttpClient() { - return this.httpClient; - } - - /** - * Build an instance of {@code WebsocketClientSpec} that reflects the current - * configuration. This can be used to check the configured parameters except - * for sub-protocols which depend on the {@link WebSocketHandler} that is used - * for a given upgrade. - */ - public WebsocketClientSpec getWebsocketClientSpec() { - return buildSpec(null); - } - - private WebsocketClientSpec buildSpec(@Nullable String protocols) { - WebsocketClientSpec.Builder builder = this.specBuilderSupplier.get(); - if (StringUtils.hasText(protocols)) { - builder.protocols(protocols); - } - return builder.build(); - } - - - @Override - public Mono execute(URI url, WebSocketHandler handler) { - return execute(url, new HttpHeaders(), handler); - } - - @Override - public Mono execute(URI url, HttpHeaders requestHeaders, WebSocketHandler handler) { - String protocols = StringUtils.collectionToCommaDelimitedString(handler.getSubProtocols()); - WebsocketClientSpec clientSpec = buildSpec(protocols); - return getHttpClient() - .headers(nettyHeaders -> setNettyHeaders(requestHeaders, nettyHeaders)) - .websocket(clientSpec) - .uri(url.toString()) - .handle((inbound, outbound) -> { - HttpHeaders responseHeaders = toHttpHeaders(inbound); - String protocol = responseHeaders.getFirst("Sec-WebSocket-Protocol"); - HandshakeInfo info = new HandshakeInfo(url, responseHeaders, Mono.empty(), protocol); - Netty5DataBufferFactory factory = new Netty5DataBufferFactory(outbound.alloc()); - WebSocketSession session = new ReactorNetty2WebSocketSession( - inbound, outbound, info, factory, clientSpec.maxFramePayloadLength()); - if (logger.isDebugEnabled()) { - logger.debug("Started session '" + session.getId() + "' for " + url); - } - return handler.handle(session).checkpoint(url + " [ReactorNetty2WebSocketClient]"); - }) - .doOnRequest(n -> { - if (logger.isDebugEnabled()) { - logger.debug("Connecting to " + url); - } - }) - .next(); - } - - private void setNettyHeaders(HttpHeaders httpHeaders, io.netty5.handler.codec.http.headers.HttpHeaders nettyHeaders) { - httpHeaders.forEach(nettyHeaders::set); - } - - private HttpHeaders toHttpHeaders(WebsocketInbound inbound) { - HttpHeaders headers = new HttpHeaders(); - inbound.headers().iterator().forEachRemaining(entry -> - headers.add(entry.getKey().toString(), entry.getValue().toString())); - return headers; - } - -} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/HandshakeWebSocketService.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/HandshakeWebSocketService.java index f8fda8bb8b2..940a2576ce3 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/HandshakeWebSocketService.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/HandshakeWebSocketService.java @@ -46,7 +46,6 @@ import org.springframework.web.reactive.socket.server.RequestUpgradeStrategy; import org.springframework.web.reactive.socket.server.WebSocketService; import org.springframework.web.reactive.socket.server.upgrade.JettyCoreRequestUpgradeStrategy; import org.springframework.web.reactive.socket.server.upgrade.JettyRequestUpgradeStrategy; -import org.springframework.web.reactive.socket.server.upgrade.ReactorNetty2RequestUpgradeStrategy; import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy; import org.springframework.web.reactive.socket.server.upgrade.StandardWebSocketUpgradeStrategy; import org.springframework.web.reactive.socket.server.upgrade.UndertowRequestUpgradeStrategy; @@ -84,8 +83,6 @@ public class HandshakeWebSocketService implements WebSocketService, Lifecycle { private static final boolean reactorNettyPresent; - private static final boolean reactorNetty2Present; - static { ClassLoader classLoader = HandshakeWebSocketService.class.getClassLoader(); jettyWsPresent = ClassUtils.isPresent( @@ -96,8 +93,6 @@ public class HandshakeWebSocketService implements WebSocketService, Lifecycle { "io.undertow.websockets.WebSocketProtocolHandshakeHandler", classLoader); reactorNettyPresent = ClassUtils.isPresent( "reactor.netty.http.server.HttpServerResponse", classLoader); - reactorNetty2Present = ClassUtils.isPresent( - "reactor.netty5.http.server.HttpServerResponse", classLoader); } @@ -285,12 +280,7 @@ public class HandshakeWebSocketService implements WebSocketService, Lifecycle { return new UndertowRequestUpgradeStrategy(); } else if (reactorNettyPresent) { - // As late as possible (Reactor Netty commonly used for WebClient) - return ReactorNettyStrategyDelegate.forReactorNetty1(); - } - else if (reactorNetty2Present) { - // As late as possible (Reactor Netty commonly used for WebClient) - return ReactorNettyStrategyDelegate.forReactorNetty2(); + return new ReactorNettyRequestUpgradeStrategy(); } else { // Let's assume Jakarta WebSocket API 2.1+ @@ -298,19 +288,4 @@ public class HandshakeWebSocketService implements WebSocketService, Lifecycle { } } - - /** - * Inner class to avoid a reachable dependency on Reactor Netty API. - */ - private static class ReactorNettyStrategyDelegate { - - public static RequestUpgradeStrategy forReactorNetty1() { - return new ReactorNettyRequestUpgradeStrategy(); - } - - public static RequestUpgradeStrategy forReactorNetty2() { - return new ReactorNetty2RequestUpgradeStrategy(); - } - } - } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNetty2RequestUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNetty2RequestUpgradeStrategy.java deleted file mode 100644 index c9eb17d4bb0..00000000000 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNetty2RequestUpgradeStrategy.java +++ /dev/null @@ -1,113 +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.web.reactive.socket.server.upgrade; - -import java.net.URI; -import java.util.function.Supplier; - -import org.jspecify.annotations.Nullable; -import reactor.core.publisher.Mono; -import reactor.netty5.http.server.HttpServerResponse; -import reactor.netty5.http.server.WebsocketServerSpec; - -import org.springframework.core.io.buffer.Netty5DataBufferFactory; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.http.server.reactive.ServerHttpResponseDecorator; -import org.springframework.util.Assert; -import org.springframework.web.reactive.socket.HandshakeInfo; -import org.springframework.web.reactive.socket.WebSocketHandler; -import org.springframework.web.reactive.socket.adapter.ReactorNetty2WebSocketSession; -import org.springframework.web.reactive.socket.server.RequestUpgradeStrategy; -import org.springframework.web.server.ServerWebExchange; - -/** - * A WebSocket {@code RequestUpgradeStrategy} for Reactor Netty for Netty 5. - * - *

This class is based on {@link ReactorNettyRequestUpgradeStrategy}. - * - * @author Violeta Georgieva - * @since 6.0 - */ -public class ReactorNetty2RequestUpgradeStrategy implements RequestUpgradeStrategy { - - private final Supplier specBuilderSupplier; - - - /** - * Create an instances with a default {@link WebsocketServerSpec.Builder}. - * @since 5.2.6 - */ - public ReactorNetty2RequestUpgradeStrategy() { - this(WebsocketServerSpec::builder); - } - - - /** - * Create an instance with a pre-configured {@link WebsocketServerSpec.Builder} - * to use for WebSocket upgrades. - * @since 5.2.6 - */ - public ReactorNetty2RequestUpgradeStrategy(Supplier builderSupplier) { - Assert.notNull(builderSupplier, "WebsocketServerSpec.Builder is required"); - this.specBuilderSupplier = builderSupplier; - } - - - /** - * Build an instance of {@code WebsocketServerSpec} that reflects the current - * configuration. This can be used to check the configured parameters except - * for sub-protocols which depend on the {@link WebSocketHandler} that is used - * for a given upgrade. - * @since 5.2.6 - */ - public WebsocketServerSpec getWebsocketServerSpec() { - return buildSpec(null); - } - - WebsocketServerSpec buildSpec(@Nullable String subProtocol) { - WebsocketServerSpec.Builder builder = this.specBuilderSupplier.get(); - if (subProtocol != null) { - builder.protocols(subProtocol); - } - return builder.build(); - } - - - @Override - public Mono upgrade(ServerWebExchange exchange, WebSocketHandler handler, - @Nullable String subProtocol, Supplier handshakeInfoFactory) { - - ServerHttpResponse response = exchange.getResponse(); - HttpServerResponse reactorResponse = ServerHttpResponseDecorator.getNativeResponse(response); - HandshakeInfo handshakeInfo = handshakeInfoFactory.get(); - Netty5DataBufferFactory bufferFactory = (Netty5DataBufferFactory) response.bufferFactory(); - URI uri = exchange.getRequest().getURI(); - - // Trigger WebFlux preCommit actions and upgrade - return response.setComplete() - .then(Mono.defer(() -> { - WebsocketServerSpec spec = buildSpec(subProtocol); - return reactorResponse.sendWebsocket((in, out) -> { - ReactorNetty2WebSocketSession session = - new ReactorNetty2WebSocketSession( - in, out, handshakeInfo, bufferFactory, spec.maxFramePayloadLength()); - return handler.handle(session).checkpoint(uri + " [ReactorNetty2RequestUpgradeStrategy]"); - }, spec); - })); - } - -} diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java index 3262f36c03d..63488519712 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java @@ -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. @@ -116,7 +116,7 @@ public class DelegatingWebFluxConfigurationTests { boolean condition = initializer.getValidator() instanceof LocalValidatorFactoryBean; assertThat(condition).isTrue(); assertThat(initializer.getConversionService()).isSameAs(formatterRegistry.getValue()); - assertThat(codecsConfigurer.getValue().getReaders()).hasSize(17); + assertThat(codecsConfigurer.getValue().getReaders()).hasSize(16); } @Test diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java index 411d955c3c3..e6662045afd 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java @@ -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. @@ -114,7 +114,7 @@ class WebFluxConfigurationSupportTests { assertThat(adapter).isNotNull(); List> readers = adapter.getMessageReaders(); - assertThat(readers).hasSize(17); + assertThat(readers).hasSize(16); ResolvableType multiValueMapType = forClassWithGenerics(MultiValueMap.class, String.class, String.class); @@ -169,7 +169,7 @@ class WebFluxConfigurationSupportTests { assertThat(handler.getOrder()).isEqualTo(0); List> writers = handler.getMessageWriters(); - assertThat(writers).hasSize(17); + assertThat(writers).hasSize(16); assertHasMessageWriter(writers, forClass(byte[].class), APPLICATION_OCTET_STREAM); assertHasMessageWriter(writers, forClass(ByteBuffer.class), APPLICATION_OCTET_STREAM); @@ -197,7 +197,7 @@ class WebFluxConfigurationSupportTests { assertThat(handler.getOrder()).isEqualTo(100); List> writers = handler.getMessageWriters(); - assertThat(writers).hasSize(17); + assertThat(writers).hasSize(16); assertHasMessageWriter(writers, forClass(byte[].class), APPLICATION_OCTET_STREAM); assertHasMessageWriter(writers, forClass(ByteBuffer.class), APPLICATION_OCTET_STREAM); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java index 1590ff68287..b86f350c2a3 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java @@ -77,7 +77,6 @@ import org.springframework.http.client.reactive.HttpComponentsClientHttpConnecto import org.springframework.http.client.reactive.JdkClientHttpConnector; import org.springframework.http.client.reactive.JettyClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.http.client.reactive.ReactorNetty2ClientHttpConnector; import org.springframework.web.reactive.function.BodyExtractors; import org.springframework.web.reactive.function.client.WebClient.ResponseSpec; import org.springframework.web.testfixture.xml.Pojo; @@ -107,7 +106,6 @@ class WebClientIntegrationTests { static Stream arguments() { return Stream.of( argumentSet("Reactor Netty", new ReactorClientHttpConnector()), - argumentSet("Reactor Netty 2", new ReactorNetty2ClientHttpConnector()), argumentSet("JDK", new JdkClientHttpConnector()), argumentSet("Jetty", new JettyClientHttpConnector()), argumentSet("HttpComponents", new HttpComponentsClientHttpConnector()) @@ -204,9 +202,6 @@ class WebClientIntegrationTests { if (clientHttpRequest instanceof ChannelOperations nettyReq) { nativeRequest.set(nettyReq.channel().attr(ReactorClientHttpConnector.ATTRIBUTES_KEY)); } - else if (clientHttpRequest instanceof reactor.netty5.channel.ChannelOperations nettyReq) { - nativeRequest.set(nettyReq.channel().attr(ReactorNetty2ClientHttpConnector.ATTRIBUTES_KEY)); - } else { nativeRequest.set(clientHttpRequest.getNativeRequest()); } @@ -222,13 +217,6 @@ class WebClientIntegrationTests { assertThat(attributes.get()).isNotNull(); assertThat(attributes.get()).containsEntry("foo", "bar"); } - else if (nativeRequest.get() instanceof io.netty5.util.Attribute) { - @SuppressWarnings("unchecked") - io.netty5.util.Attribute> attributes = - (io.netty5.util.Attribute>) nativeRequest.get(); - assertThat(attributes.get()).isNotNull(); - assertThat(attributes.get()).containsEntry("foo", "bar"); - } else if (nativeRequest.get() instanceof Request nativeReq) { assertThat(nativeReq.getAttributes()).containsEntry("foo", "bar"); } @@ -946,11 +934,6 @@ class WebClientIntegrationTests { @ParameterizedWebClientTest void statusHandlerSuppressedErrorSignalWithFlux(ClientHttpConnector connector) { - // Temporarily disabled, leads to io.netty5.buffer.BufferClosedException - if (connector instanceof ReactorNetty2ClientHttpConnector) { - return; - } - startServer(connector); prepareResponse(response -> response.setResponseCode(500) diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractReactiveWebSocketIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractReactiveWebSocketIntegrationTests.java index b3388954bd6..7e781d77f23 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractReactiveWebSocketIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractReactiveWebSocketIntegrationTests.java @@ -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. @@ -58,7 +58,6 @@ import org.springframework.web.reactive.socket.server.support.HandshakeWebSocket import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter; import org.springframework.web.reactive.socket.server.upgrade.JettyCoreRequestUpgradeStrategy; import org.springframework.web.reactive.socket.server.upgrade.JettyRequestUpgradeStrategy; -import org.springframework.web.reactive.socket.server.upgrade.ReactorNetty2RequestUpgradeStrategy; import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy; import org.springframework.web.reactive.socket.server.upgrade.StandardWebSocketUpgradeStrategy; import org.springframework.web.reactive.socket.server.upgrade.UndertowRequestUpgradeStrategy; @@ -244,16 +243,6 @@ abstract class AbstractReactiveWebSocketIntegrationTests { } - @Configuration - static class ReactorNetty2Config extends AbstractHandlerAdapterConfig { - - @Override - protected RequestUpgradeStrategy getUpgradeStrategy() { - return new ReactorNetty2RequestUpgradeStrategy(); - } - } - - @Configuration static class UndertowConfig extends AbstractHandlerAdapterConfig {