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
@@ -131,6 +131,8 @@ public abstract class IpAdapterParserUtils {
|
||||
|
||||
public static final String SSL_HANDSHAKE_TIMEOUT = "ssl-handshake-timeout";
|
||||
|
||||
public static final String CONNECT_TIMEOUT = "connect-timeout";
|
||||
|
||||
private IpAdapterParserUtils() {
|
||||
}
|
||||
|
||||
|
||||
@@ -59,73 +59,75 @@ import org.springframework.util.Assert;
|
||||
public class TcpConnectionFactoryFactoryBean extends AbstractFactoryBean<AbstractConnectionFactory>
|
||||
implements Lifecycle, BeanNameAware, ApplicationEventPublisherAware {
|
||||
|
||||
private volatile AbstractConnectionFactory connectionFactory;
|
||||
private AbstractConnectionFactory connectionFactory;
|
||||
|
||||
private volatile String type;
|
||||
private String type;
|
||||
|
||||
private volatile String host;
|
||||
private String host;
|
||||
|
||||
private volatile int port;
|
||||
private int port;
|
||||
|
||||
private volatile int soTimeout;
|
||||
private int soTimeout;
|
||||
|
||||
private volatile int soSendBufferSize;
|
||||
private int soSendBufferSize;
|
||||
|
||||
private volatile int soReceiveBufferSize;
|
||||
private int soReceiveBufferSize;
|
||||
|
||||
private volatile boolean soTcpNoDelay;
|
||||
private boolean soTcpNoDelay;
|
||||
|
||||
private volatile int soLinger = -1; // don't set by default
|
||||
private int soLinger = -1; // don't set by default
|
||||
|
||||
private volatile boolean soKeepAlive;
|
||||
private boolean soKeepAlive;
|
||||
|
||||
private volatile int soTrafficClass = -1; // don't set by default
|
||||
private int soTrafficClass = -1; // don't set by default
|
||||
|
||||
private volatile Executor taskExecutor;
|
||||
private Executor taskExecutor;
|
||||
|
||||
private volatile Deserializer<?> deserializer = new ByteArrayCrLfSerializer();
|
||||
private Deserializer<?> deserializer = new ByteArrayCrLfSerializer();
|
||||
|
||||
private volatile Serializer<?> serializer = new ByteArrayCrLfSerializer();
|
||||
private Serializer<?> serializer = new ByteArrayCrLfSerializer();
|
||||
|
||||
private volatile TcpMessageMapper mapper = new TcpMessageMapper();
|
||||
private TcpMessageMapper mapper = new TcpMessageMapper();
|
||||
|
||||
private volatile boolean mapperSet;
|
||||
private boolean mapperSet;
|
||||
|
||||
private volatile boolean singleUse;
|
||||
private boolean singleUse;
|
||||
|
||||
private volatile int backlog = 5;
|
||||
private int backlog = 5;
|
||||
|
||||
private volatile TcpConnectionInterceptorFactoryChain interceptorFactoryChain;
|
||||
private TcpConnectionInterceptorFactoryChain interceptorFactoryChain;
|
||||
|
||||
private volatile boolean lookupHost = true;
|
||||
private boolean lookupHost = true;
|
||||
|
||||
private volatile String localAddress;
|
||||
private String localAddress;
|
||||
|
||||
private volatile boolean usingNio;
|
||||
private boolean usingNio;
|
||||
|
||||
private volatile boolean usingDirectBuffers;
|
||||
private boolean usingDirectBuffers;
|
||||
|
||||
private volatile String beanName;
|
||||
private String beanName;
|
||||
|
||||
private volatile boolean applySequence;
|
||||
private boolean applySequence;
|
||||
|
||||
private volatile Long readDelay;
|
||||
private Long readDelay;
|
||||
|
||||
private volatile TcpSSLContextSupport sslContextSupport;
|
||||
private TcpSSLContextSupport sslContextSupport;
|
||||
|
||||
private volatile Integer sslHandshakeTimeout;
|
||||
private Integer sslHandshakeTimeout;
|
||||
|
||||
private volatile TcpSocketSupport socketSupport = new DefaultTcpSocketSupport();
|
||||
private TcpSocketSupport socketSupport = new DefaultTcpSocketSupport();
|
||||
|
||||
private volatile TcpNioConnectionSupport nioConnectionSupport;
|
||||
private TcpNioConnectionSupport nioConnectionSupport;
|
||||
|
||||
private volatile TcpNetConnectionSupport netConnectionSupport;
|
||||
private TcpNetConnectionSupport netConnectionSupport;
|
||||
|
||||
private volatile TcpSocketFactorySupport socketFactorySupport;
|
||||
private TcpSocketFactorySupport socketFactorySupport;
|
||||
|
||||
private volatile ApplicationEventPublisher applicationEventPublisher;
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private volatile BeanFactory beanFactory;
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private Integer connectTimeout;
|
||||
|
||||
|
||||
public TcpConnectionFactoryFactoryBean() {
|
||||
@@ -189,6 +191,9 @@ public class TcpConnectionFactoryFactoryBean extends AbstractFactoryBean<Abstrac
|
||||
this.setCommonAttributes(factory);
|
||||
factory.setTcpSocketFactorySupport(this.obtainSocketFactorySupport());
|
||||
factory.setTcpNetConnectionSupport(this.obtainNetConnectionSupport());
|
||||
if (this.connectTimeout != null) {
|
||||
factory.setConnectTimeout(this.connectTimeout);
|
||||
}
|
||||
this.connectionFactory = factory;
|
||||
}
|
||||
}
|
||||
@@ -501,6 +506,10 @@ public class TcpConnectionFactoryFactoryBean extends AbstractFactoryBean<Abstrac
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
public void setConnectTimeout(int connectTimeout) {
|
||||
this.connectTimeout = connectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SSL handshake timeout (only used with SSL and NIO).
|
||||
* @param sslHandshakeTimeout the timeout.
|
||||
|
||||
@@ -100,6 +100,8 @@ public class TcpConnectionFactoryParser extends AbstractBeanDefinitionParser {
|
||||
IpAdapterParserUtils.SSL_HANDSHAKE_TIMEOUT);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
|
||||
IpAdapterParserUtils.READ_DELAY);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
|
||||
IpAdapterParserUtils.CONNECT_TIMEOUT);
|
||||
|
||||
return builder.getBeanDefinition();
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ public class TcpOutboundGatewayParser extends AbstractConsumerEndpointParser {
|
||||
}
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
|
||||
IpAdapterParserUtils.REPLY_TIMEOUT, "sendTimeout");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "close-stream-after-send");
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@@ -219,4 +219,15 @@ public abstract class AbstractConnectionFactorySpec
|
||||
return _this();
|
||||
}
|
||||
|
||||
/**
|
||||
* This connection factory uses a new connection for each operation.
|
||||
* @param single true for a new connection for each operation.
|
||||
* @return the spec.
|
||||
* @since 5.2
|
||||
*/
|
||||
public S singleUseConnections(boolean single) {
|
||||
this.target.setSingleUse(single);
|
||||
return _this();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,4 +38,15 @@ public class TcpClientConnectionFactorySpec
|
||||
super(nio ? new TcpNioClientConnectionFactory(host, port) : new TcpNetClientConnectionFactory(host, port));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection timeout in seconds. Defaults to 60.
|
||||
* @param connectTimeout the timeout.
|
||||
* @return the spec.
|
||||
* @since 5.2
|
||||
*/
|
||||
public TcpClientConnectionFactorySpec connectTimeout(int connectTimeout) {
|
||||
this.target.setConnectTimeout(connectTimeout);
|
||||
return _this();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -89,6 +89,20 @@ public class TcpOutboundGatewaySpec extends MessageHandlerSpec<TcpOutboundGatewa
|
||||
return _this();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to close the connection ouput stream after sending without
|
||||
* closing the connection. Use to signal EOF to the server, such as when using
|
||||
* a {@link org.springframework.integration.ip.tcp.serializer.ByteArrayRawSerializer}.
|
||||
* Requires a single-use connection factory.
|
||||
* @param closeStreamAfterSend true to close.
|
||||
* @return the spec.
|
||||
* @since 5.2
|
||||
*/
|
||||
public TcpOutboundGatewaySpec closeStreamAfterSend(boolean closeStreamAfterSend) {
|
||||
this.target.setCloseStreamAfterSend(closeStreamAfterSend);
|
||||
return _this();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, String> getComponentsToRegister() {
|
||||
return this.connectionFactory != null
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.integration.ip.tcp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -84,6 +85,8 @@ public class TcpOutboundGateway extends AbstractReplyProducingMessageHandler
|
||||
|
||||
private int secondChanceDelay = DEFAULT_SECOND_CHANCE_DELAY;
|
||||
|
||||
private boolean closeStreamAfterSend;
|
||||
|
||||
/**
|
||||
* @param requestTimeout the requestTimeout to set
|
||||
*/
|
||||
@@ -117,6 +120,8 @@ public class TcpOutboundGateway extends AbstractReplyProducingMessageHandler
|
||||
if (!this.evaluationContextSet) {
|
||||
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
|
||||
}
|
||||
Assert.state(!this.closeStreamAfterSend || this.isSingleUse,
|
||||
"Single use connection needed with closeStreamAfterSend");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,9 +154,12 @@ public class TcpOutboundGateway extends AbstractReplyProducingMessageHandler
|
||||
logger.debug("Added pending reply " + connectionId);
|
||||
}
|
||||
connection.send(requestMessage);
|
||||
if (this.closeStreamAfterSend) {
|
||||
connection.shutdownOutput();
|
||||
}
|
||||
return getReply(requestMessage, connection, connectionId, reply);
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
catch (RuntimeException | IOException e) {
|
||||
logger.error("Tcp Gateway exception", e);
|
||||
if (e instanceof MessagingException) {
|
||||
throw (MessagingException) e;
|
||||
@@ -305,6 +313,18 @@ public class TcpOutboundGateway extends AbstractReplyProducingMessageHandler
|
||||
this.setOutputChannelName(replyChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to close the connection ouput stream after sending without
|
||||
* closing the connection. Use to signal EOF to the server, such as when using
|
||||
* a {@link org.springframework.integration.ip.tcp.serializer.ByteArrayRawSerializer}.
|
||||
* Requires a single-use connection factory.
|
||||
* @param closeStreamAfterSend true to close.
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setCloseStreamAfterSend(boolean closeStreamAfterSend) {
|
||||
this.closeStreamAfterSend = closeStreamAfterSend;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComponentType() {
|
||||
return "ip:tcp-outbound-gateway";
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.ip.tcp.connection;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
@@ -33,11 +34,15 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public abstract class AbstractClientConnectionFactory extends AbstractConnectionFactory {
|
||||
|
||||
private static final long DEFAULT_CONNECT_TIMEOUT = 60L;
|
||||
|
||||
private final ReadWriteLock theConnectionLock = new ReentrantReadWriteLock();
|
||||
|
||||
private volatile TcpConnectionSupport theConnection;
|
||||
private boolean manualListenerRegistration;
|
||||
|
||||
private volatile boolean manualListenerRegistration;
|
||||
private Duration connectTimeout = Duration.ofSeconds(DEFAULT_CONNECT_TIMEOUT);
|
||||
|
||||
private volatile TcpConnectionSupport theConnection;
|
||||
|
||||
/**
|
||||
* Constructs a factory that will established connections to the host and port.
|
||||
@@ -48,6 +53,19 @@ public abstract class AbstractClientConnectionFactory extends AbstractConnection
|
||||
super(host, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection timeout in seconds. Defaults to 60.
|
||||
* @param connectTimeout the timeout.
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setConnectTimeout(int connectTimeout) {
|
||||
this.connectTimeout = Duration.ofSeconds(connectTimeout);
|
||||
}
|
||||
|
||||
protected Duration getConnectTimeout() {
|
||||
return this.connectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to automatically (default) or manually add a {@link TcpListener} to the
|
||||
* connections created by this factory. By default, the factory automatically configures
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.integration.ip.tcp.connection;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
import org.springframework.core.serializer.Deserializer;
|
||||
@@ -132,4 +134,24 @@ public interface TcpConnection extends Runnable {
|
||||
*/
|
||||
SocketInfo getSocketInfo();
|
||||
|
||||
/**
|
||||
* Set the connection's input stream to end of stream.
|
||||
* @throws IOException an IO Exception.
|
||||
* @since 5.2
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
default void shutdownInput() throws IOException {
|
||||
throw new UnsupportedOperationException("This connection does not support shutDownInput()");
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the socket's output stream.
|
||||
* @throws IOException an IO Exception
|
||||
* @since 5.2
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
default void shutdownOutput() throws IOException {
|
||||
throw new UnsupportedOperationException("This connection does not support shutDownOutput()");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.integration.ip.tcp.connection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
@@ -88,7 +89,9 @@ public class TcpNetClientConnectionFactory extends
|
||||
* @throws IOException Any IOException.
|
||||
*/
|
||||
protected Socket createSocket(String host, int port) throws IOException {
|
||||
return this.tcpSocketFactorySupport.getSocketFactory().createSocket(host, port);
|
||||
Socket socket = this.tcpSocketFactorySupport.getSocketFactory().createSocket();
|
||||
socket.connect(new InetSocketAddress(host, port), (int) getConnectTimeout().toMillis());
|
||||
return socket;
|
||||
}
|
||||
|
||||
protected TcpSocketFactorySupport getTcpSocketFactorySupport() {
|
||||
|
||||
@@ -285,4 +285,24 @@ public class TcpNetConnection extends TcpConnectionSupport implements Scheduling
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the socket's input stream to end of stream.
|
||||
* @throws IOException an IO Exception.
|
||||
* @since 5.2
|
||||
* @see Socket#shutdownInput()
|
||||
*/
|
||||
public void shutdownInput() throws IOException {
|
||||
this.socket.shutdownInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the socket's output stream.
|
||||
* @throws IOException an IO Exception
|
||||
* @since 5.2
|
||||
* @see Socket#shutdownOutput()
|
||||
*/
|
||||
public void shutdownOutput() throws IOException {
|
||||
this.socket.shutdownOutput();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class TcpNioClientConnectionFactory extends
|
||||
@Override
|
||||
protected TcpConnectionSupport buildNewConnection() {
|
||||
try {
|
||||
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(getHost(), getPort()));
|
||||
SocketChannel socketChannel = SocketChannel.open();
|
||||
setSocketAttributes(socketChannel.socket());
|
||||
TcpNioConnection connection =
|
||||
this.tcpNioConnectionSupport.createNewConnection(socketChannel, false, isLookupHost(),
|
||||
@@ -99,6 +99,17 @@ public class TcpNioClientConnectionFactory extends
|
||||
TcpConnectionSupport wrappedConnection = wrapConnection(connection);
|
||||
initializeConnection(wrappedConnection, socketChannel.socket());
|
||||
socketChannel.configureBlocking(false);
|
||||
socketChannel.connect(new InetSocketAddress(getHost(), getPort()));
|
||||
boolean connected = socketChannel.finishConnect();
|
||||
long timeLeft = getConnectTimeout().toMillis();
|
||||
while (!connected && timeLeft > 0) {
|
||||
Thread.sleep(50); // NOSONAR Magic #
|
||||
connected = socketChannel.finishConnect();
|
||||
timeLeft -= 50; // NOSONAR Magic #
|
||||
}
|
||||
if (!connected) {
|
||||
throw new IOException("Not connected after connectTimeout");
|
||||
}
|
||||
if (getSoTimeout() > 0) {
|
||||
connection.setLastRead(System.currentTimeMillis());
|
||||
}
|
||||
@@ -110,6 +121,10 @@ public class TcpNioClientConnectionFactory extends
|
||||
catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new UncheckedIOException(new IOException(e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -574,6 +574,26 @@ public class TcpNioConnection extends TcpConnectionSupport {
|
||||
return this.lastSend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the socket's input stream to end of stream.
|
||||
* @throws IOException an IO Exception.
|
||||
* @since 5.2
|
||||
* @see SocketChannel#shutdownInput()
|
||||
*/
|
||||
public void shutdownInput() throws IOException {
|
||||
this.socketChannel.shutdownInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the socket's output stream.
|
||||
* @throws IOException an IO Exception
|
||||
* @since 5.2
|
||||
* @see SocketChannel#shutdownOutput()
|
||||
*/
|
||||
public void shutdownOutput() throws IOException {
|
||||
this.socketChannel.shutdownOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* OutputStream to wrap a SocketChannel; implements timeout on write.
|
||||
*
|
||||
|
||||
@@ -478,6 +478,15 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="close-stream-after-send" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Close the output stream after sending the message; this signals
|
||||
EOF to the server while keeping the connection open to receive
|
||||
the reply. Requires 'single-use' set to 'true'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="integration:smartLifeCycleAttributeGroup" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
@@ -792,6 +801,14 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="connect-timeout" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
For client factories, the amount of time to wait for a connection to
|
||||
be established.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
||||
@@ -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