// // Copyright 2010 Cinch Logic Pty Ltd. // // http://www.chililog.com // // 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.chililog.server.workbench; import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang.StringUtils; import org.chililog.server.common.Log4JLogger; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; 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.DefaultHttpResponse; import org.jboss.netty.handler.codec.http.HttpChunk; 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; /** * <p> * Route messages to specialised services based on the request URI. * <ul> * <li><code>/echo/*</code> - echo service for testing</li> * <li><code>/api/*</code> - api service exposes a RESTful interface for ChiliLog management and query</li> * <li><code>/workbench/*</code> - workbench service provides a browser based tool for ChiliLog management and query</li> * <li><code>/static/*</code> - static file service serves up static files</li> * </ul> * </p> * <p> * If a route cannot be found, 404 Not Found is returned. * </p> * <p> * If there an uncaught exception during processing, a 500 Internal Server Error is returned. The content of the * response is set to the error message. The content type is "text/plain". * * </p> */ public class HttpRequestHandler extends SimpleChannelUpstreamHandler { private static Log4JLogger _logger = Log4JLogger.getLogger(HttpRequestHandler.class); private WorkbenchRequestHandler _workbenchRequestHandler = null; /** * Constructor */ public HttpRequestHandler() { super(); } /** * Handles incoming messages by routing to services */ @Override public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent e) throws Exception { Object msg = e.getMessage(); if (msg instanceof HttpRequest) { // New request so let's figure our the service to call HttpRequest request = (HttpRequest) msg; _logger.debug("%s %s CHANNEL=%s", request.getMethod(), request.getUri(), e.getChannel().getId()); String uri = request.getUri(); if (StringUtils.isBlank(uri)) { send404NotFound(e); return; } // Route // Could have used reflection but since we have so few routes, it is quicker to hard code uri = uri.toLowerCase(); if (uri.startsWith("/api/")) { _workbenchRequestHandler = new ApiRequestHandler(); } else if (uri.equalsIgnoreCase("/workbench") || uri.equalsIgnoreCase("/workbench/")) { redirectToWorkBenchIndexHtml(e); return; } else if (uri.startsWith("/static") || uri.startsWith("/workbench")) { _workbenchRequestHandler = new StaticFileRequestHandler(); } else if (uri.startsWith("/echo")) { _workbenchRequestHandler = new EchoRequestHandler(); } else { send404NotFound(e); return; } } else if (msg instanceof HttpChunk) { // If this is HTTP chunk or web socket frame, then use existing _workbenchRequestHandler } else { throw new NotImplementedException("Message Type " + msg.getClass().getName()); } _workbenchRequestHandler.processMessage(ctx, e); return; } /** * <p> * Upon exception, just close the channel. Running over the Internet means we tend to get funny connection issues * at times. * </p> */ @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { try { _logger.error(e.getCause(), "ERROR: Unhandled exception: " + e.getCause().getMessage() + ". Closing channel " + ctx.getChannel().getId()); e.getChannel().close(); } catch (Exception ex) { _logger.debug(ex, "ERROR trying to close socket because we got an unhandled exception"); } } /** * Sends an HTTP Response with a status of <code>404 Not Found</code> back to the caller * * @param e * Message event that we are processing */ private void send404NotFound(MessageEvent e) { HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND); ChannelFuture future = e.getChannel().write(response); future.addListener(ChannelFutureListener.CLOSE); } /** * <p> * The /workbench URI path is a virtual path. We redirect to our Sproutcore UI HTML page. * </p> * <p> * We have to find the index.html file and redirect to it. * </p> * * @param e */ private void redirectToWorkBenchIndexHtml(MessageEvent e) { String indexHTML = "/workbench/index.html"; HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FOUND); response.addHeader("Location", indexHTML); ChannelFuture future = e.getChannel().write(response); future.addListener(ChannelFutureListener.CLOSE); } /** * Add channel to channel group to disconnect when shutting down Channel group automatically removes closed * channels. */ @Override public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) { WorkbenchService.getInstance().getAllChannels().add(e.getChannel()); } }