All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.cask.http.NettyHttpService Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
/*
 * Copyright © 2014 Cask Data, Inc.
 *
 * 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 co.cask.http;

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 org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.ChannelUpstreamHandler;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.http.HttpContentCompressor;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
import org.jboss.netty.handler.execution.ExecutionHandler;
import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor;
import org.jboss.netty.util.internal.ExecutorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;


/**
 * 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 long execThreadKeepAliveSecs;
  private final Map channelConfigs;
  private final RejectedExecutionHandler rejectedExecutionHandler;
  private final HandlerContext handlerContext;
  private final ChannelGroup channelGroup;
  private final HttpResourceHandler resourceHandler;
  private final Function pipelineModifier;
  private final int httpChunkLimit;
  private final SSLHandlerFactory sslHandlerFactory;

  private ServerBootstrap bootstrap;
  private ExecutionHandler executionHandler;
  private InetSocketAddress bindAddress;

  /**
   * 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 execThreadKeepAliveSecs  maximum time that excess idle threads will wait for new tasks before terminating.
   * @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 handlerHooks Hooks 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, long execThreadKeepAliveSecs,
                          Map channelConfigs,
                          RejectedExecutionHandler rejectedExecutionHandler, URLRewriter urlRewriter,
                          Iterable httpHandlers,
                          Iterable handlerHooks, int httpChunkLimit) {
    this(bindAddress, bossThreadPoolSize, workerThreadPoolSize, execThreadPoolSize, execThreadKeepAliveSecs,
         channelConfigs, rejectedExecutionHandler, urlRewriter, httpHandlers, handlerHooks, 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 execThreadKeepAliveSecs  maximum time that excess idle threads will wait for new tasks before terminating.
   * @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 handlerHooks Hooks 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, long execThreadKeepAliveSecs,
                           Map channelConfigs,
                           RejectedExecutionHandler rejectedExecutionHandler, URLRewriter urlRewriter,
                           Iterable httpHandlers,
                           Iterable handlerHooks, int httpChunkLimit,
                           Function pipelineModifier,
                           SSLHandlerFactory sslHandlerFactory, ExceptionHandler exceptionHandler) {
    this.bindAddress = bindAddress;
    this.bossThreadPoolSize = bossThreadPoolSize;
    this.workerThreadPoolSize = workerThreadPoolSize;
    this.execThreadPoolSize = execThreadPoolSize;
    this.execThreadKeepAliveSecs = execThreadKeepAliveSecs;
    this.channelConfigs = ImmutableMap.copyOf(channelConfigs);
    this.rejectedExecutionHandler = rejectedExecutionHandler;
    this.channelGroup = new DefaultChannelGroup();
    this.resourceHandler = new HttpResourceHandler(httpHandlers, handlerHooks, urlRewriter, exceptionHandler);
    this.handlerContext = new BasicHandlerContext(this.resourceHandler);
    this.httpChunkLimit = httpChunkLimit;
    this.pipelineModifier = pipelineModifier;
    this.sslHandlerFactory = sslHandlerFactory;
  }

  private boolean isSSLEnabled() {
    return this.sslHandlerFactory != null;
  }

  /**
   * Create Execution handlers with threadPoolExecutor.
   *
   * @param threadPoolSize size of threadPool
   * @param threadKeepAliveSecs  maximum time that excess idle threads will wait for new tasks before terminating.
   * @return instance of {@code ExecutionHandler}.
   */
  private ExecutionHandler createExecutionHandler(int threadPoolSize, long threadKeepAliveSecs) {

    ThreadFactory threadFactory = new ThreadFactory() {
      private final ThreadGroup threadGroup = new ThreadGroup("netty-executor-thread");
      private final AtomicLong count = new AtomicLong(0);

      @Override
      public Thread newThread(Runnable r) {
        Thread t = new Thread(threadGroup, r, String.format("netty-executor-%d", count.getAndIncrement()));
        t.setDaemon(true);
        return t;
      }
    };

    //Create ExecutionHandler
    ThreadPoolExecutor threadPoolExecutor =
      new OrderedMemoryAwareThreadPoolExecutor(threadPoolSize, 0, 0,
                                               threadKeepAliveSecs, TimeUnit.SECONDS, threadFactory);
    threadPoolExecutor.setRejectedExecutionHandler(rejectedExecutionHandler);
    return new ExecutionHandler(threadPoolExecutor);
  }

  /**
   * Bootstrap the pipeline.
   * 
    *
  • Create Execution handler
  • *
  • Setup Http resource handler
  • *
  • Setup the netty pipeline
  • *
* * @param threadPoolSize Size of threadpool in threadpoolExecutor * @param threadKeepAliveSecs maximum time that excess idle threads will wait for new tasks before terminating. */ private void bootStrap(int threadPoolSize, long threadKeepAliveSecs) throws Exception { executionHandler = (threadPoolSize) > 0 ? createExecutionHandler(threadPoolSize, threadKeepAliveSecs) : null; Executor bossExecutor = Executors.newFixedThreadPool(bossThreadPoolSize, new ThreadFactoryBuilder() .setDaemon(true) .setNameFormat("netty-boss-thread-%d") .build()); Executor workerExecutor = Executors.newFixedThreadPool(workerThreadPoolSize, new ThreadFactoryBuilder() .setDaemon(true) .setNameFormat("netty-worker-thread-%d") .build()); //Server bootstrap with default worker threads (2 * number of cores) bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(bossExecutor, bossThreadPoolSize, workerExecutor, workerThreadPoolSize)); bootstrap.setOptions(channelConfigs); resourceHandler.init(handlerContext); final ChannelUpstreamHandler connectionTracker = new SimpleChannelUpstreamHandler() { @Override public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception { channelGroup.add(e.getChannel()); super.handleUpstream(ctx, e); } }; bootstrap.setPipelineFactory(new ChannelPipelineFactory() { @Override public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = Channels.pipeline(); if (isSSLEnabled()) { // Add SSLHandler if SSL is enabled pipeline.addLast("ssl", sslHandlerFactory.create()); } pipeline.addLast("tracker", connectionTracker); pipeline.addLast("compressor", new HttpContentCompressor()); pipeline.addLast("encoder", new HttpResponseEncoder()); pipeline.addLast("decoder", new HttpRequestDecoder()); pipeline.addLast("router", new RequestRouter(resourceHandler, httpChunkLimit, isSSLEnabled())); if (executionHandler != null) { pipeline.addLast("executor", executionHandler); } pipeline.addLast("dispatcher", new HttpDispatcher()); if (pipelineModifier != null) { pipeline = pipelineModifier.apply(pipeline); } return pipeline; } }); } public static Builder builder() { return new Builder(); } @Override protected void startUp() throws Exception { LOG.info("Starting service on address {}...", bindAddress); bootStrap(execThreadPoolSize, execThreadKeepAliveSecs); Channel channel = bootstrap.bind(bindAddress); channelGroup.add(channel); bindAddress = ((InetSocketAddress) channel.getLocalAddress()); 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); try { bootstrap.shutdown(); if (!channelGroup.close().await(CLOSE_CHANNEL_TIMEOUT, TimeUnit.SECONDS)) { LOG.warn("Timeout when closing all channels."); } } finally { resourceHandler.destroy(handlerContext); bootstrap.releaseExternalResources(); if (executionHandler != null) { executionHandler.releaseExternalResources(); ExecutorUtil.terminate(executionHandler.getExecutor()); } } 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 long DEFAULT_EXEC_HANDLER_THREAD_KEEP_ALIVE_TIME_SECS = 60L; 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 channelConfigs; private Iterable handlers; private Iterable handlerHooks = ImmutableList.of(); private URLRewriter urlRewriter = null; private int bossThreadPoolSize; private int workerThreadPoolSize; private int execThreadPoolSize; private String host; private int port; private long execThreadKeepAliveSecs; private RejectedExecutionHandler rejectedExecutionHandler; private int httpChunkLimit; private SSLHandlerFactory sslHandlerFactory; private Function 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; execThreadKeepAliveSecs = DEFAULT_EXEC_HANDLER_THREAD_KEEP_ALIVE_TIME_SECS; rejectedExecutionHandler = DEFAULT_REJECTED_EXECUTION_HANDLER; httpChunkLimit = DEFAULT_HTTP_CHUNK_LIMIT; port = 0; channelConfigs = Maps.newHashMap(); channelConfigs.put("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 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 handlers) { this.handlers = handlers; return this; } /** * Set HandlerHooks to be executed pre and post handler calls. They are executed in the same order as specified * by the iterable. * * @param handlerHooks Iterable of HandlerHooks. * @return an instance of {@code Builder}. */ public Builder setHandlerHooks(Iterable handlerHooks) { this.handlerHooks = handlerHooks; return this; } /** * Set URLRewriter to re-write URL of an incoming request before any handlers or their hooks 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("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}. * @see org.jboss.netty.channel.ChannelConfig * @see org.jboss.netty.channel.socket.ServerSocketChannelConfig */ public Builder setChannelConfig(String 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 threadKeepAliveSeconds - maximum time that excess idle threads will wait for new tasks before terminating. * Default value is 60 seconds. * * @param threadKeepAliveSecs thread keep alive seconds. * @return an instance of {@code Builder}. */ public Builder setExecThreadKeepAliveSeconds(long threadKeepAliveSecs) { this.execThreadKeepAliveSecs = threadKeepAliveSecs; 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, execThreadKeepAliveSecs, channelConfigs, rejectedExecutionHandler, urlRewriter, handlers, handlerHooks, httpChunkLimit, pipelineModifier, sslHandlerFactory, exceptionHandler); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy