INT-3800: Expose Actual Port Chosen When Random
JIRA: https://jira.spring.io/browse/INT-3800 If the server port is 0, return the actual port after the factory is started. Also add more TCP Test Diagnostics.
This commit is contained in:
committed by
Artem Bilan
parent
4ab25121af
commit
bcfc4d88c1
@@ -18,6 +18,7 @@ package org.springframework.integration.ip.tcp.connection;
|
||||
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
|
||||
import org.springframework.integration.context.OrderlyShutdownCapable;
|
||||
@@ -32,8 +33,8 @@ import org.springframework.util.Assert;
|
||||
* @author Gary Russell
|
||||
* @since 2.0
|
||||
*/
|
||||
public abstract class AbstractServerConnectionFactory
|
||||
extends AbstractConnectionFactory implements SchedulingAwareRunnable, OrderlyShutdownCapable {
|
||||
public abstract class AbstractServerConnectionFactory extends AbstractConnectionFactory
|
||||
implements TcpServerConnectionFactory, SchedulingAwareRunnable, OrderlyShutdownCapable {
|
||||
|
||||
private static final int DEFAULT_BACKLOG = 5;
|
||||
|
||||
@@ -60,6 +61,11 @@ public abstract class AbstractServerConnectionFactory
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getServerSocketAddress() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
@@ -54,6 +55,25 @@ public class TcpNetServerConnectionFactory extends AbstractServerConnectionFacto
|
||||
return "tcp-net-server-connection-factory";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
int port = super.getPort();
|
||||
if (port == 0 && this.serverSocket != null) {
|
||||
return this.serverSocket.getLocalPort();
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getServerSocketAddress() {
|
||||
if (this.serverSocket != null) {
|
||||
return this.serverSocket.getLocalSocketAddress();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If no listener registers, exits.
|
||||
* Accepts incoming connections and creates TcpConnections for each new connection.
|
||||
@@ -70,11 +90,11 @@ public class TcpNetServerConnectionFactory extends AbstractServerConnectionFacto
|
||||
}
|
||||
try {
|
||||
if (getLocalAddress() == null) {
|
||||
theServerSocket = createServerSocket(getPort(), getBacklog(), null);
|
||||
theServerSocket = createServerSocket(super.getPort(), getBacklog(), null);
|
||||
}
|
||||
else {
|
||||
InetAddress whichNic = InetAddress.getByName(getLocalAddress());
|
||||
theServerSocket = createServerSocket(getPort(), getBacklog(), whichNic);
|
||||
theServerSocket = createServerSocket(super.getPort(), getBacklog(), whichNic);
|
||||
}
|
||||
getTcpSocketSupport().postProcessServerSocket(theServerSocket);
|
||||
this.serverSocket = theServerSocket;
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.CancelledKeyException;
|
||||
import java.nio.channels.ClosedSelectorException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
@@ -66,6 +67,34 @@ public class TcpNioServerConnectionFactory extends AbstractServerConnectionFacto
|
||||
return "tcp-nio-server-connection-factory";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
int port = super.getPort();
|
||||
if (port == 0 && this.serverChannel != null) {
|
||||
try {
|
||||
SocketAddress address = this.serverChannel.getLocalAddress();
|
||||
if (address instanceof InetSocketAddress) {
|
||||
port = ((InetSocketAddress) address).getPort();
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getServerSocketAddress() {
|
||||
if (this.serverChannel != null) {
|
||||
try {
|
||||
return this.serverChannel.getLocalAddress();
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If no listener registers, exits.
|
||||
* Accepts incoming connections and creates TcpConnections for each new connection.
|
||||
@@ -81,11 +110,8 @@ public class TcpNioServerConnectionFactory extends AbstractServerConnectionFacto
|
||||
}
|
||||
try {
|
||||
this.serverChannel = ServerSocketChannel.open();
|
||||
int port = getPort();
|
||||
int port = super.getPort();
|
||||
getTcpSocketSupport().postProcessServerSocket(this.serverChannel.socket());
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Listening on port " + port);
|
||||
}
|
||||
this.serverChannel.configureBlocking(false);
|
||||
if (getLocalAddress() == null) {
|
||||
this.serverChannel.socket().bind(new InetSocketAddress(port), Math.abs(getBacklog()));
|
||||
@@ -94,6 +120,9 @@ public class TcpNioServerConnectionFactory extends AbstractServerConnectionFacto
|
||||
InetAddress whichNic = InetAddress.getByName(getLocalAddress());
|
||||
this.serverChannel.socket().bind(new InetSocketAddress(whichNic, port), Math.abs(getBacklog()));
|
||||
}
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Listening on port " + getPort());
|
||||
}
|
||||
final Selector selector = Selector.open();
|
||||
this.serverChannel.register(selector, SelectionKey.OP_ACCEPT);
|
||||
setListening(true);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2015 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 java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* Connection factories that act as TCP servers, listening for incoming connections.
|
||||
* @author Gary Russell
|
||||
* @since 4.2
|
||||
*
|
||||
*/
|
||||
public interface TcpServerConnectionFactory {
|
||||
|
||||
/**
|
||||
* Return the port this server is listening on.
|
||||
* If the factory is configured to listen on a random port (0), this
|
||||
* will return the actual port after the factory is started. It may
|
||||
* return the previous value if the factory is stopped.
|
||||
* @return the port.
|
||||
*/
|
||||
int getPort();
|
||||
|
||||
/**
|
||||
* Return the {@link SocketAddress} that the underlying {@code ServerSocket}
|
||||
* is bound to.
|
||||
* @return the socket address.
|
||||
*/
|
||||
SocketAddress getServerSocketAddress();
|
||||
|
||||
}
|
||||
@@ -52,7 +52,9 @@ import javax.net.ServerSocketFactory;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.log4j.Level;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@@ -69,6 +71,7 @@ import org.springframework.integration.ip.tcp.connection.TcpConnectionSupport;
|
||||
import org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory;
|
||||
import org.springframework.integration.ip.tcp.connection.TcpNioClientConnectionFactory;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.test.rule.Log4jLevelAdjuster;
|
||||
import org.springframework.integration.test.util.SocketUtils;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
@@ -81,6 +84,10 @@ import org.springframework.messaging.support.GenericMessage;
|
||||
*/
|
||||
public class TcpOutboundGatewayTests {
|
||||
|
||||
@Rule
|
||||
public Log4jLevelAdjuster adjuster = new Log4jLevelAdjuster(Level.TRACE,
|
||||
"org.springframework.integration.ip.tcp");
|
||||
|
||||
private final Log logger = LogFactory.getLog(this.getClass());
|
||||
|
||||
@Test
|
||||
|
||||
@@ -24,6 +24,8 @@ import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -36,10 +38,9 @@ import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.integration.ip.tcp.TcpReceivingChannelAdapter;
|
||||
import org.springframework.integration.ip.util.TestingUtilities;
|
||||
import org.springframework.integration.test.util.SocketUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
@@ -50,7 +51,18 @@ import org.springframework.integration.test.util.SocketUtils;
|
||||
public class ConnectionFactoryTests {
|
||||
|
||||
@Test
|
||||
public void testObtainConnectionIds() throws Exception {
|
||||
public void testObtainConnectionIdsNet() throws Exception {
|
||||
TcpNetServerConnectionFactory serverFactory = new TcpNetServerConnectionFactory(0);
|
||||
testObtainConnectionIds(serverFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObtainConnectionIdsNio() throws Exception {
|
||||
TcpNioServerConnectionFactory serverFactory = new TcpNioServerConnectionFactory(0);
|
||||
testObtainConnectionIds(serverFactory);
|
||||
}
|
||||
|
||||
public void testObtainConnectionIds(AbstractServerConnectionFactory serverFactory) throws Exception {
|
||||
final List<TcpConnectionEvent> events =
|
||||
Collections.synchronizedList(new ArrayList<TcpConnectionEvent>());
|
||||
ApplicationEventPublisher publisher = new ApplicationEventPublisher() {
|
||||
@@ -62,17 +74,16 @@ public class ConnectionFactoryTests {
|
||||
|
||||
@Override
|
||||
public void publishEvent(Object event) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
int port = SocketUtils.findAvailableServerSocket();
|
||||
TcpNetServerConnectionFactory serverFactory = new TcpNetServerConnectionFactory(port);
|
||||
serverFactory.setBeanName("serverFactory");
|
||||
serverFactory.setApplicationEventPublisher(publisher);
|
||||
serverFactory = spy(serverFactory);
|
||||
final CountDownLatch serverConnectionInitLatch = new CountDownLatch(1);
|
||||
doAnswer(new Answer<Object>() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
Object result = invocation.callRealMethod();
|
||||
serverConnectionInitLatch.countDown();
|
||||
@@ -83,8 +94,10 @@ public class ConnectionFactoryTests {
|
||||
adapter.setConnectionFactory(serverFactory);
|
||||
adapter.start();
|
||||
TestingUtilities.waitListening(serverFactory, null);
|
||||
int port = serverFactory.getPort();
|
||||
TcpNetClientConnectionFactory clientFactory = new TcpNetClientConnectionFactory("localhost", port);
|
||||
clientFactory.registerListener(new TcpListener() {
|
||||
@Override
|
||||
public boolean onMessage(Message<?> message) {
|
||||
return false;
|
||||
}
|
||||
@@ -105,11 +118,13 @@ public class ConnectionFactoryTests {
|
||||
Thread.sleep(1000);
|
||||
clients = clientFactory.getOpenConnectionIds();
|
||||
assertEquals(0, clients.size());
|
||||
assertEquals(6, events.size()); // OPEN, CLOSE, EXCEPTION for each side
|
||||
int expected = serverFactory instanceof TcpNetServerConnectionFactory ? 6// OPEN, CLOSE, EXCEPTION for each side
|
||||
: 4; //OPEN, CLOSE
|
||||
assertEquals(expected, events.size());
|
||||
|
||||
FooEvent event = new FooEvent(client, "foo");
|
||||
client.publishEvent(event);
|
||||
assertEquals(7, events.size());
|
||||
assertEquals(expected + 1, events.size());
|
||||
|
||||
try {
|
||||
event = new FooEvent(mock(TcpConnectionSupport.class), "foo");
|
||||
@@ -119,6 +134,13 @@ public class ConnectionFactoryTests {
|
||||
catch (IllegalArgumentException e) {
|
||||
assertTrue("Can only publish events with this as the source".equals(e.getMessage()));
|
||||
}
|
||||
|
||||
SocketAddress address = serverFactory.getServerSocketAddress();
|
||||
if (address instanceof InetSocketAddress) {
|
||||
InetSocketAddress inetAddress = (InetSocketAddress) address;
|
||||
assertEquals(port, inetAddress.getPort());
|
||||
}
|
||||
serverFactory.stop();
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
|
||||
@@ -166,6 +166,11 @@ A simple server connection factory that uses `java.net.Socket` connections.
|
||||
|
||||
A simple server connection factory that uses `java.nio.channel.SocketChannel` connections.
|
||||
|
||||
NOTE: Starting with Spring Integration _version 4.2_, if the server is configured to listen on a random port (0),
|
||||
the actual port chosen by the OS can be obtained using `getPort()`.
|
||||
Also, `getServerSocketAddress()` is available to get the complete `SocketAddress`.
|
||||
See the javadocs for the `TcpServerConnectionFactory` interface for more information.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<int-ip:tcp-connection-factory id="client"
|
||||
|
||||
@@ -145,6 +145,15 @@ If you are using the serializers directly within user code, you may have to `flu
|
||||
`TcpConnectionServerExceptionEvent` s are now published whenever an unexpected exception occurs on a TCP server socket (also added to 4.1.3, 4.0.7).
|
||||
See <<tcp-events>> for more information.
|
||||
|
||||
[[x4.2-tcp-server-port]]
|
||||
===== TCP Server Port
|
||||
|
||||
If a TCP server socket factory is configured to listen on a random port, the actual port chosen by the OS can now
|
||||
be obtained using `getPort()`.
|
||||
`getServerSocketAddress()` is also available.
|
||||
|
||||
See <<connection-factories>> for more information.
|
||||
|
||||
[[x4.2-tcp-gw-rto]]
|
||||
===== TCP Gateway Remote Timeout
|
||||
|
||||
|
||||
Reference in New Issue
Block a user