GH-3410: Add UDP SocketCustomizer

Resolves https://github.com/spring-projects/spring-integration/issues/3410

**cherry-pick to 5.3.x**

* Doc polishing.
This commit is contained in:
Gary Russell
2020-10-22 13:32:56 -04:00
committed by GitHub
parent b94c20a0ce
commit a79be71ed2
15 changed files with 192 additions and 45 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -133,6 +133,8 @@ public abstract class IpAdapterParserUtils {
public static final String CONNECT_TIMEOUT = "connect-timeout";
public static final String UDP_SOCKET_CUSTOMIZER = "socket-customizer";
private IpAdapterParserUtils() {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,6 +51,8 @@ public class UdpInboundChannelAdapterParser extends AbstractChannelAdapterParser
IpAdapterParserUtils.TASK_EXECUTOR);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
IpAdapterParserUtils.LOOKUP_HOST);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element,
IpAdapterParserUtils.UDP_SOCKET_CUSTOMIZER);
return builder.getBeanDefinition();
}

View File

@@ -36,6 +36,7 @@ import org.springframework.util.StringUtils;
*/
public class UdpOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
@Override
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = parseUdp(element, parserContext);
IpAdapterParserUtils.addCommonSocketOptions(builder, element);
@@ -86,6 +87,8 @@ public class UdpOutboundChannelAdapterParser extends AbstractOutboundChannelAdap
IpAdapterParserUtils.TASK_EXECUTOR);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
"socket-expression", "socketExpressionString");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element,
IpAdapterParserUtils.UDP_SOCKET_CUSTOMIZER);
return builder;
}

View File

@@ -21,6 +21,7 @@ import java.util.function.Function;
import org.springframework.integration.dsl.MessageHandlerSpec;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.ip.udp.SocketCustomizer;
import org.springframework.integration.ip.udp.UnicastSendingMessageHandler;
import org.springframework.messaging.Message;
@@ -133,4 +134,15 @@ public abstract class AbstractUdpOutboundChannelAdapterSpec<S extends AbstractUd
return _this();
}
/**
* Configure the socket.
* @param customizer the customizer.
* @return the spec.
* @since 5.3.3
*/
public S configureSocket(SocketCustomizer customizer) {
this.target.setSocketCustomizer(customizer);
return _this();
}
}

View File

@@ -21,6 +21,7 @@ import java.util.concurrent.Executor;
import org.springframework.integration.dsl.MessageProducerSpec;
import org.springframework.integration.ip.udp.MulticastReceivingChannelAdapter;
import org.springframework.integration.ip.udp.SocketCustomizer;
import org.springframework.integration.ip.udp.UnicastReceivingChannelAdapter;
import org.springframework.scheduling.TaskScheduler;
@@ -50,7 +51,7 @@ public class UdpInboundChannelAdapterSpec
*/
public UdpInboundChannelAdapterSpec soTimeout(int soTimeout) {
this.target.setSoTimeout(soTimeout);
return _this();
return this;
}
/**
@@ -60,7 +61,7 @@ public class UdpInboundChannelAdapterSpec
*/
public UdpInboundChannelAdapterSpec taskScheduler(TaskScheduler taskScheduler) {
this.target.setTaskScheduler(taskScheduler);
return _this();
return this;
}
/**
@@ -70,7 +71,7 @@ public class UdpInboundChannelAdapterSpec
*/
public UdpInboundChannelAdapterSpec soReceiveBufferSize(int soReceiveBufferSize) {
this.target.setSoReceiveBufferSize(soReceiveBufferSize);
return _this();
return this;
}
/**
@@ -80,7 +81,7 @@ public class UdpInboundChannelAdapterSpec
*/
public UdpInboundChannelAdapterSpec receiveBufferSize(int receiveBufferSize) {
this.target.setReceiveBufferSize(receiveBufferSize);
return _this();
return this;
}
/**
@@ -90,7 +91,7 @@ public class UdpInboundChannelAdapterSpec
*/
public UdpInboundChannelAdapterSpec lengthCheck(boolean lengthCheck) {
this.target.setLengthCheck(lengthCheck);
return _this();
return this;
}
/**
@@ -100,7 +101,7 @@ public class UdpInboundChannelAdapterSpec
*/
public UdpInboundChannelAdapterSpec localAddress(String localAddress) {
this.target.setLocalAddress(localAddress);
return _this();
return this;
}
/**
@@ -110,7 +111,7 @@ public class UdpInboundChannelAdapterSpec
*/
public UdpInboundChannelAdapterSpec poolSize(int poolSize) {
this.target.setPoolSize(poolSize);
return _this();
return this;
}
/**
@@ -120,7 +121,7 @@ public class UdpInboundChannelAdapterSpec
*/
public UdpInboundChannelAdapterSpec taskExecutor(Executor taskExecutor) {
this.target.setTaskExecutor(taskExecutor);
return _this();
return this;
}
/**
@@ -130,7 +131,7 @@ public class UdpInboundChannelAdapterSpec
*/
public UdpInboundChannelAdapterSpec socket(DatagramSocket socket) {
this.target.setSocket(socket);
return _this();
return this;
}
/**
@@ -140,7 +141,7 @@ public class UdpInboundChannelAdapterSpec
*/
public UdpInboundChannelAdapterSpec soSendBufferSize(int soSendBufferSize) {
this.target.setSoSendBufferSize(soSendBufferSize);
return _this();
return this;
}
/**
@@ -150,7 +151,18 @@ public class UdpInboundChannelAdapterSpec
*/
public UdpInboundChannelAdapterSpec lookupHost(boolean lookupHost) {
this.target.setLookupHost(lookupHost);
return _this();
return this;
}
/**
* Configure the socket.
* @param customizer the customizer.
* @return the spec.
* @since 5.3.3
*/
public UdpInboundChannelAdapterSpec configureSocket(SocketCustomizer customizer) {
this.target.setSocketCustomizer(customizer);
return this;
}
}

View File

@@ -1,4 +1,5 @@
/**
* Provides TCP/UDP Component support for the Java DSL.
*/
@org.springframework.lang.NonNullApi
package org.springframework.integration.ip.dsl;

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.ip.udp;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* Configures a socket.
*
* @author Gary Russell
* @since 5.3.3
*/
@FunctionalInterface
public interface SocketCustomizer {
/**
* Configure the socket ({code setTrafficClass()}, etc).
* @param socket the socket.
* @throws SocketException a socket exception.
*/
void configure(DatagramSocket socket) throws SocketException;
}

View File

@@ -35,6 +35,7 @@ import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
/**
* A channel adapter to receive incoming UDP packets. Packets can optionally be preceded by a
@@ -58,6 +59,7 @@ public class UnicastReceivingChannelAdapter extends AbstractInternetProtocolRece
private int soSendBufferSize = -1;
private SocketCustomizer socketCustomizer = socket -> { };
/**
* Constructs a UnicastReceivingChannelAdapter that listens on the specified port.
@@ -89,6 +91,16 @@ public class UnicastReceivingChannelAdapter extends AbstractInternetProtocolRece
this.mapper.setLengthCheck(lengthCheck);
}
/**
* Set a customizer to further configure the socket after creation.
* @param socketCustomizer the customizer.
* @since 5.3.3
*/
public void setSocketCustomizer(SocketCustomizer socketCustomizer) {
Assert.notNull(socketCustomizer, "'socketCustomizer' cannot be null");
this.socketCustomizer = socketCustomizer;
}
@Override
public boolean isLongLived() {
return true;
@@ -169,6 +181,7 @@ public class UnicastReceivingChannelAdapter extends AbstractInternetProtocolRece
if (this.soSendBufferSize > 0) {
out.setSendBufferSize(this.soSendBufferSize);
}
this.socketCustomizer.configure(out);
out.send(ackPack);
out.close();
}
@@ -258,7 +271,7 @@ public class UnicastReceivingChannelAdapter extends AbstractInternetProtocolRece
}
/**
* Sets timeout and receive buffer size
* Sets timeout and receive buffer size; calls the socket customizer.
*
* @param socket The socket.
* @throws SocketException Any socket exception.
@@ -269,6 +282,7 @@ public class UnicastReceivingChannelAdapter extends AbstractInternetProtocolRece
if (soReceiveBufferSize > 0) {
socket.setReceiveBufferSize(soReceiveBufferSize);
}
this.socketCustomizer.configure(socket);
}
@Override

View File

@@ -101,6 +101,8 @@ public class UnicastSendingMessageHandler extends
private EvaluationContext evaluationContext;
private SocketCustomizer socketCustomizer = socket -> { };
private volatile CountDownLatch ackLatch;
private volatile boolean ackThreadRunning;
@@ -206,6 +208,25 @@ public class UnicastSendingMessageHandler extends
ackTimeout);
}
/**
* @param lengthCheck if true, a four byte binary length header is added to the
* packet, allowing the receiver to check for data truncation.
* @since 5.0
*/
public void setLengthCheck(boolean lengthCheck) {
this.mapper.setLengthCheck(lengthCheck);
}
/**
* Set a customizer to further configure the socket after creation.
* @param socketCustomizer the customizer.
* @since 5.3.3
*/
public void setSocketCustomizer(SocketCustomizer socketCustomizer) {
Assert.notNull(socketCustomizer, "'socketCustomizer' cannot be null");
this.socketCustomizer = socketCustomizer;
}
protected final void setReliabilityAttributes(boolean lengthCheck,
boolean acknowledge, String ackHost, int ackPort, int ackTimeout) {
@@ -224,15 +245,6 @@ public class UnicastSendingMessageHandler extends
}
}
/**
* @param lengthCheck if true, a four byte binary length header is added to the
* packet, allowing the receiver to check for data truncation.
* @since 5.0
*/
public void setLengthCheck(boolean lengthCheck) {
this.mapper.setLengthCheck(lengthCheck);
}
@Override
public void doStart() {
if (this.acknowledge) {
@@ -495,6 +507,7 @@ public class UnicastSendingMessageHandler extends
if (soSendBufferSize > 0) {
socket.setSendBufferSize(soSendBufferSize);
}
this.socketCustomizer.configure(socket);
}
/**

View File

@@ -873,6 +873,18 @@
<xsd:extension base="common-attributes">
<xsd:attribute name="check-length" type="xsd:string"/>
<xsd:attribute name="multicast" type="xsd:string"/>
<xsd:attribute name="socket-customizer" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A SocketCustomizer bean.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.ip.udp.SocketCustomizer"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

View File

@@ -19,6 +19,8 @@
<task:executor id="externalTE" pool-size="10"/>
<bean id="udpCust" class = "org.springframework.integration.ip.config.ParserUnitTests$SocketCust"/>
<ip:udp-inbound-channel-adapter id="testInUdp"
channel="udpChannel"
check-length="true"
@@ -35,6 +37,7 @@
lookup-host="false"
auto-startup="false"
phase="1234"
socket-customizer="udpCust"
/>
<ip:udp-inbound-channel-adapter id="testInUdpMulticast"
@@ -141,6 +144,7 @@
local-address="127.0.0.1"
task-executor="externalTE"
order="23"
socket-customizer="udpCust"
/>
<ip:udp-outbound-channel-adapter id="testOutUdpiMulticast"

View File

@@ -20,6 +20,8 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.time.Duration;
import java.util.Iterator;
import java.util.Set;
@@ -66,6 +68,7 @@ import org.springframework.integration.ip.tcp.connection.TcpSocketSupport;
import org.springframework.integration.ip.udp.DatagramPacketMessageMapper;
import org.springframework.integration.ip.udp.MulticastReceivingChannelAdapter;
import org.springframework.integration.ip.udp.MulticastSendingMessageHandler;
import org.springframework.integration.ip.udp.SocketCustomizer;
import org.springframework.integration.ip.udp.UnicastReceivingChannelAdapter;
import org.springframework.integration.ip.udp.UnicastSendingMessageHandler;
import org.springframework.integration.test.util.TestUtils;
@@ -273,6 +276,9 @@ public class ParserUnitTests {
@Autowired
TcpMessageMapper mapper;
@Autowired
SocketCust socketCustomizer;
private static CountDownLatch adviceCalled = new CountDownLatch(1);
@Test
@@ -293,6 +299,7 @@ public class ParserUnitTests {
assertThat((Boolean) mapperAccessor.getPropertyValue("lookupHost")).isFalse();
assertThat(TestUtils.getPropertyValue(udpIn, "autoStartup", Boolean.class)).isFalse();
assertThat(dfa.getPropertyValue("phase")).isEqualTo(1234);
assertThat(dfa.getPropertyValue("socketCustomizer")).isSameAs(this.socketCustomizer);
}
@Test
@@ -365,6 +372,7 @@ public class ParserUnitTests {
assertThat(dfa.getPropertyValue("order")).isEqualTo(23);
assertThat(udpOut.getComponentName()).isEqualTo("testOutUdp");
assertThat(udpOut.getComponentType()).isEqualTo("ip:udp-outbound-channel-adapter");
assertThat(dfa.getPropertyValue("socketCustomizer")).isSameAs(this.socketCustomizer);
}
@Test
@@ -697,4 +705,12 @@ public class ParserUnitTests {
}
public static class SocketCust implements SocketCustomizer {
@Override
public void configure(DatagramSocket socket) throws SocketException {
}
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.integration.ip.dsl;
import static org.assertj.core.api.Assertions.assertThat;
import java.net.DatagramSocket;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -56,6 +57,7 @@ import org.springframework.integration.ip.tcp.serializer.TcpCodecs;
import org.springframework.integration.ip.udp.MulticastSendingMessageHandler;
import org.springframework.integration.ip.udp.UdpServerListeningEvent;
import org.springframework.integration.ip.udp.UnicastReceivingChannelAdapter;
import org.springframework.integration.ip.udp.UnicastSendingMessageHandler;
import org.springframework.integration.ip.util.TestingUtilities;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
@@ -100,6 +102,9 @@ public class IpIntegrationTests {
@Autowired
private UnicastReceivingChannelAdapter udpInbound;
@Autowired
private UnicastSendingMessageHandler udpOutbound;
@Autowired
private QueueChannel udpIn;
@@ -176,6 +181,8 @@ public class IpIntegrationTests {
Message<?> received = this.udpIn.receive(10000);
assertThat(received).isNotNull();
assertThat(Transformers.objectToString().transform(received).getPayload()).isEqualTo("foo");
assertThat(TestUtils.getPropertyValue(this.udpOutbound, "socket", DatagramSocket.class).getTrafficClass())
.isEqualTo(0x10);
}
@Test
@@ -303,7 +310,8 @@ public class IpIntegrationTests {
@Bean
public IntegrationFlow outUdpAdapter() {
return f -> f.handle(Udp.outboundAdapter(m -> m.getHeaders().get("udp_dest")));
return f -> f.handle(Udp.outboundAdapter(m -> m.getHeaders().get("udp_dest"))
.configureSocket(socket -> socket.setTrafficClass(0x10)));
}
@Bean

View File

@@ -72,6 +72,7 @@ The following example configures a UDP outbound channel adapter:
host="somehost"
port="11111"
multicast="false"
socket-customizer="udpCustomizer"
channel="exampleChannel"/>
----
====
@@ -88,7 +89,9 @@ The `length` header provides a mechanism to detect this.
Starting with version 4.3, you can set the `port` to `0`, in which case the operating system chooses the port.
The chosen port can be discovered by invoking `getPort()` after the adapter is started and `isListening()` returns `true`.
The preceding example shows an outbound channel adapter that adds length checking to the datagram packets:
Starting with version 5.3.3, you can add a `SocketCustomizer` bean to modify the `DatagramSocket` after it is created (for example, call `setTrafficClass(0x10)`).
The following example shows an outbound channel adapter that adds length checking to the datagram packets:
====
[source,xml]
@@ -143,7 +146,7 @@ The following example shows how to configure an outbound UDP adapter with Java:
@Bean
@ServiceActivator(inputChannel = "udpOut")
public UnicastSendingMessageHandler handler() {
return new UnicastSendingMessageHandler("localhost", 11111);
return new UnicastSendingMessageHandler("localhost", 11111);
}
----
====
@@ -159,9 +162,9 @@ The following example shows how to configure an outbound UDP adapter with the Ja
----
@Bean
public IntegrationFlow udpOutFlow() {
return IntegrationFlows.from("udpOut")
.handle(Udp.outboundAdapter("localhost", 1234))
.get();
return f -> f.handle(Udp.outboundAdapter("localhost", 1234)
.configureSocket(socket -> socket.setTrafficClass(0x10)))
.get();
}
----
====
@@ -178,6 +181,7 @@ The following example shows how to configure a basic unicast inbound udp channel
port="11111"
receive-buffer-size="500"
multicast="false"
socket-customizer="udpCustomizer"
check-length="true"/>
----
====
@@ -201,6 +205,9 @@ By default, reverse DNS lookups are done on inbound packets to convert IP addres
In environments where DNS is not configured, this can cause delays.
You can override this default behavior by setting the `lookup-host` attribute to `false`.
Starting with version 5.3.3, you can add a `SocketCustomizer` bean to modify the `DatagramSocket` after it is created.
It is called for the receiving socket and any sockets created for sending acks.
==== Inbound UDP Adapters (Java Configuration)
The following example shows how to configure an inbound UDP adapter with Java:
@@ -209,9 +216,9 @@ The following example shows how to configure an inbound UDP adapter with Java:
----
@Bean
public UnicastReceivingChannelAdapter udpIn() {
UnicastReceivingChannelAdapter adapter = new UnicastReceivingChannelAdapter(11111);
adapter.setOutputChannelName("udpChannel");
return adapter;
UnicastReceivingChannelAdapter adapter = new UnicastReceivingChannelAdapter(11111);
adapter.setOutputChannelName("udpChannel");
return adapter;
}
----
@@ -223,9 +230,9 @@ The following example shows how to configure an inbound UDP adapter with the Jav
----
@Bean
public IntegrationFlow udpIn() {
return IntegrationFlows.from(Udp.inboundAdapter(11111))
.channel("udpChannel")
.get();
return IntegrationFlows.from(Udp.inboundAdapter(11111))
.channel("udpChannel")
.get();
}
----
@@ -278,11 +285,11 @@ The following example shows the equivalent configuration with the Java DSL:
----
@Bean
public IntegrationFlow udpEchoUpcaseServer() {
return IntegrationFlows.from(Udp.inboundAdapter(11111).id("udpIn"))
.<byte[], String>transform(p -> new String(p).toUpperCase())
.handle(Udp.outboundAdapter("headers['ip_packetAddress']")
.socketExpression("@udpIn.socket"))
.get();
return IntegrationFlows.from(Udp.inboundAdapter(11111).id("udpIn"))
.<byte[], String>transform(p -> new String(p).toUpperCase())
.handle(Udp.outboundAdapter("headers['ip_packetAddress']")
.socketExpression("@udpIn.socket"))
.get();
}
----
====
@@ -1396,10 +1403,10 @@ The following listing shows the definition of the `TcpNetConnectionSupport` stra
----
public interface TcpNetConnectionSupport {
TcpNetConnection createNewConnection(Socket socket,
boolean server, boolean lookupHost,
ApplicationEventPublisher applicationEventPublisher,
String connectionFactoryName) throws Exception;
TcpNetConnection createNewConnection(Socket socket,
boolean server, boolean lookupHost,
ApplicationEventPublisher applicationEventPublisher,
String connectionFactoryName) throws Exception;
}
----

View File

@@ -67,13 +67,16 @@ The legacy metrics that were replaced by Micrometer meters have been removed.
The <<./barrier.adoc#barrier,Thread Barrier>> has now two separate timeout options: `requestTimeout` and `triggerTimeout`.
[[x5.4-tcp]]
=== TCP Changes
=== TCP/UDP Changes
Connection factories now support multiple sending components (`TcpSender`); they remain limited to one receiving component (`TcpListener`).
This allows, for example, an inbound gateway and outbound channel adapter to share the same factory, supporting both request/reply and arbitrary messaging from the server to the client.
Shared factories should not be used with outbound gateways, unless single-use connections or the `ThreadAffinityClientConnectionFactory` are being used.
See <<./ip.adoc#ip-collaborating-adapters,Collaborating Channel Adapters>> and <<./ip.adoc#tcp-gateways, TCP Gateways>> for more information.
The UDP channel adapters can now be configured with a `SocketCustomizer` which allows the setting of socket properties that are not directly supported by the adapters.
See <<./ip.adoc#udp-adapters,UDP Adapters>> for more information.
[[x5.4-rmi]]
=== RMI Changes