TCP: Connect Timeout; Close Stream
- Add `connectTimeout` to client connection factories - Add `closeStreamAfterSend` to outbound gateway * Polishing - PR Comments.
This commit is contained in:
committed by
Artem Bilan
parent
cd075723e2
commit
466daa8774
@@ -179,6 +179,7 @@
|
||||
host="localhost"
|
||||
lookup-host="false"
|
||||
apply-sequence="false"
|
||||
connect-timeout="70"
|
||||
read-delay="10000"
|
||||
/>
|
||||
|
||||
@@ -190,10 +191,6 @@
|
||||
phase="125"
|
||||
/>
|
||||
|
||||
<bean id="mockClientCf" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory" />
|
||||
</bean>
|
||||
|
||||
<int:channel id="tcpAdviceChannel">
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
@@ -261,6 +258,7 @@
|
||||
request-channel="tcpAdviceGateChannel"
|
||||
reply-channel="replyChannel"
|
||||
remote-timeout-expression="4000"
|
||||
close-stream-after-send="true"
|
||||
connection-factory="mockClientCf">
|
||||
<int:poller fixed-delay="100"/>
|
||||
<ip:request-handler-advice-chain>
|
||||
@@ -17,19 +17,24 @@
|
||||
package org.springframework.integration.ip.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.core.serializer.Deserializer;
|
||||
import org.springframework.core.serializer.Serializer;
|
||||
@@ -42,6 +47,7 @@ import org.springframework.integration.ip.tcp.TcpInboundGateway;
|
||||
import org.springframework.integration.ip.tcp.TcpOutboundGateway;
|
||||
import org.springframework.integration.ip.tcp.TcpReceivingChannelAdapter;
|
||||
import org.springframework.integration.ip.tcp.TcpSendingMessageHandler;
|
||||
import org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory;
|
||||
import org.springframework.integration.ip.tcp.connection.AbstractConnectionFactory;
|
||||
import org.springframework.integration.ip.tcp.connection.DefaultTcpNetConnectionSupport;
|
||||
import org.springframework.integration.ip.tcp.connection.DefaultTcpNetSSLSocketFactorySupport;
|
||||
@@ -69,8 +75,7 @@ import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
@@ -79,8 +84,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext
|
||||
public class ParserUnitTests {
|
||||
|
||||
@@ -427,6 +431,7 @@ public class ParserUnitTests {
|
||||
assertThat((Boolean) TestUtils.getPropertyValue(
|
||||
TestUtils.getPropertyValue(cfC1, "mapper"), "applySequence")).isFalse();
|
||||
assertThat(TestUtils.getPropertyValue(cfC1, "readDelay")).isEqualTo(10000L);
|
||||
assertThat(TestUtils.getPropertyValue(cfC1, "connectTimeout")).isEqualTo(Duration.ofSeconds(70));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -476,6 +481,7 @@ public class ParserUnitTests {
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(outAdviceGateway, "remoteTimeoutExpression.expression"))
|
||||
.isEqualTo("4000");
|
||||
assertThat(TestUtils.getPropertyValue(outAdviceGateway, "closeStreamAfterSend")).isEqualTo(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -675,4 +681,18 @@ public class ParserUnitTests {
|
||||
super(connection, connectionFactoryName);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ImportResource("org/springframework/integration/ip/config/ParserTests-context.xml")
|
||||
public static class Config {
|
||||
|
||||
@Bean
|
||||
AbstractClientConnectionFactory mockClientCf() {
|
||||
AbstractClientConnectionFactory mock = mock(AbstractClientConnectionFactory.class);
|
||||
given(mock.isSingleUse()).willReturn(true);
|
||||
return mock;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.junit.Test;
|
||||
@@ -30,6 +31,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
@@ -37,13 +39,17 @@ import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.integration.dsl.IntegrationFlow;
|
||||
import org.springframework.integration.dsl.IntegrationFlows;
|
||||
import org.springframework.integration.dsl.MessageChannels;
|
||||
import org.springframework.integration.dsl.Transformers;
|
||||
import org.springframework.integration.dsl.context.IntegrationFlowContext;
|
||||
import org.springframework.integration.dsl.context.IntegrationFlowContext.IntegrationFlowRegistration;
|
||||
import org.springframework.integration.ip.tcp.TcpOutboundGateway;
|
||||
import org.springframework.integration.ip.tcp.TcpReceivingChannelAdapter;
|
||||
import org.springframework.integration.ip.tcp.TcpSendingMessageHandler;
|
||||
import org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory;
|
||||
import org.springframework.integration.ip.tcp.connection.AbstractServerConnectionFactory;
|
||||
import org.springframework.integration.ip.tcp.connection.TcpConnectionServerListeningEvent;
|
||||
import org.springframework.integration.ip.tcp.serializer.ByteArrayRawSerializer;
|
||||
import org.springframework.integration.ip.tcp.serializer.TcpCodecs;
|
||||
import org.springframework.integration.ip.udp.MulticastSendingMessageHandler;
|
||||
import org.springframework.integration.ip.udp.UdpServerListeningEvent;
|
||||
@@ -67,6 +73,9 @@ import org.springframework.test.context.junit4.SpringRunner;
|
||||
@DirtiesContext
|
||||
public class IpIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private ConfigurableApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
private AbstractServerConnectionFactory server1;
|
||||
|
||||
@@ -164,6 +173,42 @@ public class IpIntegrationTests {
|
||||
assertThat(udpMulticastOutboundChannelAdapterSpec2.get()).isInstanceOf(MulticastSendingMessageHandler.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseStream() throws InterruptedException {
|
||||
IntegrationFlow server = IntegrationFlows.from(Tcp.inboundGateway(Tcp.netServer(0)
|
||||
.deserializer(new ByteArrayRawSerializer())))
|
||||
.<byte[], String>transform(p -> "reply:" + new String(p).toUpperCase())
|
||||
.get();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicInteger port = new AtomicInteger();
|
||||
class Listener implements ApplicationListener<TcpConnectionServerListeningEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(TcpConnectionServerListeningEvent event) {
|
||||
port.set(event.getPort());
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
}
|
||||
this.applicationContext.addApplicationListener(new Listener());
|
||||
this.flowContext.registration(server)
|
||||
.id("streamCloseServer")
|
||||
.register();
|
||||
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
IntegrationFlow client = IntegrationFlows.from(MessageChannels.direct())
|
||||
.handle(Tcp.outboundGateway(Tcp.netClient("localhost", port.get())
|
||||
.singleUseConnections(true)
|
||||
.serializer(new ByteArrayRawSerializer()))
|
||||
.closeStreamAfterSend(true))
|
||||
.transform(Transformers.objectToString())
|
||||
.get();
|
||||
IntegrationFlowRegistration clientRegistration = this.flowContext.registration(client)
|
||||
.id("streamCloseClient")
|
||||
.register();
|
||||
assertThat(clientRegistration.getMessagingTemplate()
|
||||
.convertSendAndReceive("foo", String.class)).isEqualTo("reply:FOO");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
public static class Config {
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.net.ServerSocketFactory;
|
||||
import javax.net.SocketFactory;
|
||||
@@ -38,14 +39,20 @@ import javax.net.SocketFactory;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.config.ConsumerEndpointFactoryBean;
|
||||
import org.springframework.integration.handler.BridgeHandler;
|
||||
import org.springframework.integration.handler.ServiceActivatingHandler;
|
||||
import org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory;
|
||||
import org.springframework.integration.ip.tcp.connection.AbstractServerConnectionFactory;
|
||||
import org.springframework.integration.ip.tcp.connection.TcpConnectionSupport;
|
||||
import org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory;
|
||||
import org.springframework.integration.ip.tcp.connection.TcpNetServerConnectionFactory;
|
||||
import org.springframework.integration.ip.tcp.connection.TcpNioClientConnectionFactory;
|
||||
import org.springframework.integration.ip.tcp.connection.TcpNioServerConnectionFactory;
|
||||
import org.springframework.integration.ip.tcp.serializer.ByteArrayRawSerializer;
|
||||
import org.springframework.integration.ip.util.TestingUtilities;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
@@ -284,6 +291,57 @@ public class TcpInboundGatewayTests {
|
||||
scf.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetCloseStream() throws InterruptedException, IOException {
|
||||
testCloseStream(new TcpNetServerConnectionFactory(0),
|
||||
port -> new TcpNetClientConnectionFactory("localhost", port));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNioCloseStream() throws InterruptedException, IOException {
|
||||
testCloseStream(new TcpNioServerConnectionFactory(0),
|
||||
port -> new TcpNioClientConnectionFactory("localhost", port));
|
||||
}
|
||||
|
||||
private void testCloseStream(AbstractServerConnectionFactory scf,
|
||||
Function<Integer, AbstractClientConnectionFactory> ccf) throws InterruptedException, IOException {
|
||||
|
||||
scf.setSingleUse(true);
|
||||
scf.setDeserializer(new ByteArrayRawSerializer());
|
||||
TcpInboundGateway gateway = new TcpInboundGateway();
|
||||
gateway.setConnectionFactory(scf);
|
||||
BeanFactory bf = mock(ConfigurableBeanFactory.class);
|
||||
gateway.setBeanFactory(bf);
|
||||
gateway.start();
|
||||
TestingUtilities.waitListening(scf, 20000L);
|
||||
int port = scf.getPort();
|
||||
final DirectChannel channel = new DirectChannel();
|
||||
gateway.setRequestChannel(channel);
|
||||
BridgeHandler bridge = new BridgeHandler();
|
||||
bridge.setBeanFactory(bf);
|
||||
bridge.afterPropertiesSet();
|
||||
ConsumerEndpointFactoryBean consumer = new ConsumerEndpointFactoryBean();
|
||||
consumer.setInputChannel(channel);
|
||||
consumer.setBeanFactory(bf);
|
||||
consumer.setHandler(bridge);
|
||||
consumer.afterPropertiesSet();
|
||||
consumer.start();
|
||||
AbstractClientConnectionFactory client = ccf.apply(port);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
client.registerListener(message -> {
|
||||
latch.countDown();
|
||||
return false;
|
||||
});
|
||||
client.afterPropertiesSet();
|
||||
client.start();
|
||||
TcpConnectionSupport connection = client.getConnection();
|
||||
connection.send(new GenericMessage<>("foo"));
|
||||
connection.shutdownOutput(); // signal EOF to server
|
||||
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
gateway.stop();
|
||||
client.stop();
|
||||
}
|
||||
|
||||
|
||||
private void readFully(InputStream is, byte[] buff) throws IOException {
|
||||
for (int i = 0; i < buff.length; i++) {
|
||||
|
||||
@@ -18,20 +18,29 @@ package org.springframework.integration.ip.tcp.connection;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@@ -69,7 +78,7 @@ public class SocketSupportTests {
|
||||
when(socket.getInputStream()).thenReturn(is);
|
||||
InetAddress inetAddress = InetAddress.getLocalHost();
|
||||
when(socket.getInetAddress()).thenReturn(inetAddress);
|
||||
when(factory.createSocket("x", 0)).thenReturn(socket);
|
||||
when(factory.createSocket()).thenReturn(socket);
|
||||
TcpSocketSupport socketSupport = Mockito.mock(TcpSocketSupport.class);
|
||||
|
||||
TcpNetClientConnectionFactory connectionFactory = new TcpNetClientConnectionFactory("x", 0);
|
||||
@@ -83,25 +92,64 @@ public class SocketSupportTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetServer() throws Exception {
|
||||
public void testNetClientSocketTimeout() throws Exception {
|
||||
TcpSocketFactorySupport factorySupport = mock(TcpSocketFactorySupport.class);
|
||||
ServerSocketFactory factory = mock(ServerSocketFactory.class);
|
||||
when(factorySupport.getServerSocketFactory()).thenReturn(factory);
|
||||
SocketFactory factory = Mockito.mock(SocketFactory.class);
|
||||
when(factorySupport.getSocketFactory()).thenReturn(factory);
|
||||
Socket socket = mock(Socket.class);
|
||||
InputStream is = mock(InputStream.class);
|
||||
when(is.read()).thenReturn(-1);
|
||||
when(socket.getInputStream()).thenReturn(is);
|
||||
InetAddress inetAddress = InetAddress.getLocalHost();
|
||||
when(socket.getInetAddress()).thenReturn(inetAddress);
|
||||
when(factory.createSocket()).thenReturn(socket);
|
||||
doThrow(new SocketTimeoutException()).when(socket).connect(any(), eq(1000));
|
||||
TcpSocketSupport socketSupport = Mockito.mock(TcpSocketSupport.class);
|
||||
|
||||
TcpNetClientConnectionFactory connectionFactory = new TcpNetClientConnectionFactory("x", 0);
|
||||
connectionFactory.setConnectTimeout(1);
|
||||
connectionFactory.setTcpSocketFactorySupport(factorySupport);
|
||||
connectionFactory.setTcpSocketSupport(socketSupport);
|
||||
connectionFactory.start();
|
||||
assertThatThrownBy(() -> connectionFactory.getConnection())
|
||||
.isInstanceOf(UncheckedIOException.class)
|
||||
.hasCauseInstanceOf(SocketTimeoutException.class);
|
||||
|
||||
connectionFactory.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetServer() throws Exception {
|
||||
TcpSocketFactorySupport factorySupport = mock(TcpSocketFactorySupport.class);
|
||||
ServerSocketFactory factory = mock(ServerSocketFactory.class);
|
||||
when(factorySupport.getServerSocketFactory()).thenReturn(factory);
|
||||
Socket socket = mock(Socket.class);
|
||||
Socket socket1 = mock(Socket.class);
|
||||
InputStream is = mock(InputStream.class);
|
||||
when(is.read()).thenReturn(-1);
|
||||
when(socket.getInputStream()).thenReturn(is);
|
||||
when(socket1.getInputStream()).thenReturn(is);
|
||||
InetAddress inetAddress = InetAddress.getLocalHost();
|
||||
when(socket.getInetAddress()).thenReturn(inetAddress);
|
||||
when(socket1.getInetAddress()).thenReturn(inetAddress);
|
||||
ServerSocket serverSocket = mock(ServerSocket.class);
|
||||
AtomicBoolean closed = new AtomicBoolean();
|
||||
doAnswer(invoc -> {
|
||||
closed.set(true);
|
||||
return null;
|
||||
}).when(serverSocket).close();
|
||||
when(serverSocket.getInetAddress()).thenReturn(inetAddress);
|
||||
when(factory.createServerSocket(0, 5)).thenReturn(serverSocket);
|
||||
final CountDownLatch latch1 = new CountDownLatch(1);
|
||||
final CountDownLatch latch2 = new CountDownLatch(1);
|
||||
when(serverSocket.accept()).thenReturn(socket).then(invocation -> {
|
||||
if (closed.get()) {
|
||||
throw new SocketException();
|
||||
}
|
||||
latch1.countDown();
|
||||
latch2.await(10, TimeUnit.SECONDS);
|
||||
return null;
|
||||
Thread.sleep(50);
|
||||
return socket1;
|
||||
});
|
||||
TcpSocketSupport socketSupport = mock(TcpSocketSupport.class);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user