io.maelstorm.server.RequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for io.maelstorm.server.RequestHandler.java

Source

/*
 * 
 * This file is part of Http-Server
 * Copyright (C) 2013  Jatinder
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * 
 * This library 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
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this library; If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package io.maelstorm.server;

import io.maelstorm.netty.HttpObjectAggregator.AggregatedFullHttpRequest;
import io.maelstorm.server.exception.DefectiveRequest;
import io.maelstorm.server.statistics.ICollector;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
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.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.AppendableCharSequence;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.stumbleupon.async.Callback;
import com.stumbleupon.async.Deferred;

/**
 * @author Jatinder
 *
 * Servers link to handle Netty's messages and parse http request/responses
 *
 */
public abstract class RequestHandler extends ChannelInboundHandlerAdapter implements ICollector {

    private static ServerInit initializer;
    private static final Logger LOG = LoggerFactory.getLogger(RequestHandler.class);
    protected static final AtomicLong totalHttpRequests = new AtomicLong();
    protected static final AtomicLong totalExceptions = new AtomicLong();
    protected static final AtomicLong activeHttpRequests = new AtomicLong();
    private static final String normalLogTemplate = "Request on uri {} of type {} was processed in {} ms";
    private static final String exceptionLogTemplate = "Request on uri {} of type {} threw Exception in {} ms";
    private static final String unexpectedExceptionLogTemplate = "Unexpected request : {} from channel {} ";

    @Override
    public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
        final long start = System.nanoTime();
        if (msg instanceof AggregatedFullHttpRequest) {
            final AggregatedFullHttpRequest request = (AggregatedFullHttpRequest) msg;
            totalHttpRequests.incrementAndGet();
            activeHttpRequests.incrementAndGet();
            Deferred<FullHttpResponse> response = process(ctx, request);
            response.addCallbacks(new Callback<Object, FullHttpResponse>() {
                public Object call(FullHttpResponse response) throws Exception {
                    sendResponse(ctx, response, request);
                    if (LOG.isDebugEnabled())
                        LOG.trace(normalLogTemplate, request.getUri(), request.getMethod(),
                                (System.nanoTime() - start) / 1000000);
                    activeHttpRequests.decrementAndGet();
                    return null;
                }
            }, new Callback<Object, Exception>() {
                public Object call(Exception arg) throws Exception {
                    totalExceptions.incrementAndGet();
                    FullHttpResponse response = new DefaultFullHttpResponse(request.getProtocolVersion(),
                            HttpResponseStatus.INTERNAL_SERVER_ERROR);
                    response.headers().add(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8");
                    // FIXME response.setContent(arg.getMessage().getBytes(arg0));
                    sendResponse(ctx, response, request);
                    if (LOG.isDebugEnabled())
                        LOG.debug(exceptionLogTemplate, request.getUri(), request.getMethod(),
                                (System.nanoTime() - start) / 1000000);
                    LOG.error(arg.getLocalizedMessage(), arg);
                    activeHttpRequests.decrementAndGet();
                    return null;
                }
            });
        } else {
            LOG.error(unexpectedExceptionLogTemplate, msg, ctx.channel());
            totalExceptions.incrementAndGet();
            ctx.channel().close();
        }

    }

    void setInitializer(ServerInit initialiser) {
        if (null == this.initializer) {
            this.initializer = initialiser;
        }
    }

    private void sendResponse(final ChannelHandlerContext ctx, final FullHttpResponse response,
            final HttpRequest request) {
        ReferenceCountUtil.release(request);
        if (!ctx.channel().isActive()) {
            return;
        }
        final boolean keepalive = HttpHeaders.isKeepAlive(request);
        if (keepalive) {
            HttpHeaders.setContentLength(response, response.content().readableBytes());
            response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        }
        final ChannelFuture future = ctx.write(response);
        ctx.flush();
        if (!keepalive) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    protected void doShutdown() {
        LOG.info("shutdown requested ");
        // Netty gets stuck in an infinite loop if we shut it down from within a
        // NIO thread. So do this from a newly created thread.
        final class ShutdownNetty extends Thread {
            ShutdownNetty() {
                super("ShutdownNetty");
            }

            public void run() {
                initializer.shutDownGracefully();
            }
        }
        new ShutdownNetty().start();
    }

    /**
     * Should always call request.sendReply(String reply) once during whole method
     * @param msg
     */
    protected abstract Deferred<FullHttpResponse> process(ChannelHandlerContext context,
            AggregatedFullHttpRequest request);

    protected String getEndPoint(final AppendableCharSequence uri) {
        if (uri.length() < 1) {
            throw new DefectiveRequest("Empty query");
        }
        if (uri.charAt(0) != '/') {
            throw new DefectiveRequest("Query doesn't start with a slash: <code>" + uri + "</code>");
        }
        int questionmark = -1;
        int slash = -1;
        boolean foundAll = false;
        for (int i = 1; i < uri.length() && !foundAll; i++) {
            switch (uri.charAt(i)) {
            case '?':
                questionmark = i;
                break;
            case '/':
                slash = i;
                break;
            default:
                if (i > 1 && (questionmark > 1 || slash > 1)) {
                    foundAll = true;
                }
                break;
            }
        }
        int pos = uri.length(); // Will be set to where the first path segment ends.
        if (questionmark > 0) {
            if (slash > 0) {
                pos = (questionmark < slash ? questionmark // Request: /foo?bar/quux
                        : slash); // Request: /foo/bar?quux
            } else {
                pos = questionmark; // Request: /foo?bar
            }
        } else {
            pos = (slash > 0 ? slash // Request: /foo/bar
                    : uri.length()); // Request: /foo
        }
        return uri.substring(1, pos);
    }

    public Map<String, Number> getStatistics() {
        Map<String, Number> stats = new HashMap<String, Number>();
        stats.put("totalHttpRequests", totalHttpRequests.get());
        stats.put("totalExceptions", totalExceptions.get());
        stats.put("activeHttpRequests", activeHttpRequests.get());
        return stats;
    }

    public boolean isDisplayed() {
        return true;
    }
}