/* * Copyright (C) 2015 SoftIndex LLC. * * 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 io.datakernel.eventloop; import io.datakernel.annotation.Nullable; import io.datakernel.async.CompletionCallback; import io.datakernel.async.CompletionCallbackFuture; import io.datakernel.async.IgnoreCompletionCallback; import io.datakernel.jmx.EventStats; import io.datakernel.jmx.EventloopJmxMBean; import io.datakernel.jmx.JmxAttribute; import io.datakernel.jmx.ValueStats; import io.datakernel.net.ServerSocketSettings; import io.datakernel.net.SocketSettings; import org.slf4j.Logger; import javax.net.ssl.SSLContext; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import static io.datakernel.eventloop.AsyncSslSocket.wrapServerSocket; import static io.datakernel.eventloop.AsyncTcpSocket.EventHandler; import static io.datakernel.eventloop.AsyncTcpSocketImpl.wrapChannel; import static io.datakernel.net.ServerSocketSettings.DEFAULT_BACKLOG; import static io.datakernel.util.Preconditions.check; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.slf4j.LoggerFactory.getLogger; /** * It is implementation of {@link EventloopServer}. It is non-blocking server which works in eventloop. * The server runs on the one thread, and all events are fired on that thread. This server can listen few * addresses in one time and you can register multiple connections for responding to incoming data. * * @param <S> type of AbstractNioServer which extends from it */ @SuppressWarnings("WeakerAccess, unused") public abstract class AbstractServer<S extends AbstractServer<S>> implements EventloopServer, WorkerServer, EventloopJmxMBean { protected Logger logger = getLogger(this.getClass()); protected final Eventloop eventloop; public static final ServerSocketSettings DEFAULT_SERVER_SOCKET_SETTINGS = ServerSocketSettings.create(DEFAULT_BACKLOG); public static final SocketSettings DEFAULT_SOCKET_SETTINGS = SocketSettings.create(); protected ServerSocketSettings serverSocketSettings = DEFAULT_SERVER_SOCKET_SETTINGS; protected SocketSettings socketSettings = DEFAULT_SOCKET_SETTINGS; private boolean acceptOnce; public interface AcceptFilter { boolean filterAccept(SocketChannel socketChannel, InetSocketAddress localAddress, InetAddress remoteAddress, boolean ssl); } private AcceptFilter acceptFilter; private List<InetSocketAddress> listenAddresses = new ArrayList<>(); // ssl private SSLContext sslContext; private ExecutorService sslExecutor; private List<InetSocketAddress> sslListenAddresses = new ArrayList<>(); private boolean running = false; private List<ServerSocketChannel> serverSocketChannels; // jmx private static final double SMOOTHING_WINDOW = ValueStats.SMOOTHING_WINDOW_1_MINUTE; AbstractServer acceptServer = this; private final AsyncTcpSocketImpl.JmxInspector socketStats = new AsyncTcpSocketImpl.JmxInspector(); private final AsyncTcpSocketImpl.JmxInspector socketStatsSsl = new AsyncTcpSocketImpl.JmxInspector(); private final EventStats accepts = EventStats.create(SMOOTHING_WINDOW); private final EventStats acceptsSsl = EventStats.create(SMOOTHING_WINDOW); private final EventStats filteredAccepts = EventStats.create(SMOOTHING_WINDOW); // region creators & builder methods protected AbstractServer(Eventloop eventloop) { this.eventloop = eventloop; } @SuppressWarnings("unchecked") protected S self() { return (S) this; } public final S withAcceptFilter(AcceptFilter acceptFilter) { this.acceptFilter = acceptFilter; return self(); } public final S withServerSocketSettings(ServerSocketSettings serverSocketSettings) { this.serverSocketSettings = serverSocketSettings; return self(); } public final S withSocketSettings(SocketSettings socketSettings) { this.socketSettings = socketSettings; return self(); } public final S withListenAddresses(List<InetSocketAddress> addresses) { this.listenAddresses = addresses; return self(); } public final S withListenAddresses(InetSocketAddress... addresses) { return withListenAddresses(asList(addresses)); } public final S withListenAddress(InetSocketAddress address) { return withListenAddresses(singletonList(address)); } public final S withListenPort(int port) { return withListenAddress(new InetSocketAddress(port)); } public final S withSslListenAddresses(SSLContext sslContext, ExecutorService sslExecutor, List<InetSocketAddress> addresses) { this.sslContext = sslContext; this.sslExecutor = sslExecutor; this.sslListenAddresses = addresses; return self(); } public final S withSslListenAddresses(SSLContext sslContext, ExecutorService sslExecutor, InetSocketAddress... addresses) { return withSslListenAddresses(sslContext, sslExecutor, asList(addresses)); } public final S withSslListenAddress(SSLContext sslContext, ExecutorService sslExecutor, InetSocketAddress address) { return withSslListenAddresses(sslContext, sslExecutor, singletonList(address)); } public final S withSslListenPort(SSLContext sslContext, ExecutorService sslExecutor, int port) { return withSslListenAddress(sslContext, sslExecutor, new InetSocketAddress(port)); } public final S withAcceptOnce() { return withAcceptOnce(true); } public final S withAcceptOnce(boolean acceptOnce) { this.acceptOnce = acceptOnce; return self(); } public final S withLogger(Logger logger) { this.logger = logger; return self(); } // endregion // eventloop server api @Override public final Eventloop getEventloop() { return eventloop; } @Override public final void listen() throws IOException { check(eventloop.inEventloopThread()); if (running) return; running = true; onListen(); serverSocketChannels = new ArrayList<>(); if (listenAddresses != null && !listenAddresses.isEmpty()) { listenAddresses(listenAddresses, false); logger.info("Listening on {}", listenAddresses); } if (sslListenAddresses != null && !sslListenAddresses.isEmpty()) { listenAddresses(sslListenAddresses, true); logger.info("Listening SSL on {}", sslListenAddresses); } } private void listenAddresses(List<InetSocketAddress> addresses, final boolean ssl) throws IOException { for (final InetSocketAddress address : addresses) { try { ServerSocketChannel serverSocketChannel = eventloop.listen(address, serverSocketSettings, new AcceptCallback() { @Override public void onAccept(SocketChannel socketChannel) { AbstractServer.this.doAccept(socketChannel, address, ssl); } }); serverSocketChannels.add(serverSocketChannel); } catch (IOException e) { logger.error("Can't listen on {}", this, address); close(IgnoreCompletionCallback.create()); throw e; } } } protected void onListen() { } @Override public final void close(CompletionCallback callback) { check(eventloop.inEventloopThread()); if (!running) return; running = false; closeServerSocketChannels(); onClose(callback); } public final Future<?> closeFuture() { final CompletionCallbackFuture future = CompletionCallbackFuture.create(); eventloop.execute(new Runnable() { @Override public void run() { close(new CompletionCallback() { @Override protected void onComplete() { future.setComplete(); } @Override protected void onException(Exception e) { future.setException(e); } }); } }); return future; } protected void onClose(final CompletionCallback completionCallback) { eventloop.post(new Runnable() { @Override public void run() { completionCallback.setComplete(); } }); } public boolean isRunning() { return running; } private void closeServerSocketChannels() { if (serverSocketChannels == null || serverSocketChannels.isEmpty()) { return; } for (Iterator<ServerSocketChannel> it = serverSocketChannels.iterator(); it.hasNext(); ) { ServerSocketChannel serverSocketChannel = it.next(); if (serverSocketChannel == null) { continue; } closeQuietly(serverSocketChannel); it.remove(); } } private void closeQuietly(@Nullable AutoCloseable closeable) { if (closeable == null) return; try { closeable.close(); } catch (Exception e) { } } protected WorkerServer getWorkerServer() { return this; } protected AsyncTcpSocketImpl.Inspector getSocketInspector(InetAddress remoteAddress, InetSocketAddress localAddress, boolean ssl) { return ssl ? socketStatsSsl : socketStats; } protected void onAccept(SocketChannel socketChannel, InetSocketAddress localAddress, InetAddress remoteAddress, boolean ssl) { accepts.recordEvent(); if (ssl) { acceptsSsl.recordEvent(); } } protected void onFilteredAccept(SocketChannel socketChannel, InetSocketAddress localAddress, InetAddress remoteAddress, boolean ssl) { filteredAccepts.recordEvent(); } private void doAccept(final SocketChannel socketChannel, final InetSocketAddress localAddress, final boolean ssl) { assert eventloop.inEventloopThread(); final InetAddress remoteAddress; try { remoteAddress = ((InetSocketAddress) socketChannel.getRemoteAddress()).getAddress(); } catch (IOException e) { closeQuietly(socketChannel); return; } if (acceptFilter != null && acceptFilter.filterAccept(socketChannel, localAddress, remoteAddress, ssl)) { onFilteredAccept(socketChannel, localAddress, remoteAddress, ssl); return; } final WorkerServer workerServer = getWorkerServer(); final Eventloop workerServerEventloop = workerServer.getEventloop(); if (workerServerEventloop == this.eventloop) { workerServer.doAccept(socketChannel, localAddress, remoteAddress, ssl, socketSettings); } else { onAccept(socketChannel, localAddress, remoteAddress, ssl); workerServerEventloop.execute(new Runnable() { @Override public void run() { workerServer.doAccept(socketChannel, localAddress, remoteAddress, ssl, socketSettings); } }); } if (acceptOnce) { close(IgnoreCompletionCallback.create()); } } @Override public void doAccept(SocketChannel socketChannel, InetSocketAddress localAddress, InetAddress remoteAddress, final boolean ssl, SocketSettings socketSettings) { assert eventloop.inEventloopThread(); onAccept(socketChannel, localAddress, remoteAddress, ssl); final AsyncTcpSocketImpl asyncTcpSocketImpl = wrapChannel(eventloop, socketChannel, socketSettings) .withInspector(getSocketInspector(remoteAddress, localAddress, ssl)); final AsyncTcpSocket asyncTcpSocket = ssl ? wrapServerSocket(eventloop, asyncTcpSocketImpl, sslContext, sslExecutor) : asyncTcpSocketImpl; asyncTcpSocket.setEventHandler(createSocketHandler(asyncTcpSocket)); asyncTcpSocketImpl.register(); } protected abstract EventHandler createSocketHandler(AsyncTcpSocket asyncTcpSocket); private boolean isInetAddressAny(InetSocketAddress listenAddress) { return listenAddress.getAddress().isAnyLocalAddress(); } @JmxAttribute(extraSubAttributes = "totalCount") public final EventStats getAccepts() { return acceptServer.listenAddresses.isEmpty() ? null : accepts; } @JmxAttribute public EventStats getAcceptsSsl() { return acceptServer.sslListenAddresses.isEmpty() ? null : acceptsSsl; } @JmxAttribute public EventStats getFilteredAccepts() { return acceptFilter == null ? null : filteredAccepts; } @JmxAttribute public AsyncTcpSocketImpl.JmxInspector getSocketStats() { return this instanceof PrimaryServer || acceptServer.listenAddresses.isEmpty() ? null : socketStats; } @JmxAttribute public AsyncTcpSocketImpl.JmxInspector getSocketStatsSsl() { return this instanceof PrimaryServer || acceptServer.sslListenAddresses.isEmpty() ? null : socketStatsSsl; } }