/*
* Copyright 2009 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.texai.network.netty.handler;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
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.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.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.handler.codec.http.QueryStringDecoder;
import org.jboss.netty.handler.ssl.SslHandler;
import org.jboss.netty.util.CharsetUtil;
import org.texai.util.StringUtils;
/** A HTTPS request handler.
*
* @author The Netty Project (netty-dev@lists.jboss.org)
* @author Andy Taylor (andy.taylor@jboss.org)
* @author Trustin Lee (tlee@redhat.com)
*
* @version $Rev: 1685 $, $Date: 2009-08-28 16:15:49 +0900 (금, 28 8 2009) $
*/
public final class MockHTTPRequestHandler extends AbstractHTTPRequestHandler {
/** the logger */
private static final Logger LOGGER = Logger.getLogger(MockHTTPRequestHandler.class);
/** the HTTP request */
private volatile HttpRequest httpRequest;
/** the indicator that we are reading chunks */
private volatile boolean isReadingChunks;
/** the response content */
private final StringBuilder responseContent = new StringBuilder();
/** Constructs a new MockHTTPRequestHandler instance. */
public MockHTTPRequestHandler() {
}
/** Handles the message object (e.g: {@link ChannelBuffer}) 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 channelHandlerContext != null : "channelHandlerContext must not be null";
assert messageEvent != null : "messageEvent must not be null";
if (isReadingChunks) {
final HttpChunk httpChunk = (HttpChunk) messageEvent.getMessage();
if (httpChunk.isLast()) {
isReadingChunks = false;
responseContent.append("END OF CONTENT\r\n");
writeResponse(messageEvent);
} else {
responseContent.append("CHUNK: ").append(httpChunk.getContent().toString(CharsetUtil.UTF_8)).append("\r\n");
}
} else {
httpRequest = (HttpRequest) messageEvent.getMessage();
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;
}
responseContent.append("WELCOME TO THE WILD WILD WEB SERVER\r\n");
responseContent.append("===================================\r\n");
responseContent.append("VERSION: ").append(httpRequest.getProtocolVersion().getText()).append("\r\n");
if (httpRequest.containsHeader(HttpHeaders.Names.HOST)) {
responseContent.append("HOSTNAME: ").append(httpRequest.getHeader(HttpHeaders.Names.HOST)).append("\r\n");
}
responseContent.append("REQUEST_URI: ").append(httpRequest.getUri()).append("\r\n\r\n");
if (!httpRequest.getHeaderNames().isEmpty()) {
for (final String headerName : httpRequest.getHeaderNames()) {
for (final String headerValue : httpRequest.getHeaders(headerName)) {
responseContent.append("HEADER: ").append(headerName).append(" = ").append(headerValue).append("\r\n");
}
}
responseContent.append("\r\n");
}
final QueryStringDecoder queryStringDecoder = new QueryStringDecoder(httpRequest.getUri());
final Map<String, List<String>> parameterDictionary = queryStringDecoder.getParameters();
if (!parameterDictionary.isEmpty()) {
for (final Entry<String, List<String>> parameter : parameterDictionary.entrySet()) {
final String key = parameter.getKey();
final List<String> values = parameter.getValue();
for (final String value : values) {
responseContent.append("PARAM: ").append(key).append(" = ").append(value).append("\r\n");
}
}
responseContent.append("\r\n");
}
if (httpRequest.isChunked()) {
isReadingChunks = true;
} else {
final ChannelBuffer content = httpRequest.getContent();
if (content.readable()) {
responseContent.append("CONTENT: ").append(content.toString(CharsetUtil.UTF_8)).append("\r\n");
}
writeResponse(messageEvent);
}
}
}
/** Writes the response.
*
* @param messageEvent the message event
*/
private void writeResponse(final MessageEvent messageEvent) {
//Preconditions
assert messageEvent != null : "messageEvent must not be null";
// convert the response content to a channel buffer
final ChannelBuffer channelBuffer = ChannelBuffers.copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8);
responseContent.setLength(0);
// decide whether to close the connection or not
final boolean isConnectionToBeClosed =
HttpHeaders.Values.CLOSE.equalsIgnoreCase(httpRequest.getHeader(HttpHeaders.Names.CONNECTION))
|| httpRequest.getProtocolVersion().equals(HttpVersion.HTTP_1_0)
&& !HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase(httpRequest.getHeader(HttpHeaders.Names.CONNECTION));
// build the response object
final HttpResponse httpResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
httpResponse.setContent(channelBuffer);
httpResponse.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8");
if (!isConnectionToBeClosed) {
// There's no need to add 'Content-Length' header
// if this is the last response.
httpResponse.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(channelBuffer.readableBytes()));
}
final String cookieString = httpRequest.getHeader(HttpHeaders.Names.COOKIE);
if (cookieString != null) {
final CookieDecoder cookieDecoder = new CookieDecoder();
final Set<Cookie> cookies = cookieDecoder.decode(cookieString);
if (!cookies.isEmpty()) {
// Reset the cookies if necessary.
final CookieEncoder cookieEncoder = new CookieEncoder(true);
for (final Cookie cookie : cookies) {
cookieEncoder.addCookie(cookie);
}
httpResponse.addHeader(HttpHeaders.Names.SET_COOKIE, cookieEncoder.encode());
}
}
// write the response
final ChannelFuture channelFuture = messageEvent.getChannel().write(httpResponse);
// lose the connection after the write operation is done if necessary
if (isConnectionToBeClosed) {
channelFuture.addListener(ChannelFutureListener.CLOSE);
}
}
/** 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) {
LOGGER.info(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();
LOGGER.info("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) {
continue;
} else {
channelPipeline.remove(channelHandler);
}
}
final WebSocketSslServerHandler webSocketSslServerHandler = new MockWebSocketSslServerHandler(null);
channelPipeline.addLast("encoder", new HttpResponseEncoder());
channelPipeline.addLast("decoder", new HttpRequestDecoder());
channelPipeline.addLast("aggregator", new HttpChunkAggregator(1048576));
channelPipeline.addLast("ws-request-handler", webSocketSslServerHandler);
LOGGER.info("web socket channel pipeline: " + channelPipeline);
webSocketSslServerHandler.handshake(httpRequest, channelHandlerContext);
}
}