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

org.xipki.http.server.HttpServer Maven / Gradle / Ivy

The newest version!
/*
 *
 * Copyright (c) 2013 - 2017 Lijun Liao
 *
 * 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 org.xipki.http.server;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import javax.net.ssl.SSLSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.http.servlet.HttpServlet;
import org.xipki.http.servlet.ServletURI;
import org.xipki.http.servlet.SslReverseProxyMode;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
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.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.CharsetUtil;

/**
 * @author Lijun Liao
 * @since 2.2.0
 */

public final class HttpServer {

    private class NettyHttpServerInitializer extends ChannelInitializer {

        public NettyHttpServerInitializer() {
        }

        @Override
        public void initChannel(SocketChannel ch) {
            ChannelPipeline pipeline = ch.pipeline();
            if (sslContext != null) {
                pipeline.addLast("ssl", sslContext.newHandler(ch.alloc()));
            }
            pipeline.addLast(new HttpServerCodec())
                .addLast(new HttpObjectAggregator(65536))
                .addLast(new ChunkedWriteHandler())
                .addLast(new NettyHttpServerHandler());
        }
    }

    private class NettyHttpServerHandler extends SimpleChannelInboundHandler {

        private NettyHttpServerHandler() {
            super(true);
        }

        @Override
        public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request)
                throws Exception {
            if (!request.decoderResult().isSuccess()) {
                sendError(ctx, HttpResponseStatus.BAD_REQUEST);
                return;
            }

            Object[] objs = servletListener.getServlet(request.uri());
            if (objs== null) {
                sendError(ctx, HttpResponseStatus.NOT_FOUND);
                return;
            }

            ServletURI servletUri = (ServletURI) objs[0];
            HttpServlet servlet = (HttpServlet) objs[1];

            SSLSession sslSession = null;

            if (servlet.needsTlsSessionInfo() && sslContext != null) {
                SslHandler handler = (SslHandler) ctx.channel().pipeline().get("ssl");
                if (handler != null) {
                    sslSession = handler.engine().getSession();
                }
            }

            FullHttpResponse response;
            try {
                response = servlet.service(request, servletUri, sslSession, sslReverseProxyMode);
            } catch (Exception ex) {
                logException("exception raised while processing request", ex);
                sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
                return;
            }

            boolean keepAlive = true;
            int status = response.status().code();
            if (status < 200 | status > 299) {
                keepAlive = false;
            }

            ChannelFuture cf = ctx.writeAndFlush(response);
            if (!keepAlive) {
                cf.addListener(ChannelFutureListener.CLOSE);
            }
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            if (ctx.channel().isActive()) {
                sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
            }
        }

        private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
            ByteBuf content = Unpooled.copiedBuffer("Failure: " + status + "\r\n",
                    CharsetUtil.UTF_8);
            FullHttpResponse response = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1, status, content);
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");

            // Close the connection as soon as the error message is sent.
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }

        private void logException(String msg, Exception ex) {
            LOG.warn("{} - {}: {}", msg, ex.getClass().getName(), ex.getMessage());
            LOG.debug(msg, ex);
            return;
        }
    }

    private static final Logger LOG = LoggerFactory.getLogger(HttpServer.class);

    private static Boolean epollAvailable;

    private static Boolean kqueueAvailable;

    private final int port;

    private final SslContext sslContext;

    private final int numThreads;

    private ServletListener servletListener;

    private EventLoopGroup bossGroup;

    private EventLoopGroup workerGroup;

    private SslReverseProxyMode sslReverseProxyMode = SslReverseProxyMode.NONE;

    public void setSslReverseProxyMode(SslReverseProxyMode mode) {
        this.sslReverseProxyMode = (mode == null) ? SslReverseProxyMode.NONE : mode;
    }

    public HttpServer(SslContext sslContext, int port, int numThreads) {
        this.sslContext = sslContext;
        this.port = port;
        if (numThreads > 0) {
            this.numThreads = numThreads;
        } else {
            this.numThreads = 4 * Runtime.getRuntime().availableProcessors();
        }
    }

    public void setServletListener(ServletListener servletListener) {
        this.servletListener = servletListener;
    }

    static {
        String os = System.getProperty("os.name").toLowerCase();
        ClassLoader loader = HttpServer.class.getClassLoader();
        if (os.contains("linux")) {
            try {
                Class checkClazz = Class.forName(
                        "io.netty.channel.epoll.Epoll", false, loader);
                Method mt = checkClazz.getMethod("isAvailable");
                Object obj = mt.invoke(null);

                if (obj instanceof Boolean) {
                    epollAvailable = (Boolean) obj;
                }
            } catch (Throwable th) {
                if (th instanceof ClassNotFoundException) {
                    LOG.info("epoll linux is not in classpath");
                } else {
                    LOG.warn("could not use Epoll transport: {}", th.getMessage());
                    LOG.debug("could not use Epoll transport", th);
                }
            }
        } else if (os.contains("mac os") || os.contains("os x")) {
            try {
                Class checkClazz = Class.forName(
                        "io.netty.channel.epoll.kqueue.KQueue", false, loader);
                Method mt = checkClazz.getMethod("isAvailable");
                Object obj = mt.invoke(null);
                if (obj instanceof Boolean) {
                    kqueueAvailable = (Boolean) obj;
                }
            } catch (Exception ex) {
                LOG.warn("could not use KQueue transport: {}", ex.getMessage());
                LOG.debug("could not use KQueue transport", ex);
            }
        }
    }

    @SuppressWarnings("unchecked")
    public void start() {
        int numProcessors = Runtime.getRuntime().availableProcessors();
        Class channelClass = null;
        int bossGroupThreads = numProcessors == 1 ? 1  : (numProcessors + 1)/ 2;

        ClassLoader loader = getClass().getClassLoader();
        if (epollAvailable != null && epollAvailable.booleanValue()) {
            try {
                channelClass = (Class)
                        Class.forName("io.netty.channel.epoll.EpollServerSocketChannel",
                                false, loader);

                Class clazz = Class.forName("io.netty.channel.epoll.EpollEventLoopGroup",
                                    true, loader);
                Constructor constructor = clazz.getConstructor(int.class);
                this.bossGroup = (EventLoopGroup) constructor.newInstance(bossGroupThreads);
                this.workerGroup = (EventLoopGroup) constructor.newInstance(numThreads);
                LOG.info("use Epoll Transport");
            } catch (Throwable th) {
                if (th instanceof ClassNotFoundException) {
                    LOG.info("epoll linux is not in classpath");
                } else {
                    LOG.warn("could not use Epoll transport: {}", th.getMessage());
                    LOG.debug("could not use Epoll transport", th);
                }
                channelClass = null;
                this.bossGroup = null;
                this.workerGroup = null;
            }
        } else if (kqueueAvailable != null && kqueueAvailable.booleanValue()) {
            try {
                channelClass = (Class)
                        Class.forName("io.netty.channel.kqueue.KQueueServerSocketChannel",
                                false, loader);

                Class clazz = Class.forName("io.netty.channel.kqueue.KQueueEventLoopGroup",
                                    true, loader);
                Constructor constructor = clazz.getConstructor(int.class);
                this.bossGroup = (EventLoopGroup) constructor.newInstance(bossGroupThreads);
                this.workerGroup = (EventLoopGroup) constructor.newInstance(numThreads);
                LOG.info("Use KQueue Transport");
            } catch (Exception ex) {
                LOG.warn("could not use KQueue transport: {}", ex.getMessage());
                LOG.debug("could not use KQueue transport", ex);
                channelClass = null;
                this.bossGroup = null;
                this.workerGroup = null;
            }
        }

        if (this.bossGroup == null) {
            channelClass = NioServerSocketChannel.class;
            this.bossGroup = new NioEventLoopGroup(bossGroupThreads);
            this.workerGroup = new NioEventLoopGroup(numThreads);
        }

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup)
            .channel(channelClass)
            .handler(new LoggingHandler())
            .childHandler(new NettyHttpServerInitializer());

        bootstrap.bind(port).syncUninterruptibly();
        LOG.info("HTTP server is listening on port {}", port);
    }

    public void shutdown() {
        bossGroup.shutdownGracefully();
        bossGroup = null;
        workerGroup.shutdownGracefully();
        workerGroup = null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy