/** * VMware Continuent Tungsten Replicator * Copyright (C) 2015 VMware, Inc. All rights reserved. * * 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. * * Initial developer(s): Robert Hodges * Contributor(s): */ package com.continuent.tungsten.common.sockets; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.security.GeneralSecurityException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.log4j.Logger; import com.continuent.tungsten.common.config.cluster.ConfigurationException; import com.continuent.tungsten.common.security.AuthenticationInfo; /** * Implements a simple echo server that echoes back bytes sent to it. The server * creates a new thread for every incoming request. */ public class EchoServer implements Runnable { private static Logger logger = Logger .getLogger(EchoServer.class); private final String host; private final int port; private final boolean useSSL; private String keystoreAlias = null; private AuthenticationInfo securityInfo = null; private boolean silentFail = false; // Latch to signal server is ready. This prevents race conditions around // start-up so that clients do not connect too quickly. private volatile boolean ready = false; // Operational variables. These are volatile to permit concurrent access. private final ExecutorService pool = Executors .newFixedThreadPool(5); private volatile ServerSocketService socketService; private volatile boolean shutdownRequested = false; private volatile Throwable throwable; private volatile Thread serverThread; /** * Create a new echo server instance. * @throws ConfigurationException */ public EchoServer(String host, int port, boolean useSSL, String serverKeystoreAlias, AuthenticationInfo securityInfo, boolean silentFail) throws ConfigurationException { this.host = host; this.port = port; this.useSSL = useSSL; this.keystoreAlias = serverKeystoreAlias; this.securityInfo = securityInfo; this.silentFail = silentFail; this.checkConsistency(); } private void checkConsistency() throws ConfigurationException { if (this.useSSL && this.keystoreAlias != null && this.securityInfo == null) { throw new ConfigurationException("\n\tError : " + "keystoreAlias is specified but securityInfo is null. " + "It makes it impossible to fetch the key from keyStore " + "from security properties by using the alias."); } } public Throwable getThrowable() { return throwable; } /** * Starts the server, returning when it is ready to receive clients. * * @throws InterruptedException Thrown if wait is interrupted. */ public void start() throws IOException, ConfigurationException, InterruptedException, GeneralSecurityException { // Configure and connect. logger.info("Binding server: host=" + host + " port=" + port + " useSSL=" + useSSL); socketService = new ServerSocketService(); socketService.setAddress(new InetSocketAddress(host, port)); socketService.setUseSSL(useSSL, keystoreAlias, securityInfo); socketService.bind(); // Spawn ourselves in a separate server. logger.info("Spawning server thread"); serverThread = new Thread(this); serverThread.start(); // Wait until we are ready to handle connections, failing if we don't // ready that point in a reasonable interval. boolean ready = awaitReady(3000); if (!ready) { throw new IOException("Server did not become ready!"); } } /** * Loop through answering all incoming requests. */ @Override public void run() { try { doRun(); } catch (SocketTerminationException e) { logger.info("Server stopped by close on socket"); } catch (InterruptedException e) { logger.info("Server stopped by interrupt on thread"); } catch (Throwable t) { throwable = t; logger.info("Echo server failed: " + throwable.getMessage(), t); } finally { pool.shutdown(); socketService.close(); } } /** * Implements basic server processing, which continues until a call to * shutdown or the thread is interrupted. */ private void doRun() throws IOException, InterruptedException { SocketWrapper client; ready = true; while ((shutdownRequested == false) && (client = socketService.accept()) != null) { EchoSocketHandler handler = new EchoSocketHandler(this, client, this.silentFail); pool.execute(handler); } } /** * Wait for the server to enter the ready state. * * @param howLongMillis How many milliseconds to wait for the server to * become ready. * @throws InterruptedException Thrown if thread is interrupted */ private boolean awaitReady(long howLongMillis) throws InterruptedException { long untilMillis = System.currentTimeMillis() + howLongMillis; while (!ready && System.currentTimeMillis() < untilMillis) { Thread.sleep(1); } return ready; } /** Shut down a running server nicely. */ public void shutdown() { logger.info("Shutting down echo server"); shutdownRequested = true; socketService.close(); serverThread.interrupt(); try { serverThread.join(5000); } catch (InterruptedException e) { logger.warn("Unable to shut down echo server"); } } /** Shut down a running server after an error. */ public void shutdownWithError(Throwable t) { this.throwable = t; shutdown(); } } // Local class to implement a simple client handler. class EchoSocketHandler implements Runnable { private static Logger logger = Logger .getLogger(EchoSocketHandler.class); private boolean silentFail = false; EchoServer server; SocketWrapper socketWrapper; EchoSocketHandler(EchoServer server, SocketWrapper socketWrapper, boolean silentFail) { this.server = server; this.socketWrapper = socketWrapper; this.silentFail = silentFail; } @Override public void run() { try { InputStream in = socketWrapper.getInputStream(); OutputStream os = socketWrapper.getOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = in.read(buffer)) > 0 && !Thread.interrupted()) { os.write(buffer, 0, len); } } catch (Throwable t) { if (!silentFail) logger.error("Socket handler failed: " + t.getMessage(), t); server.shutdownWithError(t); } finally { socketWrapper.close(); } } }