/* * Copyright (c) 2013-2014 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 io.werval.server.netty; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import io.werval.api.exceptions.PassivationException; import io.werval.api.exceptions.WervalException; import io.werval.runtime.exceptions.WervalRuntimeException; import io.werval.runtime.util.NamedThreadFactory; import io.werval.spi.ApplicationSPI; import io.werval.spi.dev.DevShellSPI; import io.werval.spi.server.HttpServerAdapter; import io.werval.util.Reflectively; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import static io.werval.api.Mode.PROD; import static io.werval.runtime.ConfigKeys.WERVAL_HTTP_ACCEPTORS; import static io.werval.runtime.ConfigKeys.WERVAL_HTTP_ADDRESS; import static io.werval.runtime.ConfigKeys.WERVAL_HTTP_IOTHREADS; import static io.werval.runtime.ConfigKeys.WERVAL_HTTP_PORT; import static io.werval.runtime.ConfigKeys.WERVAL_SHUTDOWN_QUIETPERIOD; import static io.werval.runtime.ConfigKeys.WERVAL_SHUTDOWN_TIMEOUT; import static io.netty.channel.ChannelOption.SO_KEEPALIVE; import static io.netty.channel.ChannelOption.TCP_NODELAY; /** * Netty HTTP Server. */ @Reflectively.Loaded( by = "DevShell" ) public class NettyServer extends HttpServerAdapter { private static final int DEFAULT_POOL_SIZE = Runtime.getRuntime().availableProcessors(); private final ChannelGroup allChannels; private ServerBootstrap bootstrap; @Reflectively.Invoked( by = "DevShell" ) public NettyServer() { super(); this.allChannels = new DefaultChannelGroup( "werval-netty-server", null ); } public NettyServer( ApplicationSPI app ) { this( app, null ); } public NettyServer( ApplicationSPI app, DevShellSPI devSpi ) { this(); setApplicationSPI( app ); setDevShellSPI( devSpi ); } @Override protected void activateHttpServer() { // Netty Bootstrap bootstrap = new ServerBootstrap(); // I/O Event Loops. // The first is used to handle the accept of new connections and the second will serve the IO of them. int acceptors = app.config().intOptional( WERVAL_HTTP_ACCEPTORS ).orElse( DEFAULT_POOL_SIZE ); int iothreads = app.config().intOptional( WERVAL_HTTP_IOTHREADS ).orElse( DEFAULT_POOL_SIZE ); bootstrap.group( new NioEventLoopGroup( app.mode() == PROD ? acceptors : 1, new NamedThreadFactory( "werval-acceptor" ) ), new NioEventLoopGroup( app.mode() == PROD ? iothreads : 1, new NamedThreadFactory( "werval-io" ) ) ); // Server Channel bootstrap.channel( NioServerSocketChannel.class ); bootstrap.childHandler( new HttpServerChannelInitializer( allChannels, app, devSpi ) ); // See http://www.unixguide.net/network/socketfaq/2.16.shtml bootstrap.option( TCP_NODELAY, true ); // See http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/ bootstrap.option( SO_KEEPALIVE, true ); // Bind String address = app.config().string( WERVAL_HTTP_ADDRESS ); int port = app.config().intNumber( WERVAL_HTTP_PORT ); try { bootstrap.localAddress( address, port ); allChannels.add( bootstrap.bind().sync().channel() ); } catch( InterruptedException ex ) { throw new WervalRuntimeException( "Unable to bind to http(s)://" + address + ":" + port + "/ " + "Port already in use?", ex ); } } @Override protected void passivateHttpServer() { if( bootstrap != null ) { // app.config() can be null if activation failed, allow gracefull shutdown long shutdownQuietPeriod = app.config() == null ? 1000 : app.config().milliseconds( WERVAL_SHUTDOWN_QUIETPERIOD ); long shutdownTimeout = app.config() == null ? 5000 : app.config().milliseconds( WERVAL_SHUTDOWN_TIMEOUT ); // Record all passivation errors here to report them at once at the end List<Exception> passivationErrors = new ArrayList<>(); // Shutdown IO Threads try { bootstrap.childGroup().shutdownGracefully( shutdownQuietPeriod, shutdownTimeout, TimeUnit.MILLISECONDS ).syncUninterruptibly(); } catch( Exception ex ) { passivationErrors.add( new WervalException( "Error while shutting down IO Threads: " + ex.getMessage(), ex ) ); } // Shutdown Accept Threads try { bootstrap.group().shutdownGracefully( shutdownQuietPeriod, shutdownTimeout, TimeUnit.MILLISECONDS ).syncUninterruptibly(); } catch( Exception ex ) { passivationErrors.add( new WervalException( "Error while shutting down Accept Threads: " + ex.getMessage(), ex ) ); } // Force close all channels try { if( !allChannels.isEmpty() ) { allChannels.close(); } } catch( Exception ex ) { passivationErrors.add( new WervalException( "Error while force-closing remaining open channels: " + ex.getMessage(), ex ) ); } finally { allChannels.clear(); } // Report errors if any if( !passivationErrors.isEmpty() ) { PassivationException ex = new PassivationException( "Errors during NettyServer passivation" ); for( Exception passivationError : passivationErrors ) { ex.addSuppressed( passivationError ); } throw ex; } } } }