com.barchart.netty.server.http.HttpServer.java Source code

Java tutorial

Introduction

Here is the source code for com.barchart.netty.server.http.HttpServer.java

Source

/**
 * Copyright (C) 2011-2014 Barchart, Inc. <http://www.barchart.com/>
 *
 * All rights reserved. Licensed under the OSI BSD License.
 *
 * http://www.opensource.org/licenses/bsd-license.php
 */
package com.barchart.netty.server.http;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
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.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.stream.ChunkedWriteHandler;

import java.net.InetSocketAddress;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;

import com.barchart.netty.server.HandlerFactory;
import com.barchart.netty.server.base.AbstractStatefulServer;
import com.barchart.netty.server.http.error.DefaultErrorHandler;
import com.barchart.netty.server.http.error.ErrorHandler;
import com.barchart.netty.server.http.logging.NullRequestLogger;
import com.barchart.netty.server.http.logging.RequestLogger;
import com.barchart.netty.server.http.pipeline.HttpRequestChannelHandler;
import com.barchart.netty.server.http.request.HandlerMapping;
import com.barchart.netty.server.http.request.RequestHandler;
import com.barchart.netty.server.util.SingleHandlerFactory;

/**
 * Asynchronous HTTP server.
 */
public class HttpServer extends AbstractStatefulServer<HttpServer> {

    private final Map<String, HandlerFactory<RequestHandler>> handlers = new ConcurrentSkipListMap<String, HandlerFactory<RequestHandler>>(
            new ReverseLengthComparator());

    private final Map<String, HandlerFactory<? extends ChannelHandler>> webSocketHandlers = new ConcurrentHashMap<String, HandlerFactory<? extends ChannelHandler>>();

    protected int maxConnections = -1;
    protected int maxRequestSize = 1024 * 1024;
    protected boolean chunked = true;
    protected long timeout = 0;

    protected ConnectionTracker clientTracker = null;
    protected ErrorHandler errorHandler = new DefaultErrorHandler();
    protected RequestLogger requestLogger = new NullRequestLogger();

    private final HttpRequestChannelHandler channelHandler;

    public HttpServer() {
        channelHandler = new HttpRequestChannelHandler(this);
    }

    @Override
    public ChannelFuture listen(final int port) {
        return listen(new InetSocketAddress("0.0.0.0", port));
    }

    @Override
    public void initPipeline(final ChannelPipeline pipeline) throws Exception {

        pipeline.addLast(new HttpResponseEncoder());

        if (chunked) {
            pipeline.addLast(new ChunkedWriteHandler());
        }

        if (clientTracker != null) {
            pipeline.addLast(clientTracker);
        }

        pipeline.addLast( //
                new HttpRequestDecoder(), //
                new HttpObjectAggregator(maxRequestSize), //
                channelHandler);

    }

    /**
     * Set the maximum number of client connections.
     */
    public HttpServer maxConnections(final int max) {
        maxConnections = max;
        clientTracker = new ConnectionTracker(maxConnections);
        return this;
    }

    /**
     * Set the maximum request size in bytes (file uploads, etc). Defaults to
     * 1048576 (1MB).
     */
    public HttpServer maxRequestSize(final int max) {
        maxRequestSize = max;
        return this;
    }

    /**
     * Set the default error handler.
     */
    public HttpServer errorHandler(final ErrorHandler handler) {
        errorHandler = handler;
        return this;
    }

    /**
     * Set the request logger.
     */
    public HttpServer logger(final RequestLogger logger_) {
        requestLogger = logger_;
        return this;
    }

    /**
     * Set whether the server should send large objects using chunked encoding.
     * Defaults to true.
     */
    public HttpServer chunked(final boolean chunked_) {
        chunked = chunked_;
        return this;
    }

    /**
     * Set the maximum request time. Defaults to 0 (unlimited).
     */
    public HttpServer timeout(final long timeout_) {
        timeout = timeout_;
        return this;
    }

    /**
     * Add a request handler for the given prefix. Prefix matching is simplistic
     * and the longest prefix that matches always wins regardless of number of
     * path segments, so you should avoid overlapping handler prefixes when
     * possible. For more complex handler routing using URL pattern matching see
     * com.barchart.rest.Router.
     *
     * Assuming two request handlers defined as:
     *
     * <pre>
     * requestHandler("/service", serviceHandler);
     * requestHandler("/service/info", infoHandler);
     * </pre>
     *
     * A request to "/service/info/10" will go to infoHandler, but a request to
     * "/service/something/else" will go to serviceHandler.
     *
     * Inside the handler, ServerRequest.getPathInfo() will return the URL path
     * portion that comes *after* the matched handler prefix. So, in the case of
     * the examples above, getPathInfo() would return:
     *
     * <pre>
     * /service/info/10: "/10"
     * /service/something/else: "/something/else"
     * </pre>
     */
    public HttpServer requestHandler(final String prefix, final RequestHandler handler) {
        handlers.put(prefix, new SingleHandlerFactory<RequestHandler>(handler));
        return this;
    }

    /**
     * Add a request handler factory for the given prefix. This is similar to
     * {@link HttpServer#requestHandler(String, RequestHandler)}, but allows
     * greater control over the handler lifecycle for use cases like
     * handler-per-connection or object pooling.
     *
     * @see HttpServer#requestHandler(String, RequestHandler)
     */
    public HttpServer requestHandler(final String prefix, final HandlerFactory<RequestHandler> factory) {
        handlers.put(prefix, factory);
        return this;
    }

    /**
     * Add a websocket handler for the given prefix.
     */
    public HttpServer webSocketHandler(final String prefix, final ChannelHandler handler) {
        webSocketHandlers.put(prefix, new SingleHandlerFactory<ChannelHandler>(handler));
        return this;
    }

    /**
     * Add a websocket handler factory for the given prefix.
     */
    public HttpServer webSocketHandler(final String prefix,
            final HandlerFactory<? extends ChannelHandler> factory) {
        webSocketHandlers.put(prefix, factory);
        return this;
    }

    /**
     * Get the maximum number of client connections.
     */
    public int maxConnections() {
        return maxConnections;
    }

    /**
     * Get the maximum request size in bytes.
     */
    public int maxRequestSize() {
        return maxRequestSize;
    }

    /**
     * Get the default error handler.
     */
    public ErrorHandler errorHandler() {
        return errorHandler;
    }

    /**
     * Get the request logger.
     */
    public RequestLogger logger() {
        return requestLogger;
    }

    /**
     * Get the Netty event loop group.
     */
    public EventLoopGroup group() {
        return defaultGroup;
    }

    /**
     * Get the request handler mapping for the specified URI.
     */
    public HandlerMapping<RequestHandler> getRequestMapping(final String uri) {

        for (final Map.Entry<String, HandlerFactory<RequestHandler>> entry : handlers.entrySet()) {
            if (uri.startsWith(entry.getKey())) {
                return new HandlerMapping<RequestHandler>(entry.getKey(), entry.getValue());
            }
        }

        return null;

    }

    /**
     * Get the websocket handler mapping for the specified URI.
     */
    public HandlerFactory<? extends ChannelHandler> webSocketFactory(final String uri) {

        if (webSocketHandlers.containsKey(uri)) {
            return webSocketHandlers.get(uri);
        }

        return null;

    }

    public HandlerFactory<RequestHandler> removeRequestHandler(final String path) {
        return handlers.remove(path);
    }

    public HandlerFactory<? extends ChannelHandler> removeWebSocketHandler(final String path) {
        return webSocketHandlers.remove(path);
    }

    @Sharable
    private class ConnectionTracker extends ChannelInboundHandlerAdapter {

        private int maxConnections = -1;

        public ConnectionTracker(final int connections) {
            maxConnections = connections;
        }

        @Override
        public void channelActive(final ChannelHandlerContext context) {

            if (maxConnections > -1 && connections() >= maxConnections) {

                final ByteBuf content = Unpooled.buffer();

                content.writeBytes("503 Service Unavailable - Server Too Busy".getBytes());

                final FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                        HttpResponseStatus.SERVICE_UNAVAILABLE);

                response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, content.readableBytes());

                response.content().writeBytes(content);

                context.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);

                return;

            } else {

                context.fireChannelActive();

            }

        }

    }

    /**
     *
     * Sorts strings by reverse length first, then normal comparison. For
     * example:
     *
     * 2 handlers defined as requestHandler("/service", serviceHandler);
     * requestHandler("/service/info", infoHandler);
     *
     * A request to "/service/info/10" will go to infoHandler, but a request to
     * "/service/something/else" will go to serviceHandler.
     *
     */
    private class ReverseLengthComparator implements Comparator<String> {

        // Sort by reverse length first to allow overriding parent
        // mappings
        @Override
        public int compare(final String o1, final String o2) {

            final int l1 = o1.length();
            final int l2 = o2.length();

            if (l1 < l2) {
                return 1;
            } else if (l2 < l1) {
                return -1;
            } else {
                return o1.compareTo(o2);
            }

        }

    }

}