This patch addresses issues 1179 (provide access to read and write methods on RemoteServiceServlet) and  issue 1613 (HttpServletRequest content type checking should be case insensitive).  Basically, I moved the RSS methods into a utility class called RPCServletUtils and there added a case insensitive check for content type. 

Patch by: mmendez
Review by: scottb

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1538 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java b/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java
new file mode 100644
index 0000000..5053f73
--- /dev/null
+++ b/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2007 Google Inc.
+ * 
+ * Licensed 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 com.google.gwt.user.server.rpc;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Utility class containing helper methods used by servlets that integrate with
+ * the RPC system.
+ */
+public class RPCServletUtils {
+  private static final String ACCEPT_ENCODING = "Accept-Encoding";
+
+  private static final String CHARSET_UTF8 = "UTF-8";
+
+  private static final String CONTENT_ENCODING = "Content-Encoding";
+
+  private static final String CONTENT_ENCODING_GZIP = "gzip";
+
+  private static final String CONTENT_TYPE_TEXT_PLAIN_UTF8 = "text/plain; charset=utf-8";
+
+  private static final String GENERIC_FAILURE_MSG = "The call failed on the server; see server log for details";
+
+  /**
+   * Controls the compression threshold at and below which no compression will
+   * take place.
+   */
+  private static final int UNCOMPRESSED_BYTE_SIZE_LIMIT = 256;
+
+  /**
+   * Returns <code>true</code> if the {@link HttpServletRequest} accepts Gzip
+   * encoding. This is done by checking that the accept-encoding header
+   * specifies gzip as a supported encoding.
+   * 
+   * @param request the request instance to test for gzip encoding acceptance
+   * @return <code>true</code> if the {@link HttpServletRequest} accepts Gzip
+   *         encoding
+   */
+  public static boolean acceptsGzipEncoding(HttpServletRequest request) {
+    assert (request != null);
+
+    String acceptEncoding = request.getHeader(ACCEPT_ENCODING);
+    if (null == acceptEncoding) {
+      return false;
+    }
+
+    return (acceptEncoding.indexOf(CONTENT_ENCODING_GZIP) != -1);
+  }
+
+  /**
+   * Returns <code>true</code> if the response content's estimated UTF-8 byte
+   * length exceeds 256 bytes.
+   * 
+   * @param content the contents of the response
+   * @return <code>true</code> if the response content's estimated UTF-8 byte
+   *         length exceeds 256 bytes
+   */
+  public static boolean exceedsUncompressedContentLengthLimit(String content) {
+    return (content.length() * 2) > UNCOMPRESSED_BYTE_SIZE_LIMIT;
+  }
+
+  /**
+   * Returns the content of an {@link HttpServletRequest} by decoding it using
+   * the UTF-8 charset.
+   * 
+   * @param request the servlet request whose content we want to read
+   * @return the content of an {@link HttpServletRequest} by decoding it using
+   *         the UTF-8 charset
+   * @throws IOException if the requests input stream cannot be accessed, read
+   *           from or closed
+   * @throws ServletException if the content length of the request is not
+   *           specified of if the request's content type is not 'text/plain' or
+   *           'charset=utf-8'
+   */
+  public static String readContentAsUtf8(HttpServletRequest request)
+      throws IOException, ServletException {
+    int contentLength = request.getContentLength();
+    if (contentLength == -1) {
+      // Content length must be known.
+      throw new ServletException("Content-Length must be specified");
+    }
+
+    String contentType = request.getContentType();
+    boolean contentTypeIsOkay = false;
+    // Content-Type must be specified.
+    if (contentType != null) {
+      contentType = contentType.toLowerCase();
+      // The type must be plain text.
+      if (contentType.startsWith("text/plain")) {
+        // And it must be UTF-8 encoded (or unspecified, in which case we assume
+        // that it's either UTF-8 or ASCII).
+        if (contentType.indexOf("charset=") == -1) {
+          contentTypeIsOkay = true;
+        } else if (contentType.indexOf("charset=utf-8") != -1) {
+          contentTypeIsOkay = true;
+        }
+      }
+    }
+
+    if (!contentTypeIsOkay) {
+      throw new ServletException(
+          "Content-Type must be 'text/plain' with 'charset=utf-8' (or unspecified charset)");
+    }
+
+    InputStream in = request.getInputStream();
+    try {
+      byte[] payload = new byte[contentLength];
+      int offset = 0;
+      int len = contentLength;
+      int byteCount;
+      while (offset < contentLength) {
+        byteCount = in.read(payload, offset, len);
+        if (byteCount == -1) {
+          throw new ServletException("Client did not send " + contentLength
+              + " bytes as expected");
+        }
+        offset += byteCount;
+        len -= byteCount;
+      }
+      return new String(payload, CHARSET_UTF8);
+    } finally {
+      if (in != null) {
+        in.close();
+      }
+    }
+  }
+
+  /**
+   * Returns <code>true</code> if the request accepts gzip encoding and the
+   * the response content's estimated UTF-8 byte length exceeds 256 bytes.
+   * 
+   * @param request the request associated with the response content
+   * @param responseContent a string that will be
+   * @return <code>true</code> if the request accepts gzip encoding and the
+   *         the response content's estimated UTF-8 byte length exceeds 256
+   *         bytes
+   */
+  public static boolean shouldGzipResponseContent(HttpServletRequest request,
+      String responseContent) {
+    return acceptsGzipEncoding(request)
+        && exceedsUncompressedContentLengthLimit(responseContent);
+  }
+
+  /**
+   * Write the response content into the {@link HttpServletResponse}. If
+   * <code>gzipResponse</code> is <code>true</code>, the response content
+   * will be gzipped prior to being written into the response.
+   * 
+   * @param servletContext servlet context for this response
+   * @param response response instance
+   * @param responseContent a string containing the response content
+   * @param gzipResponse if <code>true</code> the response content will be
+   *          gzip encoded before being written into the response
+   * @throws IOException if reading, writing, or closing the response's output
+   *           stream fails
+   */
+  public static void writeResponse(ServletContext servletContext,
+      HttpServletResponse response, String responseContent, boolean gzipResponse)
+      throws IOException {
+
+    byte[] responseBytes = responseContent.getBytes(CHARSET_UTF8);
+    if (gzipResponse) {
+      // Compress the reply and adjust headers.
+      //
+      ByteArrayOutputStream output = null;
+      GZIPOutputStream gzipOutputStream = null;
+      Throwable caught = null;
+      try {
+        output = new ByteArrayOutputStream(responseBytes.length);
+        gzipOutputStream = new GZIPOutputStream(output);
+        gzipOutputStream.write(responseBytes);
+        gzipOutputStream.finish();
+        gzipOutputStream.flush();
+        response.setHeader(CONTENT_ENCODING, CONTENT_ENCODING_GZIP);
+        responseBytes = output.toByteArray();
+      } catch (IOException e) {
+        caught = e;
+      } finally {
+        if (null != gzipOutputStream) {
+          gzipOutputStream.close();
+        }
+        if (null != output) {
+          output.close();
+        }
+      }
+
+      if (caught != null) {
+        servletContext.log("Unable to compress response", caught);
+        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        return;
+      }
+    }
+
+    // Send the reply.
+    //
+    response.setContentLength(responseBytes.length);
+    response.setContentType(CONTENT_TYPE_TEXT_PLAIN_UTF8);
+    response.setStatus(HttpServletResponse.SC_OK);
+    response.getOutputStream().write(responseBytes);
+  }
+
+  /**
+   * Called when the servlet itself has a problem, rather than the invoked
+   * third-party method. It writes a simple 500 message back to the client.
+   * 
+   * @param servletContext
+   * @param response
+   * @param failure
+   */
+  public static void writeResponseForUnexpectedFailure(
+      ServletContext servletContext, HttpServletResponse response,
+      Throwable failure) {
+    servletContext.log("Exception while dispatching incoming RPC call", failure);
+
+    // Send GENERIC_FAILURE_MSG with 500 status.
+    //
+    try {
+      response.setContentType("text/plain");
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+      response.getWriter().write(GENERIC_FAILURE_MSG);
+    } catch (IOException ex) {
+      servletContext.log(
+          "respondWithUnexpectedFailure failed while sending the previous failure to the client",
+          ex);
+    }
+  }
+
+  private RPCServletUtils() {
+    // Not instantiable
+  }
+}
diff --git a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
index ce1d3c1..d4d0b2a 100644
--- a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
+++ b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
@@ -18,7 +18,6 @@
 import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
 import com.google.gwt.user.client.rpc.SerializationException;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
@@ -26,10 +25,8 @@
 import java.text.ParseException;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.zip.GZIPOutputStream;
 
 import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -41,106 +38,6 @@
  */
 public class RemoteServiceServlet extends HttpServlet implements
     SerializationPolicyProvider {
-  /*
-   * These members are used to get and set the different HttpServletResponse and
-   * HttpServletRequest headers.
-   */
-  private static final String ACCEPT_ENCODING = "Accept-Encoding";
-
-  private static final String CHARSET_UTF8 = "UTF-8";
-  private static final String CONTENT_ENCODING = "Content-Encoding";
-  private static final String CONTENT_ENCODING_GZIP = "gzip";
-
-  private static final String CONTENT_TYPE_TEXT_PLAIN_UTF8 = "text/plain; charset=utf-8";
-  private static final String GENERIC_FAILURE_MSG = "The call failed on the server; see server log for details";
-  /**
-   * Controls the compression threshold at and below which no compression will
-   * take place.
-   */
-  private static final int UNCOMPRESSED_BYTE_SIZE_LIMIT = 256;
-
-  /**
-   * Return true if the response object accepts Gzip encoding. This is done by
-   * checking that the accept-encoding header specifies gzip as a supported
-   * encoding.
-   */
-  private static boolean acceptsGzipEncoding(HttpServletRequest request) {
-    assert (request != null);
-
-    String acceptEncoding = request.getHeader(ACCEPT_ENCODING);
-    if (null == acceptEncoding) {
-      return false;
-    }
-
-    return (acceptEncoding.indexOf(CONTENT_ENCODING_GZIP) != -1);
-  }
-
-  /**
-   * This method attempts to estimate the number of bytes that a string will
-   * consume when it is sent out as part of an HttpServletResponse. This really
-   * a hack since we are assuming that every character will consume two bytes
-   * upon transmission. This is definitely not true since some characters
-   * actually consume more than two bytes and some consume less. This is even
-   * less accurate if the string is converted to UTF8. However, it does save us
-   * from converting every string that we plan on sending back to UTF8 just to
-   * determine that we should not compress it.
-   */
-  private static int estimateByteSize(final String buffer) {
-    return (buffer.length() * 2);
-  }
-
-  /**
-   * Read the payload as UTF-8 from the request stream.
-   */
-  private static String readPayloadAsUtf8(HttpServletRequest request)
-      throws IOException, ServletException {
-    int contentLength = request.getContentLength();
-    if (contentLength == -1) {
-      // Content length must be known.
-      throw new ServletException("Content-Length must be specified");
-    }
-
-    String contentType = request.getContentType();
-    boolean contentTypeIsOkay = false;
-    // Content-Type must be specified.
-    if (contentType != null) {
-      // The type must be plain text.
-      if (contentType.startsWith("text/plain")) {
-        // And it must be UTF-8 encoded (or unspecified, in which case we assume
-        // that it's either UTF-8 or ASCII).
-        if (contentType.indexOf("charset=") == -1) {
-          contentTypeIsOkay = true;
-        } else if (contentType.indexOf("charset=utf-8") != -1) {
-          contentTypeIsOkay = true;
-        }
-      }
-    }
-    if (!contentTypeIsOkay) {
-      throw new ServletException(
-          "Content-Type must be 'text/plain' with 'charset=utf-8' (or unspecified charset)");
-    }
-    InputStream in = request.getInputStream();
-    try {
-      byte[] payload = new byte[contentLength];
-      int offset = 0;
-      int len = contentLength;
-      int byteCount;
-      while (offset < contentLength) {
-        byteCount = in.read(payload, offset, len);
-        if (byteCount == -1) {
-          throw new ServletException("Client did not send " + contentLength
-              + " bytes as expected");
-        }
-        offset += byteCount;
-        len -= byteCount;
-      }
-      return new String(payload, "UTF-8");
-    } finally {
-      if (in != null) {
-        in.close();
-      }
-    }
-  }
 
   private final ThreadLocal<HttpServletRequest> perThreadRequest = new ThreadLocal<HttpServletRequest>();
 
@@ -176,7 +73,7 @@
 
       // Read the request fully.
       //
-      String requestPayload = readPayloadAsUtf8(request);
+      String requestPayload = RPCServletUtils.readContentAsUtf8(request);
 
       // Let subclasses see the serialized request.
       //
@@ -389,11 +286,8 @@
    */
   protected void doUnexpectedFailure(Throwable e) {
     ServletContext servletContext = getServletContext();
-    servletContext.log("Exception while dispatching incoming RPC call", e);
-
-    // Send GENERIC_FAILURE_MSG with 500 status.
-    //
-    respondWithFailure(getThreadLocalResponse());
+    RPCServletUtils.writeResponseForUnexpectedFailure(servletContext,
+        getThreadLocalResponse(), e);
   }
 
   /**
@@ -448,7 +342,7 @@
    */
   protected boolean shouldCompressResponse(HttpServletRequest request,
       HttpServletResponse response, String responsePayload) {
-    return estimateByteSize(responsePayload) > UNCOMPRESSED_BYTE_SIZE_LIMIT;
+    return RPCServletUtils.exceedsUncompressedContentLengthLimit(responsePayload);
   }
 
   private SerializationPolicy getCachedSerializationPolicy(
@@ -466,70 +360,12 @@
     }
   }
 
-  /**
-   * Called when the machinery of this class itself has a problem, rather than
-   * the invoked third-party method. It writes a simple 500 message back to the
-   * client.
-   */
-  private void respondWithFailure(HttpServletResponse response) {
-    try {
-      response.setContentType("text/plain");
-      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-      response.getWriter().write(GENERIC_FAILURE_MSG);
-    } catch (IOException e) {
-      getServletContext().log(
-          "respondWithFailure failed while sending the previous failure to the client",
-          e);
-    }
-  }
-
-  /**
-   * Write the response payload to the response stream.
-   */
   private void writeResponse(HttpServletRequest request,
       HttpServletResponse response, String responsePayload) throws IOException {
+    boolean gzipEncode = RPCServletUtils.acceptsGzipEncoding(request)
+        && shouldCompressResponse(request, response, responsePayload);
 
-    byte[] reply = responsePayload.getBytes(CHARSET_UTF8);
-    String contentType = CONTENT_TYPE_TEXT_PLAIN_UTF8;
-
-    if (acceptsGzipEncoding(request)
-        && shouldCompressResponse(request, response, responsePayload)) {
-      // Compress the reply and adjust headers.
-      //
-      ByteArrayOutputStream output = null;
-      GZIPOutputStream gzipOutputStream = null;
-      Throwable caught = null;
-      try {
-        output = new ByteArrayOutputStream(reply.length);
-        gzipOutputStream = new GZIPOutputStream(output);
-        gzipOutputStream.write(reply);
-        gzipOutputStream.finish();
-        gzipOutputStream.flush();
-        response.setHeader(CONTENT_ENCODING, CONTENT_ENCODING_GZIP);
-        reply = output.toByteArray();
-      } catch (IOException e) {
-        caught = e;
-      } finally {
-        if (null != gzipOutputStream) {
-          gzipOutputStream.close();
-        }
-        if (null != output) {
-          output.close();
-        }
-      }
-
-      if (caught != null) {
-        getServletContext().log("Unable to compress response", caught);
-        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-        return;
-      }
-    }
-
-    // Send the reply.
-    //
-    response.setContentLength(reply.length);
-    response.setContentType(contentType);
-    response.setStatus(HttpServletResponse.SC_OK);
-    response.getOutputStream().write(reply);
+    RPCServletUtils.writeResponse(getServletContext(), response,
+        responsePayload, gzipEncode);
   }
 }