/*
* HTTPRequestHandler.java
*
* Created on Feb 11, 2010, 7:58:40 PM
*
* Description: Provides a multiplexed HTTP request handler.
*
* Copyright (C) Feb 11, 2010 reed.
*
* 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 program 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 program;
* if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.texai.network.netty.handler;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import net.jcip.annotations.NotThreadSafe;
import org.apache.log4j.Logger;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandler;
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.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
import org.jboss.netty.handler.ssl.SslHandler;
import org.texai.util.StringUtils;
import org.texai.util.TexaiException;
/** Provides a multiplexed HTTP request handler.
*
* @author reed
*/
@NotThreadSafe
public final class HTTPRequestHandler extends AbstractHTTPRequestHandler {
/** the logger */
private static final Logger LOGGER = Logger.getLogger(HTTPRequestHandler.class);
/** the Texai HTTP request handler chain */
private final List<TexaiHTTPRequestHandler> texaiHTTPRequestHandlers = new ArrayList<>();
/** the singleton HTTP request handler instance */
private static final HTTPRequestHandler httpRequestHandler = new HTTPRequestHandler();
/** Constructs a new HTTPRequestHandler instance. */
private HTTPRequestHandler() {
}
/** Gets the singleton HTTP request handler instance.
*
* @return the singleton HTTP request handler instance
*/
public static HTTPRequestHandler getInstance() {
return httpRequestHandler;
}
/** Handles the message object that was received from a remote peer.
*
* @param channelHandlerContext the channel handler context
* @param messageEvent the message event
*/
@Override
public void messageReceived(
final ChannelHandlerContext channelHandlerContext,
final MessageEvent messageEvent) {
//Preconditions
assert messageEvent != null : "messageEvent must not be null";
final HttpRequest httpRequest = (HttpRequest) messageEvent.getMessage();
LOGGER.info("---------------------------------------------------------------------");
LOGGER.info("httpRequest: " + httpRequest);
LOGGER.info("method: " + httpRequest.getMethod());
LOGGER.info("protocol version: " + httpRequest.getProtocolVersion());
LOGGER.info("method: " + httpRequest.getMethod());
LOGGER.info("uri: " + httpRequest.getUri());
boolean isUpgradeWebsocket = false;
for (final String headerName : httpRequest.getHeaderNames()) {
final String header = httpRequest.getHeader(headerName);
LOGGER.info("header: " + headerName + " " + header);
if (headerName.equals("Upgrade") && header.toLowerCase(Locale.ENGLISH).equals("websocket")) {
isUpgradeWebsocket = true;
}
}
if (isUpgradeWebsocket) {
switchToWebSocket(httpRequest, channelHandlerContext);
return;
}
final Channel channel = messageEvent.getChannel();
synchronized (texaiHTTPRequestHandlers) {
for (final TexaiHTTPRequestHandler texaiHTTPRequestHandler : texaiHTTPRequestHandlers) {
final boolean isHandled = texaiHTTPRequestHandler.httpRequestReceived(httpRequest, channel);
if (isHandled) {
LOGGER.info(texaiHTTPRequestHandler.getClass().getName() + " handled the request");
return;
}
}
}
LOGGER.info("no handler for the request: " + httpRequest);
throw new TexaiException("no handler for the request: " + httpRequest);
}
/** Registers the given Texai HTTP request handler.
*
* @param texaiHTTPRequestHandler the given Texai HTTP request handler
*/
public void register(final TexaiHTTPRequestHandler texaiHTTPRequestHandler) {
//Preconditions
assert texaiHTTPRequestHandler != null : "texaiHTTPRequestHandler must not be null";
synchronized (texaiHTTPRequestHandlers) {
texaiHTTPRequestHandlers.add(texaiHTTPRequestHandler);
}
}
/** Deregisters the given Texai HTTP request handler.
*
* @param texaiHTTPRequestHandler the given Texai HTTP request handler
*/
public void deregister(final TexaiHTTPRequestHandler texaiHTTPRequestHandler) {
//Preconditions
assert texaiHTTPRequestHandler != null : "texaiHTTPRequestHandler must not be null";
synchronized (texaiHTTPRequestHandlers) {
texaiHTTPRequestHandlers.remove(texaiHTTPRequestHandler);
}
}
/** Handles an exception that was raised by an I/O thread or a {@link ChannelHandler}.
*
* @param channelHandlerContext the channel handler context
* @param exceptionEvent the exception event
*/
@Override
public void exceptionCaught(
final ChannelHandlerContext channelHandlerContext,
final ExceptionEvent exceptionEvent) {
//Preconditions
assert channelHandlerContext != null : "channelHandlerContext must not be null";
assert exceptionEvent != null : "exceptionEvent must not be null";
LOGGER.error(StringUtils.getStackTraceAsString(exceptionEvent.getCause()));
exceptionEvent.getChannel().close();
}
/** Dynamically switches the channel pipeline to handle the web socket protocol.
*
* @param httpRequest the HTTP request
* @param channelHandlerContext the channel handler context
*/
private void switchToWebSocket(
final HttpRequest httpRequest,
final ChannelHandlerContext channelHandlerContext) {
//Preconditions
assert channelHandlerContext != null : "channelHandlerContext must not be null";
final ChannelPipeline channelPipeline = channelHandlerContext.getPipeline();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("switching to web socket channel pipeline from: " + channelPipeline);
}
final Collection<ChannelHandler> channelHandlers = channelPipeline.toMap().values();
for (final ChannelHandler channelHandler : channelHandlers) {
if (!(channelHandler instanceof SslHandler
|| channelHandler instanceof PortUnificationHandler)) {
channelPipeline.remove(channelHandler);
}
}
final WebSocketSslServerHandler webSocketSslServerHandler = new WebSocketSslServerHandler(this);
channelPipeline.addLast("encoder", new HttpResponseEncoder());
channelPipeline.addLast("decoder", new HttpRequestDecoder());
channelPipeline.addLast("aggregator", new HttpChunkAggregator(1048576));
channelPipeline.addLast("ws-request-handler", webSocketSslServerHandler);
if (LOGGER.isDebugEnabled()) {
LOGGER.info("web socket channel pipeline: " + channelPipeline);
}
webSocketSslServerHandler.handshake(httpRequest, channelHandlerContext);
}
/** Gets the Texai HTTP request handler chain. Caller should synchronized on this.
*
* @return the Texai HTTP request handler chain
*/
public List<TexaiHTTPRequestHandler> getTexaiHTTPRequestHandlers() {
return texaiHTTPRequestHandlers;
}
}