Revision: 8449
Author: [email protected]
Date: Fri Jul 30 12:54:15 2010
Log: No longer require RPCs to contain a 'Content-Length' HTTP request header, thus
enabling support for XHR with 'Transfer-Encoding: Chunked'.

Review at http://gwt-code-reviews.appspot.com/727801

Review by: [email protected]
http://code.google.com/p/google-web-toolkit/source/detail?r=8449

Modified:
 /trunk/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java
 /trunk/user/test/com/google/gwt/user/server/rpc/RPCServletUtilsTest.java

=======================================
--- /trunk/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java Fri Nov 20 09:36:38 2009 +++ /trunk/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java Fri Jul 30 12:54:15 2010
@@ -31,6 +31,11 @@
  * the RPC system.
  */
 public class RPCServletUtils {
+  /**
+   * Package protected for use in tests.
+   */
+  static final int BUFFER_SIZE = 4096;
+
   private static final String ACCEPT_ENCODING = "Accept-Encoding";

   private static final String ATTACHMENT = "attachment";
@@ -156,39 +161,32 @@
    *         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/x-gwt-rpc' and 'charset=utf-8'
+   * @throws ServletException if the request's content type is not
+   *         'text/x-gwt-rpc' and 'charset=utf-8'
    */
   public static String readContentAsUtf8(HttpServletRequest request,
       boolean checkHeaders) throws IOException, ServletException {
-    int contentLength = request.getContentLength();
-    if (contentLength == -1) {
-      // Content length must be known.
-      throw new ServletException("Content-Length must be specified");
-    }
-
     if (checkHeaders) {
       checkContentType(request);
       checkCharacterEncoding(request);
     }

+    /*
+     * Need to support 'Transfer-Encoding: chunked', so do not rely on
+     * presence of a 'Content-Length' request header.
+     */
     InputStream in = request.getInputStream();
+    byte[] buffer = new byte[BUFFER_SIZE];
+    ByteArrayOutputStream out = new  ByteArrayOutputStream(BUFFER_SIZE);
     try {
-      byte[] payload = new byte[contentLength];
-      int offset = 0;
-      int len = contentLength;
-      int byteCount;
-      while (offset < contentLength) {
-        byteCount = in.read(payload, offset, len);
+      while (true) {
+        int byteCount = in.read(buffer);
         if (byteCount == -1) {
-          throw new ServletException("Client did not send " + contentLength
-              + " bytes as expected");
-        }
-        offset += byteCount;
-        len -= byteCount;
-      }
-      return new String(payload, CHARSET_UTF8);
+          break;
+        }
+        out.write(buffer, 0, byteCount);
+      }
+      return out.toString(CHARSET_UTF8);
     } finally {
       if (in != null) {
         in.close();
=======================================
--- /trunk/user/test/com/google/gwt/user/server/rpc/RPCServletUtilsTest.java Wed Oct 28 09:10:53 2009 +++ /trunk/user/test/com/google/gwt/user/server/rpc/RPCServletUtilsTest.java Fri Jul 30 12:54:15 2010
@@ -16,9 +16,13 @@

 package com.google.gwt.user.server.rpc;

+import com.google.gwt.user.client.rpc.UnicodeEscapingTest;
+
 import junit.framework.TestCase;

+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;

 import javax.servlet.ServletException;
 import javax.servlet.ServletInputStream;
@@ -34,11 +38,17 @@
    * Mocks a request with the specified Content-Type.
    */
   class MockReqContentType extends MockHttpServletRequest {
-    String mockContent = "abcdefg";
+    final String mockContent;
     final String mockContentType;

+
     public MockReqContentType(String contentType) {
+      this(contentType, "abcdefg");
+    }
+
+    public MockReqContentType(String contentType, String content) {
       this.mockContentType = contentType;
+      this.mockContent = content;
     }

     @Override
@@ -58,12 +68,9 @@

     @Override
     public String getHeader(String name) {
-      if (name.toLowerCase().equals("Content-Type")) {
-        return mockContentType;
-      }
-      return "";
-    }
-
+      return null;
+    }
+
     @SuppressWarnings("unused")
     @Override
     public ServletInputStream getInputStream() throws IOException {
@@ -72,11 +79,10 @@
   }

   static class MockServletInputStream extends ServletInputStream {
-    private boolean readOnce = false;
-    final private String value;
-
-    MockServletInputStream(String value) {
-      this.value = value;
+    private ByteArrayInputStream realStream;
+
+ MockServletInputStream(String mockContent) throws UnsupportedEncodingException {
+      realStream = new ByteArrayInputStream(mockContent.getBytes("UTF-8"));
     }

     @Override
@@ -86,33 +92,77 @@

     @Override
     public int read(byte[] b, int off, int len) throws IOException {
-      if (readOnce) {
-        // simulate EOF
-        return -1;
-      }
-      readOnce = true;
-
-      int pos = 0;
-      int i;
-      for (i = off; i < len; ++i, ++pos) {
-        b[i] = (byte) (this.value.charAt(pos) % 0xff);
-      }
-      return i;
-    }
-
-    @Override
-    public int readLine(byte[] b, int off, int len) throws IOException {
-      return read(b, off, len);
-    }
+      return realStream.read(b, off, len);
+    }
+  }
+
+  /**
+   * Large content length should be read correctly.
+   */
+ public void testContentLengthLarge() throws IOException, ServletException {
+    // Choose a non trivial size RPC payload
+    int contentLength = 50000;
+ String content = UnicodeEscapingTest.getStringContainingCharacterRange(0, contentLength);
+    String result = readContentAsUtf8(content);
+    assertEquals(content, result);
   }

   /**
-   * Content type doesn't match x-gwt-rpc, but ignore it.
+   * Content length smaller than the buffer size should be read correctly.
    */
- public void testIgnoreContentType() throws IOException, ServletException {
-    HttpServletRequest m = new MockReqContentType(
-        "application/www-form-encoded");
-    RPCServletUtils.readContentAsUtf8(m, false);
+ public void testContentLengthLessThanBufferSize() throws IOException, ServletException {
+    // Choose a value smaller than the buffer
+    int contentLength = RPCServletUtils.BUFFER_SIZE - 1;
+ String content = UnicodeEscapingTest.getStringContainingCharacterRange(0, contentLength);
+    String result = readContentAsUtf8(content);
+    assertEquals(content, result);
+  }
+
+  /**
+ * Content length which is an integer multiple of buffer size should be read
+   * correctly.
+   */
+ public void testContentLengthMultipleOfBufferSize() throws IOException, ServletException {
+    // Choose a value which is not a multiple of the buffer size
+    int contentLength = RPCServletUtils.BUFFER_SIZE * 3;
+ String content = UnicodeEscapingTest.getStringContainingCharacterRange(0, contentLength);
+    String result = readContentAsUtf8(content);
+    assertEquals(content, result);
+  }
+
+  /**
+ * Content length which is not an integer multiple of buffer size should be
+   * read correctly.
+   */
+ public void testContentLengthNotMultipleOfBufferSize() throws IOException, ServletException {
+    // Choose a value which is not a multiple of the buffer size
+    int contentLength = RPCServletUtils.BUFFER_SIZE * 3 + 1;
+ String content = UnicodeEscapingTest.getStringContainingCharacterRange(0, contentLength);
+    String result = readContentAsUtf8(content);
+    assertEquals(content, result);
+  }
+
+  /**
+   * Content length smaller than the buffer size should be read correctly.
+   */
+ public void testContentLengthSlightlyLargerThanBufferSize() throws IOException, ServletException {
+    // Choose a value slightly larger than the buffer
+    int contentLength = RPCServletUtils.BUFFER_SIZE + 1;
+ String content = UnicodeEscapingTest.getStringContainingCharacterRange(0, contentLength);
+    String result = readContentAsUtf8(content);
+    assertEquals(content, result);
+  }
+
+  /**
+ * Zero content length is never expected, but being able to correctly read
+   * zero length content is a useful boundary condition test.
+   */
+ public void testContentLengthZero() throws IOException, ServletException {
+    // While zero content length is not actually useful, a test
+    int contentLength = 0;
+ String content = UnicodeEscapingTest.getStringContainingCharacterRange(0, contentLength);
+    String result = readContentAsUtf8(content);
+    assertEquals(content, result);
   }

   /**
@@ -130,6 +180,14 @@

     RPCServletUtils.readContentAsUtf8(m, false);
   }
+
+  /**
+   * Content type doesn't match x-gwt-rpc, but ignore it.
+   */
+ public void testIgnoreContentType() throws IOException, ServletException { + HttpServletRequest m = new MockReqContentType("application/www-form-encoded");
+    RPCServletUtils.readContentAsUtf8(m, false);
+  }

   /**
    * A non UTF-8 character encoding should be rejected.
@@ -229,4 +287,9 @@
       fail("Expected exception from null content type");
     }
   }
-}
+
+ private String readContentAsUtf8(String content) throws IOException, ServletException { + HttpServletRequest m = new MockReqContentType("text/x-gwt-rpc", content);
+    return RPCServletUtils.readContentAsUtf8(m, false);
+  }
+}

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to