/* * 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); } }