TCP: Connect Timeout; Close Stream

- Add `connectTimeout` to client connection factories
- Add `closeStreamAfterSend` to outbound gateway

* Polishing - PR Comments.
This commit is contained in:
Gary Russell
2019-06-05 14:38:14 -04:00
committed by Artem Bilan
parent cd075723e2
commit 466daa8774
22 changed files with 420 additions and 54 deletions

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

@@ -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 {

View File

@@ -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++) {

View File

@@ -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);