/** * */ package org.minnal.core.server; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; 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.DefaultHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseEncoder; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.util.AttributeKey; import java.net.InetSocketAddress; import java.net.URI; import org.minnal.core.Lifecycle; import org.minnal.core.Router; import org.minnal.core.config.ConnectorConfiguration; import org.minnal.utils.http.HttpUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author ganeshs * */ @Sharable public abstract class AbstractHttpConnector extends SimpleChannelInboundHandler<FullHttpRequest> implements Lifecycle { private ServerBootstrap bootstrap; private Router router; private ConnectorConfiguration configuration; private ConnectorListener listener; private static final Logger logger = LoggerFactory.getLogger(AbstractHttpConnector.class); public static final String REQUEST_PROPERTY_REMOTE_ADDR = "org.minnal.container.netty.request.property.remote_addr"; public static final AttributeKey<MessageContext> MESSAGE_CONTEXT = AttributeKey.valueOf("org.minnal.message_context"); /** * @param configuration * @param router */ public AbstractHttpConnector(ConnectorConfiguration configuration, Router router) { this.configuration = configuration; this.router = router; } public void initialize() { logger.info("Initializing the connector"); EventLoopGroup bossGroup = new NioEventLoopGroup(configuration.getIoWorkerThreadCount()); EventLoopGroup workerGroup = new NioEventLoopGroup(configuration.getIoWorkerThreadCount()); bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .childOption(ChannelOption.TCP_NODELAY, true) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new HttpRequestDecoder(), new HttpResponseEncoder(), new HttpObjectAggregator(configuration.getMaxContentLength()), AbstractHttpConnector.this); addChannelHandlers(ch.pipeline()); } }); } protected abstract void addChannelHandlers(ChannelPipeline pipeline); public void start() { logger.info("Starting the connector on the port {}", configuration.getPort()); bootstrap.bind(new InetSocketAddress(configuration.getPort())); } public void stop() { logger.info("Stopping the connector on the port {}", configuration.getPort()); bootstrap.group().shutdownGracefully(); bootstrap.childGroup().shutdownGracefully(); try { bootstrap.group().terminationFuture().sync(); } catch (InterruptedException e) { logger.warn("Failed while stopping the boss threads", e); } try { bootstrap.childGroup().terminationFuture().sync(); } catch (InterruptedException e) { logger.warn("Failed while stopping the worker threads", e); } } /** * @return the configuration */ protected ConnectorConfiguration getConnectorConfiguration() { return configuration; } public void registerListener(ConnectorListener listener) { this.listener = listener; } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception { logger.error("Exception caught in the http connector", e); if (ctx.attr(MESSAGE_CONTEXT).get() instanceof MessageContext) { listener.onError(ctx.attr(MESSAGE_CONTEXT).get()); } else { listener.onError(e.getCause()); } super.exceptionCaught(ctx, e); HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest httpRequest) throws Exception { logger.trace("Received a {} message {} from the remote address {}", configuration.getScheme().name(), httpRequest, ctx.channel().remoteAddress()); URI baseUri = HttpUtil.createURI(configuration.getScheme().name(), httpRequest.headers().get(HttpHeaders.Names.HOST), "//"); MessageContext context = new MessageContext(httpRequest, baseUri); ctx.attr(MESSAGE_CONTEXT).set(context); listener.onReceived(context); router.route(context); listener.onSuccess(context); ctx.writeAndFlush(context.getResponse()).addListener(ChannelFutureListener.CLOSE); listener.onComplete(context); } }