/*
* WebSocketSslServerHandler.java
*
* Created on Jan 30, 2012, 9:49:41 AM
*
* Description: Provides a web socket SSL server handler.
*
* Copyright (C) Jan 30, 2012, Stephen L. Reed, Texai.org.
*
*/
package org.texai.network.netty.handler;
import java.util.List;
import net.jcip.annotations.NotThreadSafe;
import org.apache.log4j.Logger;
import org.jboss.netty.channel.ChannelHandlerContext;
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.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import org.texai.util.StringUtils;
import org.texai.util.TexaiException;
/** Provides a web socket SSL server handler. Each web socket connection has its own handler instance because the
* handshaker is not shared.
*
* @author reed
*/
@NotThreadSafe
public class WebSocketSslServerHandler extends SimpleChannelUpstreamHandler {
/** the logger */
private static final Logger LOGGER = Logger.getLogger(WebSocketSslServerHandler.class);
/** the web socket path */
private static final String WEBSOCKET_PATH = "/websocket";
/** the web socket server handshaker */
private WebSocketServerHandshaker webSocketServerHandshaker;
/** the parent HTTP request handler, that knows about the Texai HTTP request handler chain */
private final HTTPRequestHandler httpRequestHandler;
/** Constructs a new WebSocketSslServerHandler instance.
*
* @param httpRequestHandler the parent HTTP request handler, that knows about the Texai HTTP request handler chain
*/
public WebSocketSslServerHandler(final HTTPRequestHandler httpRequestHandler) {
this.httpRequestHandler = httpRequestHandler;
}
/** Performs the web socket handshake to upgrade the protocol from HTTP to web socket.
*
* @param httpRequest the HTTP request
* @param channelHandlerContext the channel handler context
*/
public void handshake(
final HttpRequest httpRequest,
final ChannelHandlerContext channelHandlerContext) {
//Preconditions
assert httpRequest != null : "httpRequest must not be null";
assert channelHandlerContext != null : "channelHandlerContext must not be null";
final String webSocketURL = "wss://" + httpRequest.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH;
final WebSocketServerHandshakerFactory webSocketServerHandshakeFactory = new WebSocketServerHandshakerFactory(
webSocketURL, // webSocketURL
null, // subprotocols
false); // allowExtensions
webSocketServerHandshaker = webSocketServerHandshakeFactory.newHandshaker(httpRequest);
if (webSocketServerHandshaker == null) {
webSocketServerHandshakeFactory.sendUnsupportedWebSocketVersionResponse(channelHandlerContext.getChannel());
LOGGER.warn("web socket version is not supported\n" + httpRequest);
} else {
webSocketServerHandshaker.handshake(channelHandlerContext.getChannel(), httpRequest);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("web socket server handshake completed");
LOGGER.debug("server pipeline ...\n" + channelHandlerContext.getChannel().getPipeline());
}
}
}
/** Receives a message from a remote peer.
*
* @param channelHandlerContext the channel handler context
* @param messageEvent the message event
* @throws Exception when an exception occurs
*/
@Override
public void messageReceived(
final ChannelHandlerContext channelHandlerContext,
final MessageEvent messageEvent) throws Exception {
//Preconditions
assert channelHandlerContext != null : "channelHandlerContext must not be null";
assert messageEvent != null : "messageEvent must not be null";
final Object message = messageEvent.getMessage();
if (message instanceof WebSocketFrame) {
handleWebSocketFrame(channelHandlerContext, (WebSocketFrame) message);
} else {
throw new TexaiException("message must be a WebSocketFrame, but was " + message.getClass().getName());
}
}
/** Handles a received web socket frame.
*
* @param channelHandlerContext the channel handler context
* @param webSocketFrame the web socket frame
*/
private void handleWebSocketFrame(
final ChannelHandlerContext channelHandlerContext,
final WebSocketFrame webSocketFrame) {
//Preconditions
assert channelHandlerContext != null : "channelHandlerContext must not be null";
assert webSocketFrame != null : "webSocketFrame must not be null";
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("handling web socket frame " + webSocketFrame.getClass().getSimpleName());
}
if (webSocketFrame instanceof CloseWebSocketFrame) {
webSocketServerHandshaker.close(channelHandlerContext.getChannel(), (CloseWebSocketFrame) webSocketFrame);
return;
} else if (webSocketFrame instanceof PingWebSocketFrame) {
channelHandlerContext.getChannel().write(new PongWebSocketFrame(webSocketFrame.getBinaryData()));
return;
} else if ((webSocketFrame instanceof TextWebSocketFrame)) {
final List<TexaiHTTPRequestHandler> texaiHTTPRequestHandlers = httpRequestHandler.getTexaiHTTPRequestHandlers();
synchronized (texaiHTTPRequestHandlers) {
for (final TexaiHTTPRequestHandler texaiHTTPRequestHandler : texaiHTTPRequestHandlers) {
final boolean isHandled = texaiHTTPRequestHandler.textWebSocketFrameReceived(
channelHandlerContext.getChannel(),
(TextWebSocketFrame) webSocketFrame);
if (isHandled) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(texaiHTTPRequestHandler.getClass().getName() + " handled the request");
}
return;
}
}
}
assert false : "no handler for the web socket frame: " + webSocketFrame;
} else {
throw new UnsupportedOperationException(String.format("%s frame types not supported", webSocketFrame.getClass().getName()));
}
}
/** Processes an exception not otherwise caught.
*
* @param channelHandlerContext the channel handler context
* @param exceptionEvent the exception event
* @throws Exception when an exception occurs
*/
@Override
@SuppressWarnings("ThrowableResultIgnored")
public void exceptionCaught(
final ChannelHandlerContext channelHandlerContext,
final ExceptionEvent exceptionEvent) throws Exception {
//Preconditions
assert channelHandlerContext != null : "channelHandlerContext must not be null";
assert exceptionEvent != null : "e must not be null";
LOGGER.warn(channelHandlerContext.getChannel() + "\n" +
StringUtils.getStackTraceAsString(exceptionEvent.getCause()));
exceptionEvent.getChannel().close();
throw new TexaiException(exceptionEvent.getCause());
}
}