INT-1707 Don't set SO_LINGER unless specified; close socket on send when appropriate, unless SO_LINGER=0; ensure any self-called close() is properly routed through any interceptors; suppress error on read when socket closed normally
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beansProjectDescription>
|
||||
<version>1</version>
|
||||
<pluginVersion><![CDATA[2.3.3.201005102200-CI-R3771-B739]]></pluginVersion>
|
||||
<configSuffixes>
|
||||
<configSuffix><![CDATA[xml]]></configSuffix>
|
||||
</configSuffixes>
|
||||
<enableImports><![CDATA[false]]></enableImports>
|
||||
<configs>
|
||||
</configs>
|
||||
<configSets>
|
||||
</configSets>
|
||||
</beansProjectDescription>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beansProjectDescription>
|
||||
<version>1</version>
|
||||
<pluginVersion><![CDATA[2.5.1.201011101000-RELEASE]]></pluginVersion>
|
||||
<configSuffixes>
|
||||
<configSuffix><![CDATA[xml]]></configSuffix>
|
||||
</configSuffixes>
|
||||
<enableImports><![CDATA[false]]></enableImports>
|
||||
<configs>
|
||||
<config>src/test/java/org/springframework/integration/ip/tcp/connection/SOLingerTests-context.xml</config>
|
||||
</configs>
|
||||
<configSets>
|
||||
</configSets>
|
||||
</beansProjectDescription>
|
||||
|
||||
@@ -81,7 +81,7 @@ public class TcpSendingMessageHandler extends AbstractMessageHandler implements
|
||||
public void handleMessageInternal(final Message<?> message) throws MessageRejectedException,
|
||||
MessageHandlingException, MessageDeliveryException {
|
||||
if (this.serverConnectionFactory != null) {
|
||||
// We don't own the connection
|
||||
// We don't own the connection, we are asynchronously replying
|
||||
Object connectionId = message.getHeaders().get(IpHeaders.CONNECTION_ID);
|
||||
TcpConnection connection = connections.get(connectionId);
|
||||
if (connection != null) {
|
||||
@@ -97,7 +97,7 @@ public class TcpSendingMessageHandler extends AbstractMessageHandler implements
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// we own the connection
|
||||
try {
|
||||
doWrite(message);
|
||||
} catch (MessageMappingException e) {
|
||||
|
||||
@@ -60,11 +60,11 @@ public abstract class AbstractConnectionFactory
|
||||
|
||||
private boolean soTcpNoDelay;
|
||||
|
||||
private int soLinger;
|
||||
private int soLinger = -1; // don't set by default
|
||||
|
||||
private boolean soKeepAlive;
|
||||
|
||||
private int soTrafficClass;
|
||||
private int soTrafficClass = -1; // don't set by default
|
||||
|
||||
protected Executor taskExecutor;
|
||||
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
|
||||
package org.springframework.integration.ip.tcp.connection;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.serializer.Deserializer;
|
||||
import org.springframework.core.serializer.Serializer;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.ip.tcp.serializer.AbstractByteArraySerializer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -48,6 +50,8 @@ public abstract class AbstractTcpConnection implements TcpConnection {
|
||||
protected TcpMessageMapper mapper;
|
||||
|
||||
protected TcpListener listener;
|
||||
|
||||
private TcpListener actualListener;
|
||||
|
||||
protected TcpSender sender;
|
||||
|
||||
@@ -59,8 +63,27 @@ public abstract class AbstractTcpConnection implements TcpConnection {
|
||||
|
||||
private AtomicLong sequence = new AtomicLong();
|
||||
|
||||
public AbstractTcpConnection(boolean server) {
|
||||
private int soLinger = -1;
|
||||
|
||||
public AbstractTcpConnection(Socket socket, boolean server) {
|
||||
this.server = server;
|
||||
try {
|
||||
this.soLinger = socket.getSoLinger();
|
||||
} catch (SocketException e) { }
|
||||
}
|
||||
|
||||
public void afterSend(Message<?> message) throws Exception {
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Message sent " + message);
|
||||
if (this.singleUse) {
|
||||
// if (we're a server socket, or a send-only socket), and soLinger <> 0, close
|
||||
if ((this.isServer() || this.actualListener == null) && this.soLinger != 0) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Closing single-use connection" + this.getConnectionId());
|
||||
}
|
||||
this.closeConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,7 +94,23 @@ public abstract class AbstractTcpConnection implements TcpConnection {
|
||||
this.sender.removeDeadConnection(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If we have been intercepted, propagate the close from the outermost interceptor;
|
||||
* otherwise, just call close().
|
||||
*/
|
||||
protected void closeConnection() {
|
||||
if (!(this.listener instanceof TcpConnectionInterceptor)) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
TcpConnectionInterceptor outerInterceptor = (TcpConnectionInterceptor) this.listener;
|
||||
while (outerInterceptor.getListener() instanceof TcpConnectionInterceptor) {
|
||||
outerInterceptor = (TcpConnectionInterceptor) outerInterceptor.getListener();
|
||||
}
|
||||
outerInterceptor.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the mapper
|
||||
*/
|
||||
@@ -129,6 +168,16 @@ public abstract class AbstractTcpConnection implements TcpConnection {
|
||||
*/
|
||||
public void registerListener(TcpListener listener) {
|
||||
this.listener = listener;
|
||||
// Determine the actual listener for this connection
|
||||
if (!(this.listener instanceof TcpConnectionInterceptor)) {
|
||||
this.actualListener = this.listener;
|
||||
} else {
|
||||
TcpConnectionInterceptor outerInterceptor = (TcpConnectionInterceptor) this.listener;
|
||||
while (outerInterceptor.getListener() instanceof TcpConnectionInterceptor) {
|
||||
outerInterceptor = (TcpConnectionInterceptor) outerInterceptor.getListener();
|
||||
}
|
||||
this.actualListener = outerInterceptor.getListener();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,8 @@ public abstract class AbstractTcpConnectionInterceptor implements TcpConnectionI
|
||||
private TcpListener tcpListener;
|
||||
|
||||
private TcpSender tcpSender;
|
||||
|
||||
|
||||
private Boolean realSender;
|
||||
|
||||
public void close() {
|
||||
this.theConnection.close();
|
||||
@@ -61,8 +62,8 @@ public abstract class AbstractTcpConnectionInterceptor implements TcpConnectionI
|
||||
}
|
||||
|
||||
public void registerListener(TcpListener listener) {
|
||||
this.theConnection.registerListener(this);
|
||||
this.tcpListener = listener;
|
||||
this.theConnection.registerListener(this);
|
||||
}
|
||||
|
||||
public void registerSender(TcpSender sender) {
|
||||
@@ -159,6 +160,21 @@ public abstract class AbstractTcpConnectionInterceptor implements TcpConnectionI
|
||||
public long getConnectionSeq() {
|
||||
return this.theConnection.getConnectionSeq();
|
||||
}
|
||||
|
||||
|
||||
TcpSender getSender() {
|
||||
return this.tcpSender;
|
||||
}
|
||||
|
||||
protected boolean hasRealSender() {
|
||||
if (this.realSender != null) {
|
||||
return this.realSender;
|
||||
}
|
||||
TcpSender sender = this.getSender();
|
||||
while (sender != null && sender instanceof AbstractTcpConnectionInterceptor) {
|
||||
sender = ((AbstractTcpConnectionInterceptor) sender).getSender();
|
||||
}
|
||||
this.realSender = sender != null;
|
||||
return this.realSender;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ package org.springframework.integration.ip.tcp.connection;
|
||||
public interface TcpConnectionInterceptorFactory {
|
||||
|
||||
/**
|
||||
* Called for each new connection - if an interceptor is
|
||||
* stateful, a new interceptor must be returned on each call.
|
||||
* Called for each new connection;
|
||||
* a new interceptor must be returned on each call.
|
||||
*
|
||||
* @return the TcpInterceptor
|
||||
*/
|
||||
|
||||
@@ -35,6 +35,8 @@ public class TcpNetConnection extends AbstractTcpConnection {
|
||||
|
||||
private final Socket socket;
|
||||
|
||||
private boolean noReadErrorOnClose;
|
||||
|
||||
/**
|
||||
* Constructs a TcpNetConnection for the socket.
|
||||
* @param socket the socket
|
||||
@@ -42,7 +44,7 @@ public class TcpNetConnection extends AbstractTcpConnection {
|
||||
* a result of an incoming request.
|
||||
*/
|
||||
public TcpNetConnection(Socket socket, boolean server) {
|
||||
super(server);
|
||||
super(socket, server);
|
||||
this.socket = socket;
|
||||
getConnectionId();
|
||||
}
|
||||
@@ -51,6 +53,7 @@ public class TcpNetConnection extends AbstractTcpConnection {
|
||||
* Closes this connection.
|
||||
*/
|
||||
public void close() {
|
||||
this.noReadErrorOnClose = true;
|
||||
try {
|
||||
this.socket.close();
|
||||
} catch (Exception e) {}
|
||||
@@ -65,8 +68,7 @@ public class TcpNetConnection extends AbstractTcpConnection {
|
||||
public synchronized void send(Message<?> message) throws Exception {
|
||||
Object object = mapper.fromMessage(message);
|
||||
this.serializer.serialize(object, this.socket.getOutputStream());
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Message sent " + message);
|
||||
this.afterSend(message);
|
||||
}
|
||||
|
||||
public String getHostAddress() {
|
||||
@@ -108,12 +110,19 @@ public class TcpNetConnection extends AbstractTcpConnection {
|
||||
try {
|
||||
message = this.mapper.toMessage(this);
|
||||
} catch (Exception e) {
|
||||
this.close();
|
||||
this.closeConnection();
|
||||
if (!(e instanceof SoftEndOfStreamException)) {
|
||||
if (e instanceof SocketTimeoutException && this.singleUse) {
|
||||
logger.debug("Closing single use socket after timeout");
|
||||
} else {
|
||||
if (logger.isTraceEnabled()) {
|
||||
if (this.noReadErrorOnClose) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Read exception " +
|
||||
this.getConnectionId() + " " +
|
||||
e.getClass().getSimpleName() +
|
||||
":" + e.getCause() + ":" + e.getMessage());
|
||||
}
|
||||
} else if (logger.isTraceEnabled()) {
|
||||
logger.error("Read exception " +
|
||||
this.getConnectionId(), e);
|
||||
} else {
|
||||
@@ -138,7 +147,7 @@ public class TcpNetConnection extends AbstractTcpConnection {
|
||||
if (e instanceof NoListenerException) {
|
||||
if (this.singleUse) {
|
||||
logger.debug("Closing single use socket after inbound message " + this.connectionId);
|
||||
this.close();
|
||||
this.closeConnection();
|
||||
okToRun = false;
|
||||
} else {
|
||||
logger.warn("Unexpected message - no inbound adapter registered with connection " + message);
|
||||
@@ -154,7 +163,7 @@ public class TcpNetConnection extends AbstractTcpConnection {
|
||||
*/
|
||||
if (this.singleUse && ((!this.server && !intercepted) || (this.server && this.sender == null))) {
|
||||
logger.debug("Closing single use socket after inbound message " + this.connectionId);
|
||||
this.close();
|
||||
this.closeConnection();
|
||||
okToRun = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public class TcpNioConnection extends AbstractTcpConnection {
|
||||
* a result of an incoming request.
|
||||
*/
|
||||
public TcpNioConnection(SocketChannel socketChannel, boolean server) throws Exception {
|
||||
super(server);
|
||||
super(socketChannel.socket(), server);
|
||||
this.socketChannel = socketChannel;
|
||||
this.pipedInputStream = new PipedInputStream();
|
||||
this.pipedOutputStream = new PipedOutputStream(this.pipedInputStream);
|
||||
@@ -107,6 +107,7 @@ public class TcpNioConnection extends AbstractTcpConnection {
|
||||
synchronized(mapper) {
|
||||
Object object = mapper.fromMessage(message);
|
||||
this.serializer.serialize(object, this.channelOutputStream);
|
||||
this.afterSend(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +210,7 @@ public class TcpNioConnection extends AbstractTcpConnection {
|
||||
try {
|
||||
message = this.mapper.toMessage(this);
|
||||
} catch (Exception e) {
|
||||
this.close();
|
||||
this.closeConnection();
|
||||
if (e instanceof SocketTimeoutException && this.singleUse) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Closing single use socket after timeout " + this.connectionId);
|
||||
@@ -244,7 +245,7 @@ public class TcpNioConnection extends AbstractTcpConnection {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Closing single use channel after inbound message " + this.connectionId);
|
||||
}
|
||||
this.close();
|
||||
this.closeConnection();
|
||||
}
|
||||
} else {
|
||||
logger.error("Exception sending meeeage: " + message, e);
|
||||
@@ -257,7 +258,7 @@ public class TcpNioConnection extends AbstractTcpConnection {
|
||||
*/
|
||||
if (this.singleUse && ((!this.server && !intercepted) || (this.server && this.sender == null))) {
|
||||
logger.debug("Closing single use cbannel after inbound message " + this.connectionId);
|
||||
this.close();
|
||||
this.closeConnection();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +276,7 @@ public class TcpNioConnection extends AbstractTcpConnection {
|
||||
rawBuffer.clear();
|
||||
int len = socketChannel.read(rawBuffer);
|
||||
if (len < 0) {
|
||||
this.close();
|
||||
this.closeConnection();
|
||||
throw new IOException("Channel closed");
|
||||
}
|
||||
rawBuffer.flip();
|
||||
@@ -310,7 +311,7 @@ public class TcpNioConnection extends AbstractTcpConnection {
|
||||
logger.error("Exception on Read " +
|
||||
this.getConnectionId() + " " +
|
||||
e.getMessage());
|
||||
this.close();
|
||||
this.closeConnection();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +319,7 @@ public class TcpNioConnection extends AbstractTcpConnection {
|
||||
* Close the socket due to timeout.
|
||||
*/
|
||||
void timeout() {
|
||||
this.close();
|
||||
this.closeConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,15 +69,16 @@ public class TcpInboundGatewayTests {
|
||||
return channel;
|
||||
}
|
||||
});
|
||||
Socket socket = SocketFactory.getDefault().createSocket("localhost", port);
|
||||
socket.getOutputStream().write("Test1\r\n".getBytes());
|
||||
socket.getOutputStream().write("Test2\r\n".getBytes());
|
||||
handler.handleMessage(channel.receive());
|
||||
handler.handleMessage(channel.receive());
|
||||
Socket socket1 = SocketFactory.getDefault().createSocket("localhost", port);
|
||||
socket1.getOutputStream().write("Test1\r\n".getBytes());
|
||||
Socket socket2 = SocketFactory.getDefault().createSocket("localhost", port);
|
||||
socket2.getOutputStream().write("Test2\r\n".getBytes());
|
||||
handler.handleMessage(channel.receive(1000));
|
||||
handler.handleMessage(channel.receive(1000));
|
||||
byte[] bytes = new byte[12];
|
||||
readFully(socket.getInputStream(), bytes);
|
||||
readFully(socket1.getInputStream(), bytes);
|
||||
assertEquals("Echo:Test1\r\n", new String(bytes));
|
||||
readFully(socket.getInputStream(), bytes);
|
||||
readFully(socket2.getInputStream(), bytes);
|
||||
assertEquals("Echo:Test2\r\n", new String(bytes));
|
||||
}
|
||||
|
||||
@@ -134,19 +135,17 @@ public class TcpInboundGatewayTests {
|
||||
return channel;
|
||||
}
|
||||
});
|
||||
Socket socket = SocketFactory.getDefault().createSocket("localhost", port);
|
||||
socket.getOutputStream().write("Test1\r\n".getBytes());
|
||||
socket.getOutputStream().write("Test2\r\n".getBytes());
|
||||
Socket socket1 = SocketFactory.getDefault().createSocket("localhost", port);
|
||||
socket1.getOutputStream().write("Test1\r\n".getBytes());
|
||||
Socket socket2 = SocketFactory.getDefault().createSocket("localhost", port);
|
||||
socket2.getOutputStream().write("Test2\r\n".getBytes());
|
||||
handler.handleMessage(channel.receive());
|
||||
handler.handleMessage(channel.receive());
|
||||
Set<String> results = new HashSet<String>();
|
||||
byte[] bytes = new byte[12];
|
||||
readFully(socket.getInputStream(), bytes);
|
||||
results.add(new String(bytes));
|
||||
readFully(socket.getInputStream(), bytes);
|
||||
results.add(new String(bytes));
|
||||
assertTrue(results.remove("Echo:Test1\r\n"));
|
||||
assertTrue(results.remove("Echo:Test2\r\n"));
|
||||
readFully(socket1.getInputStream(), bytes);
|
||||
assertEquals("Echo:Test1\r\n", new String(bytes));
|
||||
readFully(socket2.getInputStream(), bytes);
|
||||
assertEquals("Echo:Test2\r\n", new String(bytes));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -210,13 +209,14 @@ public class TcpInboundGatewayTests {
|
||||
gateway.setRequestChannel(channel);
|
||||
ServiceActivatingHandler handler = new ServiceActivatingHandler(new FailingService());
|
||||
channel.subscribe(handler);
|
||||
Socket socket = SocketFactory.getDefault().createSocket("localhost", port);
|
||||
socket.getOutputStream().write("Test1\r\n".getBytes());
|
||||
socket.getOutputStream().write("Test2\r\n".getBytes());
|
||||
Socket socket1 = SocketFactory.getDefault().createSocket("localhost", port);
|
||||
socket1.getOutputStream().write("Test1\r\n".getBytes());
|
||||
Socket socket2 = SocketFactory.getDefault().createSocket("localhost", port);
|
||||
socket2.getOutputStream().write("Test2\r\n".getBytes());
|
||||
byte[] bytes = new byte[errorMessage.length() + 2];
|
||||
readFully(socket.getInputStream(), bytes);
|
||||
readFully(socket1.getInputStream(), bytes);
|
||||
assertEquals(errorMessage + "\r\n", new String(bytes));
|
||||
readFully(socket.getInputStream(), bytes);
|
||||
readFully(socket2.getInputStream(), bytes);
|
||||
assertEquals(errorMessage + "\r\n", new String(bytes));
|
||||
}
|
||||
|
||||
|
||||
@@ -518,6 +518,7 @@ public class TcpReceivingChannelAdapterTests {
|
||||
new ObjectOutputStream(socket.getOutputStream()).writeObject("Hello");
|
||||
assertEquals("world!", new ObjectInputStream(socket.getInputStream()).readObject());
|
||||
new ObjectOutputStream(socket.getOutputStream()).writeObject("Test1");
|
||||
|
||||
socket = SocketFactory.getDefault().createSocket("localhost", port);
|
||||
new ObjectOutputStream(socket.getOutputStream()).writeObject("Hello");
|
||||
assertEquals("world!", new ObjectInputStream(socket.getInputStream()).readObject());
|
||||
|
||||
@@ -42,6 +42,8 @@ public class HelloWorldInterceptor extends AbstractTcpConnectionInterceptor {
|
||||
private String hello = "Hello";
|
||||
|
||||
private String world = "world!";
|
||||
|
||||
private boolean closeReceived;
|
||||
|
||||
public HelloWorldInterceptor() {
|
||||
}
|
||||
@@ -86,24 +88,58 @@ public class HelloWorldInterceptor extends AbstractTcpConnectionInterceptor {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onMessage(message);
|
||||
try {
|
||||
return super.onMessage(message);
|
||||
} finally {
|
||||
// on the server side, we don't want to close if we are expecting a response
|
||||
if (!(this.isServer() && this.hasRealSender())) {
|
||||
this.checkDeferredClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Message<?> message) throws Exception {
|
||||
if (!this.negotiated) {
|
||||
if (!this.isServer()) {
|
||||
logger.debug("Sending " + hello);
|
||||
super.send(MessageBuilder.withPayload(hello).build());
|
||||
this.negotiationSemaphore.tryAcquire(this.timeout, TimeUnit.MILLISECONDS);
|
||||
if (!this.negotiated) {
|
||||
throw new MessagingException("Negotiation error");
|
||||
try {
|
||||
if (!this.negotiated) {
|
||||
if (!this.isServer()) {
|
||||
logger.debug("Sending " + hello);
|
||||
super.send(MessageBuilder.withPayload(hello).build());
|
||||
this.negotiationSemaphore.tryAcquire(this.timeout, TimeUnit.MILLISECONDS);
|
||||
if (!this.negotiated) {
|
||||
throw new MessagingException("Negotiation error");
|
||||
}
|
||||
}
|
||||
}
|
||||
super.send(message);
|
||||
} finally {
|
||||
this.checkDeferredClose();
|
||||
}
|
||||
super.send(message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defer the close until we've actually sent the data after negotiation
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.negotiated) {
|
||||
super.close();
|
||||
return;
|
||||
}
|
||||
closeReceived = true;
|
||||
logger.debug("Deferring close");
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the close, if deferred
|
||||
*/
|
||||
private void checkDeferredClose() {
|
||||
if (this.closeReceived) {
|
||||
logger.debug("Executing deferred close");
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-ip="http://www.springframework.org/schema/integration/ip"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/ip http://www.springframework.org/schema/integration/ip/spring-integration-ip-2.0.xsd">
|
||||
|
||||
<bean id="tcpIpUtils" class="org.springframework.integration.ip.util.SocketTestUtils" />
|
||||
|
||||
<int-ip:tcp-connection-factory id="inCFNet"
|
||||
type="server"
|
||||
port="#{tcpIpUtils.findAvailableServerSocket(9000)}"
|
||||
so-timeout="1000"
|
||||
single-use="true"
|
||||
/>
|
||||
|
||||
<int-ip:tcp-inbound-gateway request-channel="echo"
|
||||
connection-factory="inCFNet" />
|
||||
|
||||
<int-ip:tcp-connection-factory id="inCFNio"
|
||||
type="server"
|
||||
port="#{tcpIpUtils.findAvailableServerSocket(9100)}"
|
||||
so-timeout="1000"
|
||||
single-use="true"
|
||||
using-nio="true"
|
||||
/>
|
||||
|
||||
<int-ip:tcp-inbound-gateway request-channel="echo"
|
||||
connection-factory="inCFNio" />
|
||||
|
||||
<int-ip:tcp-connection-factory id="inCFNetRst"
|
||||
type="server"
|
||||
port="#{tcpIpUtils.findAvailableServerSocket(9200)}"
|
||||
so-timeout="1000"
|
||||
single-use="true"
|
||||
so-linger="0"
|
||||
/>
|
||||
|
||||
<int-ip:tcp-inbound-gateway request-channel="echo"
|
||||
connection-factory="inCFNetRst" />
|
||||
|
||||
<int-ip:tcp-connection-factory id="inCFNioRst"
|
||||
type="server"
|
||||
port="#{tcpIpUtils.findAvailableServerSocket(9300)}"
|
||||
so-timeout="1000"
|
||||
single-use="true"
|
||||
using-nio="true"
|
||||
so-linger="0"
|
||||
/>
|
||||
|
||||
<int-ip:tcp-inbound-gateway request-channel="echo"
|
||||
connection-factory="inCFNioRst" />
|
||||
|
||||
<int-ip:tcp-connection-factory id="inCFNetLinger"
|
||||
type="server"
|
||||
port="#{tcpIpUtils.findAvailableServerSocket(9400)}"
|
||||
so-timeout="1000"
|
||||
single-use="true"
|
||||
so-linger="1000"
|
||||
/>
|
||||
|
||||
<int-ip:tcp-inbound-gateway request-channel="echo"
|
||||
connection-factory="inCFNetLinger" />
|
||||
|
||||
<int-ip:tcp-connection-factory id="inCFNioLinger"
|
||||
type="server"
|
||||
port="#{tcpIpUtils.findAvailableServerSocket(9500)}"
|
||||
so-timeout="1000"
|
||||
single-use="true"
|
||||
using-nio="true"
|
||||
so-linger="1000"
|
||||
/>
|
||||
|
||||
<int-ip:tcp-inbound-gateway request-channel="echo"
|
||||
connection-factory="inCFNioLinger" />
|
||||
|
||||
<int:service-activator input-channel="echo" ref="testService"/>
|
||||
|
||||
<bean id="testService" class="org.springframework.integration.ip.tcp.TestService"/>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2002-2010 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
|
||||
*
|
||||
* http://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.tcp.connection;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 2.0.2
|
||||
*
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class SOLingerTests {
|
||||
|
||||
@Autowired
|
||||
private AbstractServerConnectionFactory inCFNet;
|
||||
|
||||
@Autowired
|
||||
private AbstractServerConnectionFactory inCFNio;
|
||||
|
||||
@Autowired
|
||||
private AbstractServerConnectionFactory inCFNetRst;
|
||||
|
||||
@Autowired
|
||||
private AbstractServerConnectionFactory inCFNioRst;
|
||||
|
||||
@Autowired
|
||||
private AbstractServerConnectionFactory inCFNetLinger;
|
||||
|
||||
@Autowired
|
||||
private AbstractServerConnectionFactory inCFNioLinger;
|
||||
|
||||
@Test
|
||||
public void configOk() {}
|
||||
|
||||
@Test
|
||||
public void finReceivedNet() {
|
||||
finReceived(inCFNet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finReceivedNio() {
|
||||
finReceived(inCFNio);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rstReceivedNet() {
|
||||
rstReceived(inCFNetRst);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rstReceivedNio() {
|
||||
rstReceived(inCFNioRst);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finReceivedNetLinger() {
|
||||
finReceived(inCFNetLinger);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finReceivedNioLinger() {
|
||||
finReceived(inCFNioLinger);
|
||||
}
|
||||
|
||||
private void finReceived(AbstractServerConnectionFactory inCF) {
|
||||
int port = inCF.getPort();
|
||||
int n = 0;
|
||||
while (!inCF.isListening()) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
fail("Interrupted");
|
||||
}
|
||||
if (n++ > 100) {
|
||||
fail("Failed to start");
|
||||
}
|
||||
}
|
||||
try {
|
||||
Socket socket = SocketFactory.getDefault().createSocket("localhost", port);
|
||||
String test = "Test\r\n";
|
||||
socket.getOutputStream().write(test.getBytes());
|
||||
byte[] buff = new byte[test.length() + 5];
|
||||
readFully(socket.getInputStream(), buff);
|
||||
assertEquals("echo:" + test, new String(buff));
|
||||
n = socket.getInputStream().read();
|
||||
// we expect an orderly close
|
||||
assertEquals(-1, n);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail("Unexpected Exception " + e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void rstReceived(AbstractServerConnectionFactory inCF) {
|
||||
int port = inCF.getPort();
|
||||
int n = 0;
|
||||
while (!inCF.isListening()) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
fail("Interrupted");
|
||||
}
|
||||
if (n++ > 100) {
|
||||
fail("Failed to start");
|
||||
}
|
||||
}
|
||||
try {
|
||||
Socket socket = SocketFactory.getDefault().createSocket("localhost", port);
|
||||
socket.setSoTimeout(200);
|
||||
String test = "Test\r\n";
|
||||
socket.getOutputStream().write(test.getBytes());
|
||||
byte[] buff = new byte[test.length() + 5];
|
||||
readFully(socket.getInputStream(), buff);
|
||||
assertEquals("echo:" + test, new String(buff));
|
||||
try {
|
||||
n = socket.getInputStream().read();
|
||||
fail("Expected IOException");
|
||||
} catch (IOException ioe) {
|
||||
assertTrue(ioe instanceof SocketTimeoutException);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail("Unexpected Exception " + e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
private void readFully(InputStream is, byte[] buff) throws IOException {
|
||||
for (int i = 0; i < buff.length; i++) {
|
||||
buff[i] = (byte) is.read();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,10 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.ip.IpHeaders;
|
||||
@@ -68,7 +72,8 @@ public class TcpMessageMapperTests {
|
||||
public void testToMessageSequence() throws Exception {
|
||||
|
||||
TcpMessageMapper mapper = new TcpMessageMapper();
|
||||
TcpConnection connection = new AbstractTcpConnection(false) {
|
||||
Socket socket = SocketFactory.getDefault().createSocket();
|
||||
TcpConnection connection = new AbstractTcpConnection(socket, false) {
|
||||
public void run() {
|
||||
}
|
||||
public void send(Message<?> message) throws Exception {
|
||||
|
||||
@@ -235,6 +235,7 @@ public class TcpNioConnectionReadTests {
|
||||
SocketTestUtils.testSendLengthOverflow(port);
|
||||
whileOpen(semaphore, added);
|
||||
assertEquals(1, added.size());
|
||||
assertTrue(semaphore.tryAcquire(10000, TimeUnit.MILLISECONDS));
|
||||
assertTrue(removed.size() > 0);
|
||||
scf.close();
|
||||
}
|
||||
@@ -273,6 +274,7 @@ public class TcpNioConnectionReadTests {
|
||||
SocketTestUtils.testSendStxEtxOverflow(port);
|
||||
whileOpen(semaphore, added);
|
||||
assertEquals(1, added.size());
|
||||
assertTrue(semaphore.tryAcquire(10000, TimeUnit.MILLISECONDS));
|
||||
assertTrue(removed.size() > 0);
|
||||
scf.close();
|
||||
}
|
||||
@@ -311,6 +313,7 @@ public class TcpNioConnectionReadTests {
|
||||
SocketTestUtils.testSendCrLfOverflow(port);
|
||||
whileOpen(semaphore, added);
|
||||
assertEquals(1, added.size());
|
||||
assertTrue(semaphore.tryAcquire(10000, TimeUnit.MILLISECONDS));
|
||||
assertTrue(removed.size() > 0);
|
||||
scf.close();
|
||||
}
|
||||
@@ -349,6 +352,7 @@ public class TcpNioConnectionReadTests {
|
||||
socket.close();
|
||||
whileOpen(semaphore, added);
|
||||
assertEquals(1, added.size());
|
||||
assertTrue(semaphore.tryAcquire(10000, TimeUnit.MILLISECONDS));
|
||||
assertTrue(removed.size() > 0);
|
||||
scf.close();
|
||||
}
|
||||
@@ -416,6 +420,7 @@ public class TcpNioConnectionReadTests {
|
||||
socket.close();
|
||||
whileOpen(semaphore, added);
|
||||
assertEquals(1, added.size());
|
||||
assertTrue(semaphore.tryAcquire(10000, TimeUnit.MILLISECONDS));
|
||||
assertTrue(removed.size() > 0);
|
||||
scf.close();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user