/*
* Weblounge: Web Content Management System
* Copyright (c) 2003 - 2011 The Weblounge Team
* http://entwinemedia.com/weblounge
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package ch.entwine.weblounge.common.impl.request;
import ch.entwine.weblounge.common.request.WebloungeResponse;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/**
* Utility class used to facilitate in creating <code>HTTP 1.1</code> responses
* and header analysis.
*
* TODO: use set instead of array
*/
public final class Http11Utils implements Http11Constants {
/** the request methods supported by this handler */
public static final String[] DEFAULT_METHODS = {
METHOD_GET,
METHOD_HEAD,
METHOD_POST };
/** the default allow string */
public static final String DEFAULT_ALLOW = buildAllowString(DEFAULT_METHODS);
/**
* This class is not intended to be instantiated.
*/
private Http11Utils() {
// Nothing to be done here
}
/**
* Constructs an HTTP "Allow" response header for the allowed request methods.
*
* @param methods
* the allowed methods
* @return an "Allow" response header
*/
private static String buildAllowString(String[] methods) {
StringBuffer b = new StringBuffer(METHOD_OPTIONS);
for (int i = 0; i < methods.length; i++) {
b.append(',');
b.append(methods[i]);
}
return b.toString();
}
/**
* Checks the request against the default request methods that all <code>
* RequestHandlers</code> must handle. These are:
* <ul>
* <li>GET
* <li>HEAD
* <li>POST
* </ul>
* The OPTIONS request method is handled transparently. <br>
* If the check succeeds, the caller can assume that the request methods is
* one of the default methods and should be handled accordingly. <br>
* If the check fails, the request is either an OPTIONS request or is not
* allowed. But is has already been handled and the caller doesn't need to do
* any further request handling.
*
* @param method
* the request method to check against and handle
* @param response
* the response of the request
* @return <code>true</code>if the check succeeded and the caller can handle
* the default methods, <code>false</code> if the check failed and the
* request has already been handled.
*/
public static boolean checkDefaultMethods(String method,
HttpServletResponse response) {
// handle OPTIONS requests
if (METHOD_OPTIONS.equals(method)) {
response.setHeader(HEADER_ALLOW, DEFAULT_ALLOW);
response.setContentLength(0);
return false;
}
// check whether the methods is allowed
for (int i = 0; i < DEFAULT_METHODS.length; i++)
if (DEFAULT_METHODS[i].equals(method))
return true;
// invalid request method
response.setHeader(HEADER_ALLOW, DEFAULT_ALLOW);
try {
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
} catch (IOException e) { /* ignore */
}
return false;
}
/**
* Checks the request against a custom set of supported methods.
*
* @see Http11Utils#checkDefaultMethods(String, WebloungeResponse)
* @param method
* the request method to check against and handle
* @param response
* the response of the request
* @param methods
* @return <code>true</code>if the check succeeded and the caller can handle
* the default methods, <code>false</code> if the check failed and the
* request has already been handled.
*/
public static boolean checkMethods(String method,
HttpServletResponse response, String[] methods) {
// handle OPTIONS requests
if (METHOD_OPTIONS.equals(method)) {
response.setHeader(HEADER_ALLOW, buildAllowString(methods));
response.setContentLength(0);
return false;
}
// check whether the methods is allowed
for (int i = 0; i < methods.length; i++)
if (methods[i].equals(method))
return true;
// invalid request method
response.setHeader(HEADER_ALLOW, buildAllowString(methods));
try {
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
} catch (IOException e) { /* ignore */
}
return false;
}
/**
* Prepares a response for handling a HEAD request using a GET handler. <br>
* NOTE: This method should not be used in conjunction with the cache, since
* the cache can do a much better job in handling HEAD requests than just
* discarding all its output! <br>
* NOTE: This method should not be used with the Http11ProtocolHandler, since
* it already handles HEAD requests correctly and much more efficiently!
*
* @param resp
* the original response
*/
public static void startHeadResponse(WebloungeResponse resp) {
WebloungeResponseImpl r = (WebloungeResponseImpl) resp;
r.setResponse(new DiscardBodyResponse((HttpServletResponse) r.getResponse()));
}
/**
* Finishes a HEAD response that was started with <code>startHeadResponse()
* <code>.
*
* @param resp
* the original response
*/
public static void endHeadResponse(WebloungeResponse resp) {
DiscardBodyResponse b = (DiscardBodyResponse) ((WebloungeResponseImpl) resp).getResponse();
b.finishOutput();
}
/**
* Calculates an ETag for a given modification time.
*
* @param modified
* the modification time in milliseconds since the epoch
* @return String the ETag
*/
public static String calcETag(long modified) {
return "\"WL-" + Long.toHexString(modified) + "\"";
}
/**
* A servlet response that make sure no HTTP response body is sent back to the
* client.
*/
private static class DiscardBodyResponse extends HttpServletResponseWrapper {
/** the servlet output stream that discards all output */
private NullOutputStream os = new NullOutputStream();
/** the servlet writer that discards all output */
private PrintWriter pw;
/**
* Creates a new <code>DiscardBodyResponse</code>.
*
* @param resp
* the original response
*/
protected DiscardBodyResponse(HttpServletResponse resp) {
super(resp);
}
/**
* Ends a response and makes sure, the content length is set correctly.
*/
protected void finishOutput() {
getResponse().setContentLength(os.getBytesWritten());
}
/**
* @see javax.servlet.ServletResponse#getOutputStream()
*/
public ServletOutputStream getOutputStream() throws IOException {
return os;
}
/**
* @see javax.servlet.ServletResponse#getWriter()
*/
public PrintWriter getWriter() throws IOException {
if (pw == null)
pw = new PrintWriter(new OutputStreamWriter(os, getResponse().getCharacterEncoding()));
return pw;
}
}
/**
* Output stream that discards all output and only counts the number of bytes
* that have been written.
*/
static class NullOutputStream extends ServletOutputStream {
/** the number of bytes that have been written to this stream */
private int bytesWritten = 0;
/**
* @see java.io.OutputStream#write(byte[], int, int)
*/
public void write(byte[] b, int off, int len) throws IOException {
bytesWritten += len;
}
/**
* @see java.io.OutputStream#write(int)
*/
public void write(int b) throws IOException {
bytesWritten++;
}
/**
* Returns the number of bytes written to this stream.
*
* @return the number of bytes written to this stream
*/
protected int getBytesWritten() {
return bytesWritten;
}
}
}