org.wso2.carbon.mss.internal.router.NettyHttpService.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.mss.internal.router.NettyHttpService.java

Source

/*
 *  Copyright (c) WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. licenses this file to you 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.wso2.carbon.mss.internal.router;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.AbstractIdleService;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
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 io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.mss.HandlerContext;
import org.wso2.carbon.mss.HttpHandler;

import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Webservice implemented using the netty framework. Implements Guava's Service interface to manage the states
 * of the webservice.
 */
public final class NettyHttpService extends AbstractIdleService {

    private static final Logger log = LoggerFactory.getLogger(NettyHttpService.class);

    private static final int CLOSE_CHANNEL_TIMEOUT = 5;
    private final int bossThreadPoolSize;
    private final int workerThreadPoolSize;
    private final int execThreadPoolSize;
    private final Map<ChannelOption, Object> channelConfigs;
    private final RejectedExecutionHandler rejectedExecutionHandler;
    private final HandlerContext handlerContext;
    private final ChannelGroup channelGroup;
    private final HttpResourceHandler resourceHandler;
    private final Function<ChannelPipeline, ChannelPipeline> pipelineModifier;
    private final int httpChunkLimit;
    private final SSLHandlerFactory sslHandlerFactory;

    private ServerBootstrap bootstrap;
    private InetSocketAddress bindAddress;

    private DefaultEventExecutorGroup eventExecutorGroup;

    /**
     * Initialize NettyHttpService.
     *
     * @param bindAddress              Address for the service to bind to.
     * @param bossThreadPoolSize       Size of the boss thread pool.
     * @param workerThreadPoolSize     Size of the worker thread pool.
     * @param execThreadPoolSize       Size of the thread pool for the executor.
     * @param channelConfigs           Configurations for the server socket channel.
     * @param rejectedExecutionHandler rejection policy for executor.
     * @param urlRewriter              URLRewriter to rewrite incoming URLs.
     * @param httpHandlers             HttpHandlers to handle the calls.
     * @param interceptors             Interceptors to be called before/after request processing by httpHandlers.
     * @deprecated Use {@link NettyHttpService.Builder} instead.
     */
    @Deprecated
    public NettyHttpService(InetSocketAddress bindAddress, int bossThreadPoolSize, int workerThreadPoolSize,
            int execThreadPoolSize, Map<ChannelOption, Object> channelConfigs,
            RejectedExecutionHandler rejectedExecutionHandler, URLRewriter urlRewriter,
            Iterable<? extends HttpHandler> httpHandlers, Iterable<? extends Interceptor> interceptors,
            int httpChunkLimit) {
        this(bindAddress, bossThreadPoolSize, workerThreadPoolSize, execThreadPoolSize, channelConfigs,
                rejectedExecutionHandler, urlRewriter, httpHandlers, interceptors, httpChunkLimit, null, null,
                new ExceptionHandler());
    }

    /**
     * Initialize NettyHttpService. Also includes SSL implementation.
     *
     * @param bindAddress              Address for the service to bind to.
     * @param bossThreadPoolSize       Size of the boss thread pool.
     * @param workerThreadPoolSize     Size of the worker thread pool.
     * @param execThreadPoolSize       Size of the thread pool for the executor.
     * @param channelConfigs           Configurations for the server socket channel.
     * @param rejectedExecutionHandler rejection policy for executor.
     * @param urlRewriter              URLRewriter to rewrite incoming URLs.
     * @param httpHandlers             HttpHandlers to handle the calls.
     * @param interceptors             Interceptors to be called before/after request processing by httpHandlers.
     * @param pipelineModifier         Function used to modify the pipeline.
     * @param sslHandlerFactory        Object used to share SSL certificate details
     * @param exceptionHandler         Handles exceptions from calling handler methods
     */
    private NettyHttpService(InetSocketAddress bindAddress, int bossThreadPoolSize, int workerThreadPoolSize,
            int execThreadPoolSize, Map<ChannelOption, Object> channelConfigs,
            RejectedExecutionHandler rejectedExecutionHandler, URLRewriter urlRewriter,
            Iterable<? extends HttpHandler> httpHandlers, Iterable<? extends Interceptor> interceptors,
            int httpChunkLimit, Function<ChannelPipeline, ChannelPipeline> pipelineModifier,
            SSLHandlerFactory sslHandlerFactory, ExceptionHandler exceptionHandler) {
        this.bindAddress = bindAddress;
        this.bossThreadPoolSize = bossThreadPoolSize;
        this.workerThreadPoolSize = workerThreadPoolSize;
        this.execThreadPoolSize = execThreadPoolSize;
        this.channelConfigs = ImmutableMap.copyOf(channelConfigs);
        this.rejectedExecutionHandler = rejectedExecutionHandler;
        this.channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
        this.resourceHandler = new HttpResourceHandler(httpHandlers, interceptors, urlRewriter, exceptionHandler);
        this.handlerContext = new BasicHandlerContext(this.resourceHandler);
        this.httpChunkLimit = httpChunkLimit;
        this.pipelineModifier = pipelineModifier;
        this.sslHandlerFactory = sslHandlerFactory;
    }

    public static Builder builder() {
        return new Builder();
    }

    /**
     * Bootstrap the pipeline.
     * <ul>
     * <li>Create Execution handler</li>
     * <li>Setup Http resource handler</li>
     * <li>Setup the netty pipeline</li>
     * </ul>
     *
     * @param eventExecutor Executor group
     */
    private void bootStrap(final DefaultEventExecutorGroup eventExecutor) throws Exception {

        NioEventLoopGroup bossGroup = new NioEventLoopGroup(bossThreadPoolSize,
                new ThreadFactoryBuilder().setDaemon(true).setNameFormat("netty-boss-thread").build());

        NioEventLoopGroup workerGroup = new NioEventLoopGroup(workerThreadPoolSize,
                new ThreadFactoryBuilder().setDaemon(true).setNameFormat("netty-worker-thread").build());

        //Server bootstrap with default worker threads (2 * number of cores)
        bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup);
        bootstrap.channel(NioServerSocketChannel.class);
        bootstrap.handler(new LoggingHandler(LogLevel.INFO));

        for (Map.Entry<ChannelOption, Object> entry : channelConfigs.entrySet()) {
            bootstrap.option(entry.getKey(), entry.getValue());
        }

        resourceHandler.init(handlerContext);

        //TODO: Check how to handle this
        /*final ChannelInboundHandlerAdapter connectionTracker = new ChannelInboundHandlerAdapter() {
            
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            channelGroup.add(ctx.channel());
            super.channelActive(ctx);
        }
        };*/
        bootstrap.childHandler(new ServerInitializer(eventExecutor, httpChunkLimit, sslHandlerFactory,
                resourceHandler, pipelineModifier));
    }

    @Override
    protected void startUp() throws Exception {
        log.info("Starting service on address {}...", bindAddress);
        eventExecutorGroup = new DefaultEventExecutorGroup(execThreadPoolSize);
        bootStrap(eventExecutorGroup);
        ChannelFuture channelFuture = bootstrap.bind(bindAddress).sync();
        channelGroup.add(channelFuture.channel());
        bindAddress = ((InetSocketAddress) channelFuture.channel().localAddress());
        log.info("Started service on address {}", bindAddress);
    }

    /**
     * @return port where the service is running.
     */
    public InetSocketAddress getBindAddress() {
        return bindAddress;
    }

    @Override
    protected void shutDown() throws Exception {
        log.info("Stopping service on address {}...", bindAddress);
        eventExecutorGroup.shutdownGracefully();
        try {
            if (!channelGroup.close().await(CLOSE_CHANNEL_TIMEOUT, TimeUnit.SECONDS)) {
                log.warn("Timeout when closing all channels.");
            }
        } finally {
            resourceHandler.destroy(handlerContext);
        }
        log.info("Done stopping service on address {}", bindAddress);
    }

    /**
     * Builder to help create the NettyHttpService.
     */
    public static class Builder {

        private static final int DEFAULT_BOSS_THREAD_POOL_SIZE = 1;
        private static final int DEFAULT_WORKER_THREAD_POOL_SIZE = 10;
        private static final int DEFAULT_CONNECTION_BACKLOG = 1000;
        private static final int DEFAULT_EXEC_HANDLER_THREAD_POOL_SIZE = 60;
        private static final RejectedExecutionHandler DEFAULT_REJECTED_EXECUTION_HANDLER = new ThreadPoolExecutor.CallerRunsPolicy();
        private static final int DEFAULT_HTTP_CHUNK_LIMIT = 150 * 1024 * 1024;

        private final Map<ChannelOption, Object> channelConfigs;

        private Iterable<? extends HttpHandler> handlers;
        private Iterable<? extends Interceptor> interceptors = ImmutableList.of();
        private URLRewriter urlRewriter = null;
        private int bossThreadPoolSize;
        private int workerThreadPoolSize;
        private int execThreadPoolSize;
        private String host;
        private int port;
        private RejectedExecutionHandler rejectedExecutionHandler;
        private int httpChunkLimit;
        private SSLHandlerFactory sslHandlerFactory;
        private Function<ChannelPipeline, ChannelPipeline> pipelineModifier;
        private ExceptionHandler exceptionHandler;

        // Protected constructor to prevent instantiating Builder instance directly.
        protected Builder() {
            bossThreadPoolSize = DEFAULT_BOSS_THREAD_POOL_SIZE;
            workerThreadPoolSize = DEFAULT_WORKER_THREAD_POOL_SIZE;
            execThreadPoolSize = DEFAULT_EXEC_HANDLER_THREAD_POOL_SIZE;
            rejectedExecutionHandler = DEFAULT_REJECTED_EXECUTION_HANDLER;
            httpChunkLimit = DEFAULT_HTTP_CHUNK_LIMIT;
            port = 0;
            channelConfigs = Maps.newHashMap();
            channelConfigs.put(ChannelOption.SO_BACKLOG, DEFAULT_CONNECTION_BACKLOG);
            sslHandlerFactory = null;
            exceptionHandler = new ExceptionHandler();
        }

        /**
         * Modify the pipeline upon build by applying the function.
         *
         * @param function Function that modifies and returns a pipeline.
         * @return builder
         */
        public Builder modifyChannelPipeline(Function<ChannelPipeline, ChannelPipeline> function) {
            this.pipelineModifier = function;
            return this;
        }

        /**
         * Add HttpHandlers that service the request.
         *
         * @param handlers Iterable of HttpHandlers.
         * @return instance of {@code Builder}.
         */
        public Builder addHttpHandlers(Iterable<? extends HttpHandler> handlers) {
            this.handlers = handlers;
            return this;
        }

        /**
         * Set Interceptors to be executed pre and post handler calls. They are executed in the same order as specified
         * by the iterable.
         *
         * @param interceptors Iterable of interceptors.
         * @return an instance of {@code Builder}.
         */
        public Builder setInterceptors(Iterable<? extends Interceptor> interceptors) {
            this.interceptors = interceptors;
            return this;
        }

        /**
         * Set URLRewriter to re-write URL of an incoming request before any handlers or their interceptors are called.
         *
         * @param urlRewriter instance of URLRewriter.
         * @return an instance of {@code Builder}.
         */
        public Builder setUrlRewriter(URLRewriter urlRewriter) {
            this.urlRewriter = urlRewriter;
            return this;
        }

        /**
         * Set size of bossThreadPool in netty default value is 1 if it is not set.
         *
         * @param bossThreadPoolSize size of bossThreadPool.
         * @return an instance of {@code Builder}.
         */
        public Builder setBossThreadPoolSize(int bossThreadPoolSize) {
            this.bossThreadPoolSize = bossThreadPoolSize;
            return this;
        }

        /**
         * Set size of workerThreadPool in netty default value is 10 if it is not set.
         *
         * @param workerThreadPoolSize size of workerThreadPool.
         * @return an instance of {@code Builder}.
         */
        public Builder setWorkerThreadPoolSize(int workerThreadPoolSize) {
            this.workerThreadPoolSize = workerThreadPoolSize;
            return this;
        }

        /**
         * Set size of backlog in netty service - size of accept queue of the TCP stack.
         *
         * @param connectionBacklog backlog in netty server. Default value is 1000.
         * @return an instance of {@code Builder}.
         */
        public Builder setConnectionBacklog(int connectionBacklog) {
            channelConfigs.put(ChannelOption.SO_BACKLOG, connectionBacklog);
            return this;
        }

        /**
         * Sets channel configuration for the the netty service.
         *
         * @param key   Name of the configuration.
         * @param value Value of the configuration.
         * @return an instance of {@code Builder}.
         */
        public Builder setChannelConfig(ChannelOption key, Object value) {
            channelConfigs.put(key, value);
            return this;
        }

        /**
         * Set size of executorThreadPool in netty default value is 60 if it is not set.
         * If the size is {@code 0}, then no executor will be used, hence calls to {@link HttpHandler} would be made
         * from worker threads directly.
         *
         * @param execThreadPoolSize size of workerThreadPool.
         * @return an instance of {@code Builder}.
         */
        public Builder setExecThreadPoolSize(int execThreadPoolSize) {
            this.execThreadPoolSize = execThreadPoolSize;
            return this;
        }

        /**
         * Set RejectedExecutionHandler - rejection policy for executor.
         *
         * @param rejectedExecutionHandler rejectionExecutionHandler.
         * @return an instance of {@code Builder}.
         */
        public Builder setRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler) {
            this.rejectedExecutionHandler = rejectedExecutionHandler;
            return this;
        }

        /**
         * Set the port on which the service should listen to.
         * By default the service will run on a random port.
         *
         * @param port port on which the service should listen to.
         * @return instance of {@code Builder}.
         */
        public Builder setPort(int port) {
            this.port = port;
            return this;
        }

        /**
         * Set the bindAddress for the service. Default value is localhost.
         *
         * @param host bindAddress for the service.
         * @return instance of {@code Builder}.
         */
        public Builder setHost(String host) {
            this.host = host;
            return this;
        }

        public Builder setHttpChunkLimit(int value) {
            this.httpChunkLimit = value;
            return this;
        }

        /**
         * Enable SSL by using the provided SSL information.
         */
        public Builder enableSSL(SSLConfig sslConfig) {
            this.sslHandlerFactory = new SSLHandlerFactory(sslConfig);
            return this;
        }

        public Builder setExceptionHandler(ExceptionHandler exceptionHandler) {
            Preconditions.checkNotNull(exceptionHandler, "exceptionHandler cannot be null");
            this.exceptionHandler = exceptionHandler;
            return this;
        }

        /**
         * @return instance of {@code NettyHttpService}
         */
        public NettyHttpService build() {
            InetSocketAddress bindAddress;
            if (host == null) {
                bindAddress = new InetSocketAddress("localhost", port);
            } else {
                bindAddress = new InetSocketAddress(host, port);
            }

            return new NettyHttpService(bindAddress, bossThreadPoolSize, workerThreadPoolSize, execThreadPoolSize,
                    channelConfigs, rejectedExecutionHandler, urlRewriter, handlers, interceptors, httpChunkLimit,
                    pipelineModifier, sslHandlerFactory, exceptionHandler);
        }
    }
}