org.mobicents.tools.http.balancer.HttpRequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.mobicents.tools.http.balancer.HttpRequestHandler.java

Source

/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.mobicents.tools.http.balancer;

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.Set;

import org.apache.log4j.Logger;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.handler.codec.http.Cookie;
import org.jboss.netty.handler.codec.http.CookieDecoder;
import org.jboss.netty.handler.codec.http.CookieEncoder;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
import org.mobicents.tools.sip.balancer.BalancerRunner;
import org.mobicents.tools.sip.balancer.InvocationContext;
import org.mobicents.tools.sip.balancer.SIPNode;
import org.mobicents.tools.sip.balancer.StatisticObject;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

/**
 * @author Vladimir Ralev (vladimir.ralev@jboss.org)
 * 
 */
//https://issues.jboss.org/browse/NETTY-283 - ChannelPipelineCoverage deprecated
//@ChannelPipelineCoverage("one")
public class HttpRequestHandler extends SimpleChannelUpstreamHandler {

    private static final Logger logger = Logger.getLogger(HttpRequestHandler.class.getCanonicalName());

    private volatile HttpRequest request;
    private volatile boolean readingChunks;
    private volatile boolean wsrequest;
    private String wsVersion;
    private WebsocketModifyClientPipelineFactory websocketServerPipelineFactory;
    private volatile SIPNode node;
    private boolean isSecured;

    private BalancerRunner balancerRunner;

    public HttpRequestHandler(BalancerRunner balancerRunner, boolean isSecured) {
        this.balancerRunner = balancerRunner;
        this.isSecured = isSecured;
    }

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        Object msg = e.getMessage();
        if (msg instanceof HttpRequest) {
            request = (HttpRequest) e.getMessage();
            if (balancerRunner.getProperty("statisticPort") != null) {
                try {
                    URI uri = new URI(request.getUri());
                    if (((InetSocketAddress) ctx.getChannel().getLocalAddress()).getPort() == Integer
                            .parseInt(balancerRunner.getProperty("statisticPort"))) {
                        if (uri.getPath().equalsIgnoreCase("/lbstat")) {
                            writeStatisticResponse(e);
                            return;
                        } else {
                            writeResponse(e, HttpResponseStatus.INTERNAL_SERVER_ERROR, "Server error");
                            return;
                        }
                    }
                } catch (URISyntaxException e1) {
                    //ignore exception
                }
            }

            balancerRunner.balancerContext.httpRequests.incrementAndGet();
            balancerRunner.balancerContext.httpRequestsProcessedByMethod.get(request.getMethod().getName())
                    .incrementAndGet();
            balancerRunner.balancerContext.httpBytesToServer.addAndGet(request.getContent().capacity());
            String telestaxHeader = request.headers().get("TelestaxProxy");
            if (telestaxHeader != null && telestaxHeader.equalsIgnoreCase("true")) {
                balancerRunner.getLatestInvocationContext().balancerAlgorithm.proxyMessage(ctx, e);
            } else {
                handleHttpRequest(ctx, e);
            }
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocketFrame(ctx, e);
        }
    }

    private void handleWebSocketFrame(ChannelHandlerContext ctx, final MessageEvent e) {
        Object msg = e.getMessage();
        final String request = ((TextWebSocketFrame) msg).getText();
        if (logger.isDebugEnabled()) {
            logger.debug(
                    String.format("Channel %s received WebSocket request %s", ctx.getChannel().getId(), request));
        }
        //Modify the Client Pipeline - Phase 2
        Channel channel = HttpChannelAssociations.channels.get(e.getChannel());
        ChannelPipeline p = channel.getPipeline();
        websocketServerPipelineFactory.upgradeClientPipelineFactoryPhase2(p, wsVersion);

        if (channel != null) {
            channel.write(new TextWebSocketFrame(request));
        }

    }

    private void handleHttpRequest(ChannelHandlerContext ctx, final MessageEvent e) throws Exception {
        if (!readingChunks) {
            request = (HttpRequest) e.getMessage();

            if (logger.isDebugEnabled()) {
                logger.debug("Request URI accessed: " + request.getUri() + " channel " + e.getChannel());
            }

            Channel associatedChannel = HttpChannelAssociations.channels.get(e.getChannel());

            InvocationContext invocationContext = balancerRunner.getLatestInvocationContext();

            //         SIPNode node = null;
            try {
                //TODO: If WebSocket request, choose a NODE that is able to handle WebSocket requests (has a websocket connector)
                node = invocationContext.balancerAlgorithm.processHttpRequest(request);
            } catch (Exception ex) {
                StringWriter sw = new StringWriter();
                ex.printStackTrace(new PrintWriter(sw));
                logger.warn("Problem in balancer algorithm", ex);

                writeResponse(e, HttpResponseStatus.INTERNAL_SERVER_ERROR,
                        "Load Balancer Error: Exception in the balancer algorithm:\n" + sw.toString());
                return;
            }

            if (node == null) {
                if (logger.isInfoEnabled()) {
                    logger.info("Service unavailable. No server is available.");
                }
                writeResponse(e, HttpResponseStatus.SERVICE_UNAVAILABLE, "Service is temporarily unavailable");
                return;
            }

            if (associatedChannel != null && associatedChannel.isConnected()) {
                associatedChannel.write(request);
            } else {

                e.getChannel().getCloseFuture().addListener(new ChannelFutureListener() {
                    public void operationComplete(ChannelFuture arg0) throws Exception {
                        closeChannelPair(arg0.getChannel());
                    }
                });

                // Start the connection attempt.
                ChannelFuture future = null;
                Set<String> headers = request.getHeaderNames();
                if (headers.contains("Sec-WebSocket-Protocol")) {
                    if (request.getHeader("Sec-WebSocket-Protocol").equalsIgnoreCase("sip")) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("New SIP over WebSocket request. WebSocket uri: " + request.getUri());
                            logger.debug("Dispatching WebSocket request to node: " + node.getIp() + " port: "
                                    + node.getProperties().get("wsPort"));
                        }
                        wsrequest = true;
                        wsVersion = request.getHeader(Names.SEC_WEBSOCKET_VERSION);
                        websocketServerPipelineFactory = new WebsocketModifyClientPipelineFactory();
                        future = HttpChannelAssociations.inboundBootstrap.connect(
                                new InetSocketAddress(node.getIp(), (Integer) node.getProperties().get("wsPort")));

                    }
                } else {
                    if (!isSecured) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Dispatching HTTP request to node: " + node.getIp() + " port: "
                                    + node.getProperties().get("httpPort"));
                        }
                        future = HttpChannelAssociations.inboundBootstrap.connect(new InetSocketAddress(
                                node.getIp(), (Integer) node.getProperties().get("httpPort")));
                    } else {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Dispatching HTTPS request to node: " + node.getIp() + " port: "
                                    + node.getProperties().get("sslPort"));
                        }
                        future = HttpChannelAssociations.inboundSecureBootstrap.connect(
                                new InetSocketAddress(node.getIp(), (Integer) node.getProperties().get("sslPort")));
                    }
                }

                future.addListener(new ChannelFutureListener() {

                    public void operationComplete(ChannelFuture arg0) throws Exception {
                        Channel channel = arg0.getChannel();
                        HttpChannelAssociations.channels.put(e.getChannel(), channel);
                        HttpChannelAssociations.channels.put(channel, e.getChannel());

                        if (request.isChunked()) {
                            readingChunks = true;
                        }
                        channel.write(request);

                        if (wsrequest) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("This is a websocket request, changing the pipeline");
                            }
                            //Modify the Client Pipeline - Phase 1
                            ChannelPipeline p = channel.getPipeline();
                            websocketServerPipelineFactory.upgradeClientPipelineFactoryPhase1(p, wsVersion);
                        }

                        channel.getCloseFuture().addListener(new ChannelFutureListener() {
                            public void operationComplete(ChannelFuture arg0) throws Exception {
                                closeChannelPair(arg0.getChannel());
                            }
                        });
                    }
                });
            }
        } else {
            HttpChunk chunk = (HttpChunk) e.getMessage();
            if (chunk.isLast()) {
                readingChunks = false;
            }
            HttpChannelAssociations.channels.get(e.getChannel()).write(chunk);
        }
    }

    private void closeChannelPair(Channel channel) {
        Channel associatedChannel = HttpChannelAssociations.channels.get(channel);
        if (associatedChannel != null) {
            try {
                HttpChannelAssociations.channels.remove(associatedChannel);
                if (!associatedChannel.isConnected()) {
                    associatedChannel.disconnect();
                    associatedChannel.close();
                    associatedChannel.getCloseFuture().awaitUninterruptibly();
                }
                associatedChannel = null;
            } catch (Exception e) {

            }
        }
        HttpChannelAssociations.channels.remove(channel);
        //logger.info("Channel closed. Channels remaining: " + HttpChannelAssocialtions.channels.size());
        if (logger.isDebugEnabled()) {
            try {
                logger.debug("Channel closed " + HttpChannelAssociations.channels.size() + " " + channel);
                Enumeration<Channel> c = HttpChannelAssociations.channels.keys();
                while (c.hasMoreElements()) {
                    logger.debug(c.nextElement().toString());
                }
            } catch (Exception e) {
                logger.debug("error", e);
            }
        }
    }

    private void writeResponse(MessageEvent e, HttpResponseStatus status, String responseString) {
        // Convert the response content to a ChannelBuffer.
        ChannelBuffer buf = ChannelBuffers.copiedBuffer(responseString, Charset.forName("UTF-8"));

        // Decide whether to close the connection or not.
        boolean close = HttpHeaders.Values.CLOSE.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION))
                || request.getProtocolVersion().equals(HttpVersion.HTTP_1_0) && !HttpHeaders.Values.KEEP_ALIVE
                        .equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION));

        // Build the response object.
        HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
        response.setContent(buf);
        response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8");

        if (!close) {
            // There's no need to add 'Content-Length' header
            // if this is the last response.
            response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(buf.readableBytes()));
        }

        String cookieString = request.getHeader(HttpHeaders.Names.COOKIE);
        if (cookieString != null) {
            CookieDecoder cookieDecoder = new CookieDecoder();
            Set<Cookie> cookies = cookieDecoder.decode(cookieString);
            if (!cookies.isEmpty()) {
                // Reset the cookies if necessary.
                CookieEncoder cookieEncoder = new CookieEncoder(true);
                for (Cookie cookie : cookies) {
                    cookieEncoder.addCookie(cookie);
                }
                response.addHeader(HttpHeaders.Names.SET_COOKIE, cookieEncoder.encode());
            }
        }

        // Write the response.
        ChannelFuture future = e.getChannel().write(response);

        // Close the connection after the write operation is done if necessary.
        if (close) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    //   @Override
    //   public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent event) throws Exception {
    //      //      logger.info("new handleUpstream reveiced on channel: ["+ctx.getChannel().toString() +"] event message: ["+event.toString()+"]");
    //      super.handleUpstream(ctx, event);
    //   }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
        logger.error("Error", e.getCause());
        e.getChannel().close();
    }

    private void writeStatisticResponse(MessageEvent e) {
        GsonBuilder builder = new GsonBuilder();
        Gson gson = builder.setPrettyPrinting().create();
        JsonElement je = gson.toJsonTree(new StatisticObject(balancerRunner));
        JsonObject jo = new JsonObject();
        jo.add("Metrics", je);
        String output = jo.toString();
        HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        response.setHeader(HttpHeaders.Names.CONTENT_TYPE, APPLICATION_JSON);
        ChannelBuffer buf = ChannelBuffers.copiedBuffer(output, Charset.forName("UTF-8"));
        response.setContent(buf);
        ChannelFuture future = e.getChannel().write(response);
        future.addListener(ChannelFutureListener.CLOSE);
    }
}