/* * NettyHTTPUtils.java * * Created on Mar 5, 2010, 12:12:19 PM * * Description: Netty-related HTTP utilities. * * Copyright (C) Mar 5, 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.utils; import java.util.List; import net.jcip.annotations.NotThreadSafe; import org.apache.log4j.Logger; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.handler.codec.http.DefaultHttpResponse; import org.jboss.netty.handler.codec.http.HttpHeaders; 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; import org.jboss.netty.util.CharsetUtil; /** Netty-related HTTP utilities. * * @author reed */ @NotThreadSafe public final class NettyHTTPUtils { /** the logger */ private static final Logger LOGGER = Logger.getLogger(NettyHTTPUtils.class); /** Prevents the instantiation of this utility class. */ private NettyHTTPUtils() { } /** Returns the Texai session cookie from the given HTTP request, or null if not found. * * @param httpRequest the given HTTP request * @return the Texai session cookie or null if not found */ public static String getTexaiSessionCookie(final HttpRequest httpRequest) { //Preconditions assert httpRequest != null : "httpRequest must not be null"; final List<String> cookieHeaders = httpRequest.getHeaders("Cookie"); for (final String cookieHeader : cookieHeaders) { // texai-session=9ec68e1d-8fd2-4ade-8c06-651cc7ee4c7c if (cookieHeader.startsWith("texai-session=")) { return cookieHeader.substring(14); } } return null; } /** Writes a HTML text response. * * @param httpRequest the HTTP request * @param responseContent the response content * @param channel the channel */ public static void writeHTMLResponse( final HttpRequest httpRequest, final StringBuilder responseContent, final Channel channel) { //Preconditions assert httpRequest != null : "httpRequest must not be null"; assert responseContent != null : "responseContent must not be null"; assert responseContent.length() > 0 : "responseContent must not be empty"; assert channel != null : "channel must not be null"; writeHTMLResponse( httpRequest, responseContent.toString(), channel, null); // sessionCookie } /** Writes a HTML response. * * @param httpRequest the HTTP request * @param responseContent the response content * @param channel the channel * @param sessionCookie the session cookie */ public static void writeHTMLResponse( final HttpRequest httpRequest, final String responseContent, final Channel channel, final String sessionCookie) { //Preconditions assert httpRequest != null : "httpRequest must not be null"; assert responseContent != null : "responseContent must not be null"; assert !responseContent.isEmpty() : "responseContent must not be empty"; assert channel != null : "channel must not be null"; // convert the response content to a channel buffer final ChannelBuffer channelBuffer = ChannelBuffers.copiedBuffer(responseContent, CharsetUtil.UTF_8); // 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); if (sessionCookie != null) { httpResponse.setHeader(HttpHeaders.Names.SET_COOKIE, "texai-session=" + sessionCookie); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("writeHTMLResponse, sessionCookie=" + sessionCookie); } setAccessControl(httpRequest, httpResponse); httpResponse.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8"); httpResponse.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(channelBuffer.readableBytes())); // write the response final ChannelFuture channelFuture = channel.write(httpResponse); // close the connection after the write operation is done if necessary if (isConnectionToBeClosed) { channelFuture.addListener(ChannelFutureListener.CLOSE); } } /** Writes a text response. * * @param httpRequest the HTTP request * @param responseContent the response content * @param channel the channel */ public static void writeTextResponse( final HttpRequest httpRequest, final String responseContent, final Channel channel) { //Preconditions assert httpRequest != null : "httpRequest must not be null"; assert responseContent != null : "responseContent must not be null"; assert !responseContent.isEmpty() : "responseContent must not be empty"; assert channel != null : "channel must not be null"; // convert the response content to a channel buffer final ChannelBuffer channelBuffer = ChannelBuffers.copiedBuffer(responseContent, CharsetUtil.UTF_8); // 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); setAccessControl(httpRequest, httpResponse); httpResponse.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8"); httpResponse.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(channelBuffer.readableBytes())); // write the response final ChannelFuture channelFuture = channel.write(httpResponse); // close the connection after the write operation is done if necessary if (isConnectionToBeClosed) { channelFuture.addListener(ChannelFutureListener.CLOSE); } } /** Writes a CSS response. * * @param httpRequest the HTTP request * @param responseContent the response content * @param channel the channel */ public static void writeCSSResponse( final HttpRequest httpRequest, final String responseContent, final Channel channel) { //Preconditions assert httpRequest != null : "httpRequest must not be null"; assert responseContent != null : "responseContent must not be null"; assert !responseContent.isEmpty() : "responseContent must not be empty"; assert channel != null : "channel must not be null"; // convert the response content to a channel buffer final ChannelBuffer channelBuffer = ChannelBuffers.copiedBuffer(responseContent, CharsetUtil.UTF_8); // 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); setAccessControl(httpRequest, httpResponse); httpResponse.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/css; charset=UTF-8"); httpResponse.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(channelBuffer.readableBytes())); // write the response final ChannelFuture channelFuture = channel.write(httpResponse); // close the connection after the write operation is done if necessary if (isConnectionToBeClosed) { channelFuture.addListener(ChannelFutureListener.CLOSE); } } /** Writes a binary response. * * @param httpRequest the HTTP request * @param responseContentBytes the response content bytes * @param channel the channel * @param sessionCookie the session cookie */ public static void writeBinaryResponse( final HttpRequest httpRequest, final byte[] responseContentBytes, final Channel channel, final String sessionCookie) { //Preconditions assert httpRequest != null : "httpRequest must not be null"; assert responseContentBytes != null : "responseContentBytes must not be null"; assert responseContentBytes.length > 0 : "responseContentBytes must not be empty"; assert channel != null : "channel must not be null"; // convert the response content to a channel buffer final ChannelBuffer channelBuffer = ChannelBuffers.wrappedBuffer(responseContentBytes); // 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, "application/octet-stream"); if (sessionCookie != null) { httpResponse.setHeader(HttpHeaders.Names.SET_COOKIE, "texai-session=" + sessionCookie); } setAccessControl(httpRequest, httpResponse); httpResponse.setChunked(false); httpResponse.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(channelBuffer.readableBytes())); // when debugging multi-threading issues, it is sometimes useful to echo the requesting user-agent //httpResponse.setHeader(HttpHeaders.Names.USER_AGENT, httpRequest.getHeader(HttpHeaders.Names.USER_AGENT)); // write the response final ChannelFuture channelFuture = channel.write(httpResponse); // close the connection after the write operation is done if necessary if (isConnectionToBeClosed) { channelFuture.addListener(ChannelFutureListener.CLOSE); } } /** Set access control headers on the HTTP response. * * @param httpRequest the HTTP request * @param httpResponse the HTTP response */ private static void setAccessControl( final HttpRequest httpRequest, final HttpResponse httpResponse) { //Preconditions assert httpRequest != null : "httpRequest must not be null"; assert httpResponse != null : "httpResponse must not be null"; String origin = httpRequest.getHeader(HttpHeaders.Names.ORIGIN); if (origin == null) { origin = "*"; } httpResponse.setHeader("Access-Control-Allow-Origin", origin); httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); } }