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

net.officefloor.server.http.netty.AbstractNettyHttpServer Maven / Gradle / Ivy

There is a newer version: 3.40.0
Show newest version
/*
 * OfficeFloor - http://www.officefloor.net
 * Copyright (C) 2005-2018 Daniel Sagenschneider
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 */
package net.officefloor.server.http.netty;

import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.kqueue.KQueue;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.kqueue.KQueueServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetector.Level;
import net.officefloor.frame.api.manage.ProcessManager;

/**
 * Abstract Netty HTTP server.
 * 
 * @author Daniel Sagenschneider
 */
public abstract class AbstractNettyHttpServer {

	static {
		ResourceLeakDetector.setLevel(Level.DISABLED);
	}

	/**
	 * Maximum length of the request entity.
	 */
	private final int maxRequestEntityLength;

	/**
	 * {@link EventLoopGroup}.
	 */
	private EventLoopGroup loopGroup = null;

	/**
	 * Instantiate.
	 * 
	 * @param maxRequestEntityLength Maximum length of the request entity.
	 */
	public AbstractNettyHttpServer(int maxRequestEntityLength) {
		this.maxRequestEntityLength = maxRequestEntityLength;
	}

	/**
	 * Starts the HTTP Server.
	 * 
	 * @param httpPort   HTTP port.
	 * @param httpsPort  HTTPS secure port.
	 * @param sslContext {@link SSLContext}. May be null.
	 * @throws Exception If fails to start the HTTP Server.
	 */
	public void startHttpServer(int httpPort, int httpsPort, SSLContext sslContext) throws Exception {

		// Configure the HTTP Server
		if (Epoll.isAvailable()) {
			this.startHttpServer(httpPort, httpsPort, sslContext, new EpollEventLoopGroup(),
					EpollServerSocketChannel.class);
		} else if (KQueue.isAvailable()) {
			this.startHttpServer(httpPort, httpsPort, sslContext, new KQueueEventLoopGroup(),
					KQueueServerSocketChannel.class);
		} else {
			this.startHttpServer(httpPort, httpsPort, sslContext, new NioEventLoopGroup(),
					NioServerSocketChannel.class);
		}
	}

	/**
	 * Stops the HTTP server.
	 * 
	 * @throws Exception If fails to stop the HTTP Server.
	 */
	public void stopHttpServer() throws Exception {
		try {
			// Attempt to stop server gracefully
			this.loopGroup.shutdownGracefully().sync();
		} finally {
			// Ensure shut down
			this.loopGroup.shutdownGracefully(0, 1, TimeUnit.SECONDS).sync();
		}
	}

	/**
	 * Services the {@link HttpRequest}.
	 * 
	 * @param context {@link ChannelHandlerContext}.
	 * @param request {@link HttpRequest}.
	 * @return {@link ProcessManager}.
	 * @throws Exception If fails to service the {@link HttpRequest}.
	 */
	protected abstract ProcessManager service(ChannelHandlerContext context, HttpRequest request) throws Exception;

	/**
	 * Starts the HTTP Server.
	 * 
	 * @param httpPort           HTTP port.
	 * @param httpsPort          HTTPS secure port.
	 * @param sslContext         {@link SSLContext}. May be null.
	 * @param loopGroup          {@link EventLoopGroup}.
	 * @param serverChannelClass {@link ServerChannel} {@link Class}.
	 * @throws InterruptedException If fails to start.
	 */
	private void startHttpServer(int httpPort, int httpsPort, SSLContext sslContext, EventLoopGroup loopGroup,
			Class serverChannelClass) throws InterruptedException {
		this.loopGroup = loopGroup;

		// Configure and start the non-secure server
		this.startPortServicing(httpPort, new ServerChannelInitialiser(), serverChannelClass);

		// Configure and start the secure server
		if (httpsPort > 0) {
			ChannelInitializer channelInitialiser = (sslContext != null
					? new SecureServerChannelInitialiser(sslContext)
					: new ServerChannelInitialiser());
			this.startPortServicing(httpsPort, channelInitialiser, serverChannelClass);
		}
	}

	/**
	 * Starts servicing the port.
	 * 
	 * @param port               Port.
	 * @param channelInitializer {@link ChannelInitializer}.
	 * @param serverChannelClass {@link ServerChannel} {@link Class}.
	 * @throws InterruptedException If fails to start.
	 */
	private void startPortServicing(int port, ChannelInitializer channelInitializer,
			Class serverChannelClass) throws InterruptedException {
		ServerBootstrap bootstrap = new ServerBootstrap();
		if (loopGroup instanceof EpollEventLoopGroup) {
			bootstrap.option(EpollChannelOption.SO_REUSEPORT, true);
		}
		bootstrap.option(ChannelOption.SO_BACKLOG, 8192);
		bootstrap.option(ChannelOption.SO_REUSEADDR, true);
		bootstrap.group(loopGroup).channel(serverChannelClass).childHandler(channelInitializer);
		bootstrap.childOption(ChannelOption.SO_REUSEADDR, true);
		bootstrap.bind(new InetSocketAddress(port)).sync();
	}

	/**
	 * Initialises the {@link ChannelPipeline}.
	 * 
	 * @param pipeline {@link ChannelPipeline}.
	 */
	private void initPipeline(ChannelPipeline pipeline) {
		pipeline.addLast("server", new HttpServerCodec());
		pipeline.addLast("aggregator", new HttpObjectAggregator(this.maxRequestEntityLength));
		pipeline.addLast("handler", new ServiceServerHandler());
	}

	/**
	 * Netty Server {@link ChannelInitializer}.
	 */
	private class ServerChannelInitialiser extends ChannelInitializer {

		/*
		 * ================== ChannelInitializer =====================
		 */

		@Override
		protected void initChannel(SocketChannel channel) throws Exception {
			ChannelPipeline pipeline = channel.pipeline();
			AbstractNettyHttpServer.this.initPipeline(pipeline);
		}
	}

	/**
	 * Netty Server {@link ChannelInitializer}.
	 */
	private class SecureServerChannelInitialiser extends ChannelInitializer {

		/**
		 * {@link SslContext}.
		 */
		private final SSLContext sslContext;

		/**
		 * Instantiate.
		 * 
		 * @param sslContext {@link SslContext}.
		 */
		private SecureServerChannelInitialiser(SSLContext sslContext) {
			this.sslContext = sslContext;
		}

		/*
		 * ================== ChannelInitializer =====================
		 */

		@Override
		protected void initChannel(SocketChannel channel) throws Exception {

			// Create the engine
			SSLEngine engine = this.sslContext.createSSLEngine();
			engine.setUseClientMode(false);

			// Create the secure pipeline
			ChannelPipeline pipeline = channel.pipeline();
			pipeline.addLast("ssl", new SslHandler(engine, true));
			AbstractNettyHttpServer.this.initPipeline(pipeline);
		}
	}

	/**
	 * {@link ChannelInboundHandlerAdapter} to invoke service method.
	 */
	private class ServiceServerHandler extends ChannelInboundHandlerAdapter {

		/**
		 * {@link ProcessManager} key.
		 */
		private final AttributeKey key = AttributeKey.valueOf("KEY");

		/*
		 * ================== ChannelInboundHandlerAdapter ============
		 */

		@Override
		public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

			// Ensure flag process manager handling on channel
			Attribute attribute = ctx.channel().attr(this.key);
			ProcessManager manager = attribute.get();
			if (manager == null) {

				// First request, so load the close listener
				ctx.channel().closeFuture().addListener((future) -> {
					ProcessManager processManager = ctx.channel().attr(this.key).get();
					processManager.cancel();
				});
			}

			// Handler
			if (msg instanceof HttpRequest) {
				try {
					HttpRequest request = (HttpRequest) msg;
					manager = AbstractNettyHttpServer.this.service(ctx, request);

					// Register for cancel handling on close
					attribute.set(manager);

				} finally {
					ReferenceCountUtil.release(msg);
				}
			} else {
				ctx.fireChannelRead(msg);
			}
		}

		@Override
		public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
			ctx.flush();
		}

		@Override
		public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
			ctx.close();
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy