/* * Copyright 2001-2016 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.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.util.Date; import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.task.TaskRejectedException; import org.springframework.integration.context.OrderlyShutdownCapable; import org.springframework.scheduling.SchedulingAwareRunnable; import org.springframework.scheduling.TaskScheduler; import org.springframework.util.Assert; /** * Base class for all server connection factories. Server connection factories * listen on a port for incoming connections and create new TcpConnection objects * for each new connection. * * @author Gary Russell * @since 2.0 */ public abstract class AbstractServerConnectionFactory extends AbstractConnectionFactory implements TcpServerConnectionFactory, SchedulingAwareRunnable, OrderlyShutdownCapable { private static final int DEFAULT_BACKLOG = 5; private volatile boolean listening; private volatile String localAddress; private volatile int backlog = DEFAULT_BACKLOG; private volatile boolean shuttingDown; /** * The port on which the factory will listen. * * @param port The port. */ public AbstractServerConnectionFactory(int port) { super(port); } @Override public boolean isLongLived() { return true; } @Override public SocketAddress getServerSocketAddress() { return null; } @Override public void start() { synchronized (this.lifecycleMonitor) { if (!isActive()) { this.setActive(true); this.shuttingDown = false; getTaskExecutor().execute(this); } } super.start(); } /** * Not supported because the factory manages multiple connections and this * method cannot discriminate. */ @Override public TcpConnection getConnection() throws Exception { throw new UnsupportedOperationException("Getting a connection from a server factory is not supported"); } /** * @param listening the listening to set */ protected void setListening(boolean listening) { this.listening = listening; } /** * * @return true if the server is listening on the port. */ public boolean isListening() { return this.listening; } protected boolean isShuttingDown() { return this.shuttingDown; } /** * Transfers attributes such as (de)serializer, mapper etc to a new connection. * For single use sockets, enforces a socket timeout (default 10 seconds) to prevent * DoS attacks. * @param connection The new connection. * @param socket The new socket. */ protected void initializeConnection(TcpConnectionSupport connection, Socket socket) { TcpListener listener = getListener(); if (listener != null) { connection.registerListener(listener); } connection.registerSender(getSender()); connection.setMapper(getMapper()); connection.setDeserializer(getDeserializer()); connection.setSerializer(getSerializer()); /* * If we are configured * for single use; need to enforce a timeout on the socket so we will close * if the client connects, but sends nothing. (Protect against DoS). * Behavior can be overridden by explicitly setting the timeout to zero. */ if (isSingleUse() && getSoTimeout() < 0) { try { socket.setSoTimeout(DEFAULT_REPLY_TIMEOUT); } catch (SocketException e) { logger.error("Error setting default reply timeout", e); } } } protected void postProcessServerSocket(ServerSocket serverSocket) { getTcpSocketSupport().postProcessServerSocket(serverSocket); } /** * * @return the localAddress */ public String getLocalAddress() { return this.localAddress; } /** * Used on multi-homed systems to enforce the server to listen * on a specfic network address instead of all network adapters. * @param localAddress the ip address of the required adapter. */ public void setLocalAddress(String localAddress) { this.localAddress = localAddress; } /** * The number of sockets in the server connection backlog. * @return The backlog. */ public int getBacklog() { return this.backlog; } /** * The number of sockets in the connection backlog. Default 5; * increase if you expect high connection rates. * @param backlog The backlog to set. */ public void setBacklog(int backlog) { Assert.isTrue(backlog >= 0, "You cannot set backlog negative"); this.backlog = backlog; } @Override public int beforeShutdown() { this.shuttingDown = true; return 0; } @Override public int afterShutdown() { stop(); return 0; } protected void publishServerExceptionEvent(Exception e) { if (getApplicationEventPublisher() != null) { getApplicationEventPublisher().publishEvent(new TcpConnectionServerExceptionEvent(this, e)); } } protected void publishServerListeningEvent(int port) { final ApplicationEventPublisher eventPublisher = getApplicationEventPublisher(); if (eventPublisher != null) { final TcpConnectionServerListeningEvent event = new TcpConnectionServerListeningEvent(this, port); TaskScheduler taskScheduler = this.getTaskScheduler(); if (taskScheduler != null) { try { taskScheduler.schedule((Runnable) () -> eventPublisher.publishEvent(event), new Date()); } catch (TaskRejectedException e) { eventPublisher.publishEvent(event); } } else { eventPublisher.publishEvent(event); } } } }