Adds a native XMLHttpRequest implementation, updates RequestBuilder to use it,
and removes the long-deprecated XMLHTTPRequest object from the User module.
This object uses no deferred-binding and is intended to be suitable for use in
Core.

Patch by: jgw
Review by: spoon


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@4779 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/http/HTTP.gwt.xml b/user/src/com/google/gwt/http/HTTP.gwt.xml
index 76d3238..c212dd4 100644
--- a/user/src/com/google/gwt/http/HTTP.gwt.xml
+++ b/user/src/com/google/gwt/http/HTTP.gwt.xml
@@ -18,5 +18,10 @@
 <!-- package must inherit this module.                                      -->
 <!--                                                                        -->
 <module>
-  <inherits name="com.google.gwt.core.Core"/>
+	<inherits name="com.google.gwt.core.Core"/>
+	<inherits name="com.google.gwt.xhr.XMLHttpRequest"/>
+
+	<!-- Inheriting User module for Window and Timer. These should be factored
+	     out of User soon. -->
+	<inherits name="com.google.gwt.user.User"/>
 </module>
diff --git a/user/src/com/google/gwt/http/client/Request.java b/user/src/com/google/gwt/http/client/Request.java
index ddc8ecd..fd0ea9d 100644
--- a/user/src/com/google/gwt/http/client/Request.java
+++ b/user/src/com/google/gwt/http/client/Request.java
@@ -16,22 +16,23 @@
 package com.google.gwt.http.client;
 
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
 import com.google.gwt.user.client.Timer;
+import com.google.gwt.xhr.client.XMLHttpRequest;
 
 /**
  * An HTTP request that is waiting for a response. Requests can be queried for
  * their pending status or they can be canceled.
  * 
- * <h3>Required Module</h3>
- * Modules that use this class should inherit
+ * <h3>Required Module</h3> Modules that use this class should inherit
  * <code>com.google.gwt.http.HTTP</code>.
  * 
- * {@gwt.include com/google/gwt/examples/http/InheritsExample.gwt.xml}
+ * {@gwt.include
+ * com/google/gwt/examples/http/InheritsExample.gwt.xml}
  * 
  */
 public class Request {
+
   /**
    * Creates a {@link Response} instance for the given JavaScript XmlHttpRequest
    * object.
@@ -39,45 +40,98 @@
    * @param xmlHttpRequest xmlHttpRequest object for which we need a response
    * @return a {@link Response} object instance
    */
-  private static Response createResponse(final JavaScriptObject xmlHttpRequest) {
-    assert (XMLHTTPRequest.isResponseReady(xmlHttpRequest));
+  private static Response createResponse(final XMLHttpRequest xmlHttpRequest) {
+    assert (isResponseReady(xmlHttpRequest));
     Response response = new Response() {
       @Override
       public String getHeader(String header) {
         StringValidator.throwIfEmptyOrNull("header", header);
 
-        return XMLHTTPRequest.getResponseHeader(xmlHttpRequest, header);
+        return xmlHttpRequest.getResponseHeader(header);
       }
 
       @Override
       public Header[] getHeaders() {
-        return XMLHTTPRequest.getHeaders(xmlHttpRequest);
+        return Request.getHeaders(xmlHttpRequest);
       }
 
       @Override
       public String getHeadersAsString() {
-        return XMLHTTPRequest.getAllResponseHeaders(xmlHttpRequest);
+        return xmlHttpRequest.getAllResponseHeaders();
       }
 
       @Override
       public int getStatusCode() {
-        return XMLHTTPRequest.getStatusCode(xmlHttpRequest);
+        return xmlHttpRequest.getStatus();
       }
 
       @Override
       public String getStatusText() {
-        return XMLHTTPRequest.getStatusText(xmlHttpRequest);
+        return xmlHttpRequest.getStatusText();
       }
 
       @Override
       public String getText() {
-        return XMLHTTPRequest.getResponseText(xmlHttpRequest);
+        return xmlHttpRequest.getResponseText();
       }
     };
     return response;
   }
 
   /**
+   * Returns an array of headers built by parsing the string of headers returned
+   * by the JavaScript <code>XmlHttpRequest</code> object.
+   * 
+   * @param xmlHttpRequest
+   * @return array of Header items
+   */
+  private static Header[] getHeaders(XMLHttpRequest xmlHttp) {
+    String allHeaders = xmlHttp.getAllResponseHeaders();
+    String[] unparsedHeaders = allHeaders.split("\n");
+    Header[] parsedHeaders = new Header[unparsedHeaders.length];
+
+    for (int i = 0, n = unparsedHeaders.length; i < n; ++i) {
+      String unparsedHeader = unparsedHeaders[i];
+
+      if (unparsedHeader.length() == 0) {
+        continue;
+      }
+
+      int endOfNameIdx = unparsedHeader.indexOf(':');
+      if (endOfNameIdx < 0) {
+        continue;
+      }
+
+      final String name = unparsedHeader.substring(0, endOfNameIdx).trim();
+      final String value = unparsedHeader.substring(endOfNameIdx + 1).trim();
+      Header header = new Header() {
+        @Override
+        public String getName() {
+          return name;
+        }
+
+        @Override
+        public String getValue() {
+          return value;
+        }
+
+        @Override
+        public String toString() {
+          return name + " : " + value;
+        }
+      };
+
+      parsedHeaders[i] = header;
+    }
+
+    return parsedHeaders;
+  }
+
+  private static boolean isResponseReady(XMLHttpRequest xhr) {
+    return xhr.getReadyState() == XMLHttpRequest.DONE;
+  }
+
+  /**
    * The number of milliseconds to wait for this HTTP request to complete.
    */
   private final int timeoutMillis;
@@ -93,7 +147,7 @@
    * not final because we transfer ownership of it to the HTTPResponse object
    * and set this field to null.
    */
-  private JavaScriptObject xmlHttpRequest;
+  private XMLHttpRequest xmlHttpRequest;
 
   /**
    * Constructs an instance of the Request object.
@@ -105,7 +159,7 @@
    * @throws IllegalArgumentException if timeoutMillis &lt; 0
    * @throws NullPointerException if xmlHttpRequest, or callback are null
    */
-  Request(JavaScriptObject xmlHttpRequest, int timeoutMillis,
+  Request(XMLHttpRequest xmlHttpRequest, int timeoutMillis,
       final RequestCallback callback) {
     if (xmlHttpRequest == null) {
       throw new NullPointerException();
@@ -157,10 +211,11 @@
      * this in Java by nulling out our reference to the XmlHttpRequest object.
      */
     if (xmlHttpRequest != null) {
-      JavaScriptObject xmlHttp = xmlHttpRequest;
+      XMLHttpRequest xmlHttp = xmlHttpRequest;
       xmlHttpRequest = null;
 
-      XMLHTTPRequest.abort(xmlHttp);
+      xmlHttp.clearOnReadyStateChange();
+      xmlHttp.abort();
 
       cancelTimer();
     }
@@ -176,19 +231,19 @@
       return false;
     }
 
-    int readyState = XMLHTTPRequest.getReadyState(xmlHttpRequest);
+    int readyState = xmlHttpRequest.getReadyState();
 
     /*
      * Because we are doing asynchronous requests it is possible that we can
      * call XmlHttpRequest.send and still have the XmlHttpRequest.getReadyState
      * method return the state as XmlHttpRequest.OPEN. That is why we include
-     * open although it is not *technically* true since open implies that the
+     * open although it is nottechnically true since open implies that the
      * request has not been sent.
      */
     switch (readyState) {
-      case XMLHTTPRequest.OPEN:
-      case XMLHTTPRequest.SENT:
-      case XMLHTTPRequest.RECEIVING:
+      case XMLHttpRequest.OPENED:
+      case XMLHttpRequest.HEADERS_RECEIVED:
+      case XMLHttpRequest.LOADING:
         return true;
     }
 
@@ -196,6 +251,19 @@
   }
 
   /*
+   * Method called when the JavaScript XmlHttpRequest object's readyState
+   * reaches 4 (LOADED).
+   */
+  void fireOnResponseReceived(RequestCallback callback) {
+    UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler();
+    if (handler != null) {
+      fireOnResponseReceivedAndCatch(handler, callback);
+    } else {
+      fireOnResponseReceivedImpl(callback);
+    }
+  }
+
+  /*
    * Stops the current HTTPRequest timer if there is one.
    */
   private void cancelTimer() {
@@ -204,22 +272,6 @@
     }
   }
 
-  /*
-   * Method called when the JavaScript XmlHttpRequest object's readyState
-   * reaches 4 (LOADED).
-   * 
-   * NOTE: this method is called from JSNI
-   */
-  @SuppressWarnings("unused")
-  private void fireOnResponseReceived(RequestCallback callback) {
-    UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler();
-    if (handler != null) {
-      fireOnResponseReceivedAndCatch(handler, callback);
-    } else {
-      fireOnResponseReceivedImpl(callback);
-    }
-  }
-
   private void fireOnResponseReceivedAndCatch(UncaughtExceptionHandler handler,
       RequestCallback callback) {
     try {
@@ -242,15 +294,15 @@
      * JavaScript XmlHttpRequest object so we manually null out our reference to
      * the JavaScriptObject
      */
-    final JavaScriptObject xmlHttp = xmlHttpRequest;
+    final XMLHttpRequest xhr = xmlHttpRequest;
     xmlHttpRequest = null;
 
-    String errorMsg = XMLHTTPRequest.getBrowserSpecificFailure(xmlHttp);
+    String errorMsg = getBrowserSpecificFailure(xhr);
     if (errorMsg != null) {
       Throwable exception = new RuntimeException(errorMsg);
       callback.onError(this, exception);
     } else {
-      Response response = createResponse(xmlHttp);
+      Response response = createResponse(xhr);
       callback.onResponseReceived(this, response);
     }
   }
@@ -270,4 +322,41 @@
 
     callback.onError(this, new RequestTimeoutException(this, timeoutMillis));
   }
+
+  /**
+   * Tests if the JavaScript <code>XmlHttpRequest.status</code> property is
+   * readable. This can return failure in two different known scenarios:
+   * 
+   * <ol>
+   * <li>On Mozilla, after a network error, attempting to read the status code
+   * results in an exception being thrown. See <a
+   * href="https://bugzilla.mozilla.org/show_bug.cgi?id=238559">https://bugzilla.mozilla.org/show_bug.cgi?id=238559</a>.
+   * </li>
+   * <li>On Safari, if the HTTP response does not include any response text.
+   * See <a
+   * href="http://bugs.webkit.org/show_bug.cgi?id=3810">http://bugs.webkit.org/show_bug.cgi?id=3810</a>.
+   * </li>
+   * </ol>
+   * 
+   * @param xhr the JavaScript <code>XmlHttpRequest</code> object
+   *          to test
+   * @return a String message containing an error message if the
+   *         <code>XmlHttpRequest.status</code> code is unreadable or null if
+   *         the status code could be successfully read.
+   */
+  private native String getBrowserSpecificFailure(
+      XMLHttpRequest xhr) /*-{
+    try {
+      if (xhr.status === undefined) {
+        return "XmlHttpRequest.status == undefined, please see Safari bug " +
+               "http://bugs.webkit.org/show_bug.cgi?id=3810 for more details";
+      }
+      return null;
+    } catch (e) {
+      return "Unable to read XmlHttpRequest.status; likely causes are a " +
+             "networking error or bad cross-domain request. Please see " +
+             "https://bugzilla.mozilla.org/show_bug.cgi?id=238559 for more " +
+             "details";
+    }
+  }-*/;
 }
diff --git a/user/src/com/google/gwt/http/client/RequestBuilder.java b/user/src/com/google/gwt/http/client/RequestBuilder.java
index 91ab448..38a9fb6 100644
--- a/user/src/com/google/gwt/http/client/RequestBuilder.java
+++ b/user/src/com/google/gwt/http/client/RequestBuilder.java
@@ -15,9 +15,9 @@
  */
 package com.google.gwt.http.client;
 
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.user.client.impl.HTTPRequestImpl;
+import com.google.gwt.core.client.JavaScriptException;
+import com.google.gwt.xhr.client.ReadyStateChangeHandler;
+import com.google.gwt.xhr.client.XMLHttpRequest;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -36,11 +36,11 @@
  * http://bugs.webkit.org/show_bug.cgi?id=3812</a> for more details.
  * </p>
  * 
- * <h3>Required Module</h3>
- * Modules that use this class should inherit
+ * <h3>Required Module</h3> Modules that use this class should inherit
  * <code>com.google.gwt.http.HTTP</code>.
  * 
- * {@gwt.include com/google/gwt/examples/http/InheritsExample.gwt.xml}
+ * {@gwt.include
+ * com/google/gwt/examples/http/InheritsExample.gwt.xml}
  * 
  */
 public class RequestBuilder {
@@ -70,8 +70,6 @@
    */
   public static final Method POST = new Method("POST");
 
-  private static final HTTPRequestImpl httpRequest = (HTTPRequestImpl) GWT.create(HTTPRequestImpl.class);
-
   /**
    * The callback to call when the request completes.
    */
@@ -139,11 +137,12 @@
    * @throws IllegalArgumentException if the httpMethod or URL are empty
    * @throws NullPointerException if the httpMethod or the URL are null
    * 
-   * <p>
-   * <b>WARNING:</b>This method is provided in order to allow the creation of
-   * HTTP request other than GET and POST to be made. If this is done, the
-   * developer must accept that the behavior on Safari is undefined.
-   * </p>
+   *           <p>
+   *           <b>WARNING:</b>This method is provided in order to allow the
+   *           creation of HTTP request other than GET and POST to be made. If
+   *           this is done, the developer must accept that the behavior on
+   *           Safari is undefined.
+   *           </p>
    */
   protected RequestBuilder(String httpMethod, String url) {
 
@@ -156,8 +155,8 @@
 
   /**
    * Returns the callback previously set by
-   * {@link #setCallback(RequestCallback)}, or <code>null</code> if no
-   * callback was set.
+   * {@link #setCallback(RequestCallback)}, or <code>null</code> if no callback
+   * was set.
    */
   public RequestCallback getCallback() {
     return callback;
@@ -165,8 +164,8 @@
 
   /**
    * Returns the value of a header previous set by
-   * {@link #setHeader(String, String)}, or <code>null</code> if no such
-   * header was set.
+   * {@link #setHeader(String, String)}, or <code>null</code> if no such header
+   * was set.
    * 
    * @param header the name of the header
    */
@@ -265,8 +264,7 @@
    * @param callback the response handler to be notified when the request fails
    *          or completes
    * 
-   * @throws NullPointerException if <code>callback</code> is
-   *           <code>null</code>
+   * @throws NullPointerException if <code>callback</code> is <code>null</code>
    */
   public void setCallback(RequestCallback callback) {
     StringValidator.throwIfNull("callback", callback);
@@ -367,34 +365,43 @@
    * @throws NullPointerException if request data has not been set
    * @throws NullPointerException if a request callback has not been set
    */
-  private Request doSend(String requestData, RequestCallback callback)
+  private Request doSend(String requestData, final RequestCallback callback)
       throws RequestException {
-    JavaScriptObject xmlHttpRequest = httpRequest.createXmlHTTPRequest();
-    String openError;
-    if (user != null && password != null) {
-      openError = XMLHTTPRequest.open(xmlHttpRequest, httpMethod, url, true,
-          user, password);
-    } else if (user != null) {
-      openError = XMLHTTPRequest.open(xmlHttpRequest, httpMethod, url, true,
-          user);
-    } else {
-      openError = XMLHTTPRequest.open(xmlHttpRequest, httpMethod, url, true);
-    }
-    if (openError != null) {
+    XMLHttpRequest xmlHttpRequest = XMLHttpRequest.create();
+
+    try {
+      if (user != null && password != null) {
+        xmlHttpRequest.open(httpMethod, url, user, password);
+      } else if (user != null) {
+        xmlHttpRequest.open(httpMethod, url, user);
+      } else {
+        xmlHttpRequest.open(httpMethod, url);
+      }
+    } catch (JavaScriptException e) {
       RequestPermissionException requestPermissionException = new RequestPermissionException(
           url);
-      requestPermissionException.initCause(new RequestException(openError));
+      requestPermissionException.initCause(new RequestException(e.getMessage()));
       throw requestPermissionException;
     }
 
     setHeaders(xmlHttpRequest);
 
-    Request request = new Request(xmlHttpRequest, timeoutMillis, callback);
+    final Request request = new Request(xmlHttpRequest, timeoutMillis, callback);
 
-    String sendError = XMLHTTPRequest.send(xmlHttpRequest, request,
-        requestData, callback);
-    if (sendError != null) {
-      throw new RequestException(sendError);
+    // Must set the onreadystatechange handler before calling send().
+    xmlHttpRequest.setOnReadyStateChange(new ReadyStateChangeHandler() {
+      public void onReadyStateChange(XMLHttpRequest xhr) {
+        if (xhr.getReadyState() == XMLHttpRequest.DONE) {
+          xhr.clearOnReadyStateChange();
+          request.fireOnResponseReceived(callback);
+        }
+      }
+    });
+
+    try {
+      xmlHttpRequest.send(requestData);
+    } catch (JavaScriptException e) {
+      throw new RequestException(e.getMessage());
     }
 
     return request;
@@ -406,18 +413,18 @@
    * the "Content-Type" to "text/plain; charset=utf-8". This is really lining us
    * up for integration with RPC.
    */
-  private void setHeaders(JavaScriptObject xmlHttpRequest)
+  private void setHeaders(XMLHttpRequest xmlHttpRequest)
       throws RequestException {
     if (headers != null && headers.size() > 0) {
       for (Map.Entry<String, String> header : headers.entrySet()) {
-        String errorMessage = XMLHTTPRequest.setRequestHeader(xmlHttpRequest,
-            header.getKey(), header.getValue());
-        if (errorMessage != null) {
-          throw new RequestException(errorMessage);
+        try {
+          xmlHttpRequest.setRequestHeader(header.getKey(), header.getValue());
+        } catch (JavaScriptException e) {
+          throw new RequestException(e.getMessage());
         }
       }
     } else {
-      XMLHTTPRequest.setRequestHeader(xmlHttpRequest, "Content-Type",
+      xmlHttpRequest.setRequestHeader("Content-Type",
           "text/plain; charset=utf-8");
     }
   }
diff --git a/user/src/com/google/gwt/http/client/RequestCallback.java b/user/src/com/google/gwt/http/client/RequestCallback.java
index 14e79e2..7a01893 100644
--- a/user/src/com/google/gwt/http/client/RequestCallback.java
+++ b/user/src/com/google/gwt/http/client/RequestCallback.java
@@ -26,6 +26,7 @@
  * {@gwt.include com/google/gwt/examples/http/InheritsExample.gwt.xml}
  */
 public interface RequestCallback {
+
   /**
    * Called when a pending {@link com.google.gwt.http.client.Request} completes
    * normally.  Note this method is called even when the status code of the 
diff --git a/user/src/com/google/gwt/http/client/XMLHTTPRequest.java b/user/src/com/google/gwt/http/client/XMLHTTPRequest.java
deleted file mode 100644
index 32da515..0000000
--- a/user/src/com/google/gwt/http/client/XMLHTTPRequest.java
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * 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.http.client;
-
-import com.google.gwt.core.client.JavaScriptObject;
-
-/**
- * Utility class that serves as the one place where we interact with the
- * JavaScript <code>XmlHttpRequest</code> object.
- */
-final class XMLHTTPRequest {
-
-  /*
-   * NOTE: Testing discovered that for some bizarre reason, on Mozilla, the
-   * JavaScript <code>XmlHttpRequest.onreadystatechange</code> handler
-   * function maybe still be called after it is deleted. The theory is that the
-   * callback is cached somewhere. Setting it to null or an empty function does
-   * seem to work properly, though.
-   * 
-   * On IE, there are two problems: Setting onreadystatechange to null (as
-   * opposed to an empty function) sometimes throws an exception. With
-   * particular (rare) versions of jscript.dll, setting onreadystatechange from
-   * within onreadystatechange causes a crash. Setting it from within a timeout
-   * fixes this bug (see issue 1610).
-   * 
-   * End result: *always* set onreadystatechange to an empty function (never to
-   * null). Never set onreadystatechange from within onreadystatechange (always
-   * in a setTimeout()).
-   */
-
-  public static final int UNITIALIZED = 0;
-  public static final int OPEN = 1;
-  public static final int SENT = 2;
-  public static final int RECEIVING = 3;
-  public static final int LOADED = 4;
-
-  static native void abort(JavaScriptObject xmlHttpRequest) /*-{
-    xmlHttpRequest.onreadystatechange = @com.google.gwt.user.client.impl.HTTPRequestImpl::nullFunc;
-    xmlHttpRequest.abort();
-  }-*/;
-
-  static native String getAllResponseHeaders(JavaScriptObject xmlHttpRequest) /*-{
-    return xmlHttpRequest.getAllResponseHeaders();
-  }-*/;
-
-  /**
-   * Tests if the JavaScript <code>XmlHttpRequest.status</code> property is
-   * readable. This can return failure in two different known scenarios:
-   * 
-   * <ol>
-   * <li>On Mozilla, after a network error, attempting to read the status code
-   * results in an exception being thrown. See <a
-   * href="https://bugzilla.mozilla.org/show_bug.cgi?id=238559">https://bugzilla.mozilla.org/show_bug.cgi?id=238559</a>.
-   * </li>
-   * <li>On Safari, if the HTTP response does not include any response text.
-   * See <a
-   * href="http://bugs.webkit.org/show_bug.cgi?id=3810">http://bugs.webkit.org/show_bug.cgi?id=3810</a>.
-   * </li>
-   * </ol>
-   * 
-   * @param xmlHttpRequest the JavaScript <code>XmlHttpRequest</code> object
-   *          to test
-   * @return a String message containing an error message if the
-   *         <code>XmlHttpRequest.status</code> code is unreadable or null if
-   *         the status code could be successfully read.
-   */
-  static native String getBrowserSpecificFailure(
-      JavaScriptObject xmlHttpRequest) /*-{
-    try {
-      if (xmlHttpRequest.status === undefined) {
-        return "XmlHttpRequest.status == undefined, please see Safari bug " +
-               "http://bugs.webkit.org/show_bug.cgi?id=3810 for more details";
-      }
-      return null;
-    } catch (e) {
-      return "Unable to read XmlHttpRequest.status; likely causes are a " +
-             "networking error or bad cross-domain request. Please see " +
-             "https://bugzilla.mozilla.org/show_bug.cgi?id=238559 for more " +
-             "details";
-    }
-  }-*/;
-
-  /**
-   * Returns an array of headers built by parsing the string of headers returned
-   * by the JavaScript <code>XmlHttpRequest</code> object.
-   * 
-   * @param xmlHttpRequest
-   * @return array of Header items
-   */
-  static Header[] getHeaders(JavaScriptObject xmlHttpRequest) {
-    String allHeaders = getAllResponseHeaders(xmlHttpRequest);
-    String[] unparsedHeaders = allHeaders.split("\n");
-    Header[] parsedHeaders = new Header[unparsedHeaders.length];
-
-    for (int i = 0, n = unparsedHeaders.length; i < n; ++i) {
-      String unparsedHeader = unparsedHeaders[i];
-
-      if (unparsedHeader.length() == 0) {
-        continue;
-      }
-
-      int endOfNameIdx = unparsedHeader.indexOf(':');
-      if (endOfNameIdx < 0) {
-        continue;
-      }
-
-      final String name = unparsedHeader.substring(0, endOfNameIdx).trim();
-      final String value = unparsedHeader.substring(endOfNameIdx + 1).trim();
-      Header header = new Header() {
-        @Override
-        public String getName() {
-          return name;
-        }
-
-        @Override
-        public String getValue() {
-          return value;
-        }
-
-        @Override
-        public String toString() {
-          return name + " : " + value;
-        }
-      };
-
-      parsedHeaders[i] = header;
-    }
-
-    return parsedHeaders;
-  }
-
-  static native int getReadyState(JavaScriptObject xmlHttpRequest) /*-{
-    return xmlHttpRequest.readyState;
-  }-*/;
-
-  static native String getResponseHeader(JavaScriptObject xmlHttpRequest,
-      String header) /*-{
-    try {
-      return xmlHttpRequest.getResponseHeader(header);
-    } catch (e) {
-      // purposely ignored
-    }
-    return null;
-  }-*/;
-
-  static native String getResponseText(JavaScriptObject xmlHttpRequest) /*-{
-    return xmlHttpRequest.responseText;
-  }-*/;
-
-  static native int getStatusCode(JavaScriptObject xmlHttpRequest) /*-{
-    return xmlHttpRequest.status;
-  }-*/;
-
-  static native String getStatusText(JavaScriptObject xmlHttpRequest) /*-{
-    return xmlHttpRequest.statusText;
-  }-*/;
-
-  static boolean isResponseReady(JavaScriptObject xmlHttpRequest) {
-    return getReadyState(xmlHttpRequest) == LOADED;
-  }
-
-  /**
-   * Opens the request and catches any exceptions thrown. If an exception is
-   * caught, its string representation will be returned. This is the only signal
-   * that an error has occurred.
-   * 
-   * @param xmlHttpRequest JavaScript <code>XmlHttpRequest</code> object  
-   * @param httpMethod the method to use for open call
-   * @param url the URL to use for the open call
-   * @param async true if we should do an asynchronous open 
-   * @return error message if an exception is thrown or null if there is none 
-   */
-  static native String open(JavaScriptObject xmlHttpRequest, String httpMethod,
-      String url, boolean async) /*-{
-    try {
-      xmlHttpRequest.open(httpMethod, url, async);
-      return null;
-    } catch (e) {
-      return e.message || e.toString();
-    }
-  }-*/;
-
-  /**
-   * Opens the request and catches any exceptions thrown. If an exception is
-   * caught, its string representation will be returned. This is the only signal
-   * that an error has occurred.
-   * 
-   * @param xmlHttpRequest JavaScript <code>XmlHttpRequest</code> object  
-   * @param httpMethod the method to use for open call
-   * @param url the URL to use for the open call
-   * @param async true if we should do an asynchronous open 
-   * @param user user to use in the URL
-   * @return error message if an exception is thrown or null if there is none 
-   */
-  static native String open(JavaScriptObject xmlHttpRequest, String httpMethod,
-      String url, boolean async, String user) /*-{
-    try {
-      xmlHttpRequest.open(httpMethod, url, async, user);
-      return null;
-    } catch (e) {
-      return e.message || e.toString();
-    }
-  }-*/;
-
-  /**
-   * Opens the request and catches any exceptions thrown. If an exception is
-   * caught, its string representation will be returned. This is the only signal
-   * that an error has occurred.
-   * 
-   * @param xmlHttpRequest JavaScript <code>XmlHttpRequest</code> object  
-   * @param httpMethod the method to use for open call
-   * @param url the URL to use for the open call
-   * @param async true if we should do an asynchronous open 
-   * @param user user to use in the URL
-   * @param password password to use in the URL
-   * @return error message if an exception is thrown or null if there is none 
-   */
-  static native String open(JavaScriptObject xmlHttpRequest, String httpMethod,
-      String url, boolean async, String user, String password) /*-{
-    try {
-      xmlHttpRequest.open(httpMethod, url, async, user, password);
-      return null;
-    } catch (e) {
-      return e.message || e.toString();
-    }
-  }-*/;
-
-  /*
-   * Creates a closure that calls the HTTPRequest::fireOnResponseRecieved method
-   * when the server's response is received and sends the data.
-   */
-  static native String send(JavaScriptObject xmlHttpRequest, Request httpRequest,
-      String requestData, RequestCallback callback) /*-{
-    xmlHttpRequest.onreadystatechange = function() {
-      if (xmlHttpRequest.readyState == @com.google.gwt.http.client.XMLHTTPRequest::LOADED) {
-        $wnd.setTimeout(function() {
-          xmlHttpRequest.onreadystatechange = @com.google.gwt.user.client.impl.HTTPRequestImpl::nullFunc;
-        }, 0);
-        httpRequest.@com.google.gwt.http.client.Request::fireOnResponseReceived(Lcom/google/gwt/http/client/RequestCallback;)(callback);
-      }
-    };
-    try {
-      xmlHttpRequest.send(requestData);
-      return null;
-    } catch (e) {
-      xmlHttpRequest.onreadystatechange = @com.google.gwt.user.client.impl.HTTPRequestImpl::nullFunc;
-      return e.message || e.toString();
-    }
-  }-*/;
-
-  static native String setRequestHeader(JavaScriptObject xmlHttpRequest,
-      String header, String value) /*-{
-    try {
-      xmlHttpRequest.setRequestHeader(header, value);
-      return null;
-    } catch (e) {
-      return e.message || e.toString();
-    }
-  }-*/;
-  
-  private XMLHTTPRequest() {
-  }
-}
diff --git a/user/src/com/google/gwt/user/HTTPRequest.gwt.xml b/user/src/com/google/gwt/user/HTTPRequest.gwt.xml
index 3401614..f0f5133 100644
--- a/user/src/com/google/gwt/user/HTTPRequest.gwt.xml
+++ b/user/src/com/google/gwt/user/HTTPRequest.gwt.xml
@@ -18,16 +18,5 @@
 <!--                                                                        -->
 <module>
 	<inherits name="com.google.gwt.core.Core"/>
-	<inherits name="com.google.gwt.user.UserAgent"/>
-
-	<!-- Fall through to this rule is the browser isn't IE -->
-	<replace-with class="com.google.gwt.user.client.impl.HTTPRequestImpl">
-		<when-type-is class="com.google.gwt.user.client.impl.HTTPRequestImpl"/>
-	</replace-with>
-
-	<!-- IE differs slightly in how XmlHttpRequest gets instantiated -->
-	<replace-with class="com.google.gwt.user.client.impl.HTTPRequestImplIE6">
-		<when-type-is class="com.google.gwt.user.client.impl.HTTPRequestImpl"/>
-		<when-property-is name="user.agent" value="ie6"/>
-	</replace-with>
+	<inherits name="com.google.gwt.xhr.XMLHttpRequest"/>
 </module>
diff --git a/user/src/com/google/gwt/user/client/HTTPRequest.java b/user/src/com/google/gwt/user/client/HTTPRequest.java
index 0ff0e44..50acb62 100644
--- a/user/src/com/google/gwt/user/client/HTTPRequest.java
+++ b/user/src/com/google/gwt/user/client/HTTPRequest.java
@@ -15,9 +15,6 @@
  */
 package com.google.gwt.user.client;
 
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.user.client.impl.HTTPRequestImpl;
-
 /**
  * This class allows you to make asynchronous HTTP requests to the originating
  * server.
@@ -28,8 +25,6 @@
 @Deprecated
 public class HTTPRequest {
 
-  private static final HTTPRequestImpl httpRequest = GWT.create(HTTPRequestImpl.class);
-
   /**
    * Makes an asynchronous HTTP GET to a remote server.
    * 
@@ -39,7 +34,7 @@
    * @return <code>false</code> if the invocation fails to issue
    */
   public static boolean asyncGet(String url, ResponseTextHandler handler) {
-    return httpRequest.asyncGet(url, handler);
+    return asyncGetImpl(null, null, url, handler);
   }
 
   /**
@@ -52,7 +47,7 @@
    */
   public static boolean asyncGet(String user, String pwd, String url,
       ResponseTextHandler handler) {
-    return httpRequest.asyncGet(user, pwd, url, handler);
+    return asyncGetImpl(user, pwd, url, handler);
   }
 
   /**
@@ -66,7 +61,7 @@
    */
   public static boolean asyncPost(String url, String postData,
       ResponseTextHandler handler) {
-    return httpRequest.asyncPost(url, postData, handler);
+    return asyncPostImpl(null, null, url, postData, handler);
   }
 
   /**
@@ -80,6 +75,51 @@
    */
   public static boolean asyncPost(String user, String pwd, String url,
       String postData, ResponseTextHandler handler) {
-    return httpRequest.asyncPost(user, pwd, url, postData, handler);
+    return asyncPostImpl(user, pwd, url, postData, handler);
   }
+
+  private static native boolean asyncGetImpl(String user, String pwd, String url,
+      ResponseTextHandler handler) /*-{
+    var xmlHttp = @com.google.gwt.xhr.client.XMLHttpRequest::create()();
+    try {
+      xmlHttp.open("GET", url, true);
+      xmlHttp.setRequestHeader("Content-Type", "text/plain; charset=utf-8");
+      xmlHttp.onreadystatechange = function() {
+        if (xmlHttp.readyState == 4) {
+          $wnd.setTimeout(function() {
+            xmlHttp.onreadystatechange = @com.google.gwt.user.client.impl.HTTPRequestImpl::nullFunc;
+          }, 0);
+          handler.@com.google.gwt.user.client.ResponseTextHandler::onCompletion(Ljava/lang/String;)(xmlHttp.responseText || "");
+        }
+      };
+      xmlHttp.send('');
+      return true;
+    } catch (e) {
+      xmlHttp.onreadystatechange = @com.google.gwt.user.client.impl.HTTPRequestImpl::nullFunc;
+      return false;
+    }
+  }-*/;
+
+  private static native boolean asyncPostImpl(String user, String pwd, String url,
+      String postData, ResponseTextHandler handler) /*-{
+    var xmlHttp = @com.google.gwt.xhr.client.XMLHttpRequest::create()();
+    try {
+      xmlHttp.open("POST", url, true);
+      xmlHttp.setRequestHeader("Content-Type", "text/plain; charset=utf-8");
+      xmlHttp.onreadystatechange = function() {
+        if (xmlHttp.readyState == 4) {
+          $wnd.setTimeout(function() {
+            xmlHttp.onreadystatechange = @com.google.gwt.user.client.impl.HTTPRequestImpl::nullFunc;
+          }, 0);
+          handler.@com.google.gwt.user.client.ResponseTextHandler::onCompletion(Ljava/lang/String;)(xmlHttp.responseText || "");
+        }
+      };
+      xmlHttp.send(postData);
+      return true;
+    }
+    catch (e) {
+      xmlHttp.onreadystatechange = @com.google.gwt.user.client.impl.HTTPRequestImpl::nullFunc;
+      return false;
+    }
+  }-*/;
 }
diff --git a/user/src/com/google/gwt/user/client/impl/HTTPRequestImplIE6.java b/user/src/com/google/gwt/user/client/impl/HTTPRequestImplIE6.java
deleted file mode 100644
index 1bea53c..0000000
--- a/user/src/com/google/gwt/user/client/impl/HTTPRequestImplIE6.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.client.impl;
-
-import com.google.gwt.core.client.JavaScriptObject;
-
-/**
- * Internet Explorer 6 implementation of {@link HTTPRequestImpl}.
- */
-class HTTPRequestImplIE6 extends HTTPRequestImpl {
-
-  @Override
-  protected native JavaScriptObject doCreateXmlHTTPRequest() /*-{
-    if ($wnd.XMLHttpRequest) {
-      return new XMLHttpRequest();
-    } else {
-      try {
-        return new ActiveXObject('MSXML2.XMLHTTP.3.0');
-      } catch (e) {
-        return new ActiveXObject("Microsoft.XMLHTTP");
-      }
-    }
-  }-*/;
-}
diff --git a/user/src/com/google/gwt/xhr/XMLHttpRequest.gwt.xml b/user/src/com/google/gwt/xhr/XMLHttpRequest.gwt.xml
new file mode 100644
index 0000000..214dc42
--- /dev/null
+++ b/user/src/com/google/gwt/xhr/XMLHttpRequest.gwt.xml
@@ -0,0 +1,22 @@
+<!--                                                                        -->
+<!-- Copyright 2009 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<!-- Types and resources required to support HTTP requests.                 -->
+<!--                                                                        -->
+<!-- Any user application that wishes to use the types declared in the http -->
+<!-- package must inherit this module.                                      -->
+<!--                                                                        -->
+<module>
+	<inherits name="com.google.gwt.core.Core"/>
+</module>
diff --git a/user/src/com/google/gwt/xhr/client/ReadyStateChangeHandler.java b/user/src/com/google/gwt/xhr/client/ReadyStateChangeHandler.java
new file mode 100644
index 0000000..feec633
--- /dev/null
+++ b/user/src/com/google/gwt/xhr/client/ReadyStateChangeHandler.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2009 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.xhr.client;
+
+/**
+ * A ready-state callback for an {@Link XMLHttpRequest} object.
+ */
+public interface ReadyStateChangeHandler {
+
+  /**
+   * This is called whenever the state of the XMLHttpRequest changes. See
+   * {@link XMLHttpRequest#setOnReadyStateHandler}.
+   * 
+   * @param xhr the object whose state has changed.
+   */
+  void onReadyStateChange(XMLHttpRequest xhr);
+}
diff --git a/user/src/com/google/gwt/xhr/client/XMLHttpRequest.java b/user/src/com/google/gwt/xhr/client/XMLHttpRequest.java
new file mode 100644
index 0000000..868a429
--- /dev/null
+++ b/user/src/com/google/gwt/xhr/client/XMLHttpRequest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2009 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.xhr.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.http.client.RequestBuilder;
+
+/**
+ * The native XMLHttpRequest object. Most applications should use the higher-
+ * level {@link RequestBuilder} class unless they need specific functionality
+ * provided by the XMLHttpRequest object.
+ * 
+ * @see http://www.w3.org/TR/XMLHttpRequest/
+ */
+public class XMLHttpRequest extends JavaScriptObject {
+
+  /*
+   * NOTE: Testing discovered that for some bizarre reason, on Mozilla, the
+   * JavaScript <code>XmlHttpRequest.onreadystatechange</code> handler
+   * function maybe still be called after it is deleted. The theory is that the
+   * callback is cached somewhere. Setting it to null or an empty function does
+   * seem to work properly, though.
+   * 
+   * On IE, there are two problems: Setting onreadystatechange to null (as
+   * opposed to an empty function) sometimes throws an exception. With
+   * particular (rare) versions of jscript.dll, setting onreadystatechange from
+   * within onreadystatechange causes a crash. Setting it from within a timeout
+   * fixes this bug (see issue 1610).
+   * 
+   * End result: *always* set onreadystatechange to an empty function (never to
+   * null). Never set onreadystatechange from within onreadystatechange (always
+   * in a setTimeout()).
+   */
+
+  /**
+   * When constructed, the XMLHttpRequest object must be in the UNSENT state.
+   */
+  public static final int UNSENT = 0;
+
+  /**
+   * The OPENED state is the state of the object when the open() method has been
+   * successfully invoked. During this state request headers can be set using
+   * setRequestHeader() and the request can be made using send().
+   */
+  public static final int OPENED = 1;
+
+  /**
+   * The HEADERS_RECEIVED state is the state of the object when all response
+   * headers have been received.
+   */
+  public static final int HEADERS_RECEIVED = 2;
+
+  /**
+   * The LOADING state is the state of the object when the response entity body
+   * is being received.
+   */
+  public static final int LOADING = 3;
+
+  /**
+   * The DONE state is the state of the object when either the data transfer has
+   * been completed or something went wrong during the transfer (infinite
+   * redirects for instance).
+   */
+  public static final int DONE = 4;
+
+  /**
+   * Creates an XMLHttpRequest object.
+   * 
+   * @return the created object
+   */
+  public static native XMLHttpRequest create() /*-{
+    if ($wnd.XMLHttpRequest) {
+      return new XMLHttpRequest();
+    } else {
+      try {
+        return new ActiveXObject('MSXML2.XMLHTTP.3.0');
+      } catch (e) {
+        return new ActiveXObject("Microsoft.XMLHTTP");
+      }
+    }
+  }-*/;
+
+  protected XMLHttpRequest() {
+  }
+
+  /**
+   * Aborts the current request.
+   * 
+   * @see http://www.w3.org/TR/XMLHttpRequest/#abort
+   */
+  public final native void abort() /*-{
+    this.abort();
+  }-*/;
+
+  /**
+   * Clears the {@link ReadyStateChangeHandler}.
+   * 
+   * @see #clearOnReadyStateChange()
+   * @see http://www.w3.org/TR/XMLHttpRequest/#onreadystatechange
+   */
+  public final native void clearOnReadyStateChange() /*-{
+    $wnd.setTimeout(function() {
+      this.onreadystatechange = function(){};
+    }, 0);
+  }-*/;
+
+  /**
+   * Gets all the HTTP response headers, as a single string.
+   * 
+   * @return the response headers.
+   * @see http://www.w3.org/TR/XMLHttpRequest/#getallresponseheaders
+   */
+  public final native String getAllResponseHeaders() /*-{
+    return this.getAllResponseHeaders();
+  }-*/;
+
+  /**
+   * Get's the current ready-state.
+   * 
+   * @return the ready-state constant
+   * @see http://www.w3.org/TR/XMLHttpRequest/#readystate
+   */
+  public final native int getReadyState() /*-{
+    return this.readyState;
+  }-*/;
+
+  /**
+   * Gets an HTTP response header.
+   * 
+   * @param header the response header to be retrieved
+   * @return the header value
+   * @see http://www.w3.org/TR/XMLHttpRequest/#getresponseheader
+   */
+  public final native String getResponseHeader(String header) /*-{
+    return this.getResponseHeader(header);
+  }-*/;
+
+  /**
+   * Gets the response text.
+   * 
+   * @return the response text
+   * @see http://www.w3.org/TR/XMLHttpRequest/#responsetext
+   */
+  public final native String getResponseText() /*-{
+    return this.responseText;
+  }-*/;
+
+  /**
+   * Gets the status code.
+   * 
+   * @return the status code
+   * @see http://www.w3.org/TR/XMLHttpRequest/#status
+   */
+  public final native int getStatus() /*-{
+    return this.status;
+  }-*/;
+
+  /**
+   * Gets the status text.
+   * 
+   * @return the status text
+   * @see http://www.w3.org/TR/XMLHttpRequest/#statustext
+   */
+  public final native String getStatusText() /*-{
+    return this.statusText;
+  }-*/;
+
+  /**
+   * Opens an asynchronous connection.
+   * 
+   * @param httpMethod the HTTP method to use
+   * @param url the URL to be opened
+   * @see http://www.w3.org/TR/XMLHttpRequest/#open
+   */
+  public final native void open(String httpMethod, String url) /*-{
+    this.open(httpMethod, url, true);
+  }-*/;
+
+  /**
+   * Opens an asynchronous connection.
+   * 
+   * @param httpMethod the HTTP method to use
+   * @param url the URL to be opened
+   * @param user user to use in the URL
+   * @see http://www.w3.org/TR/XMLHttpRequest/#open
+   */
+  public final native void open(String httpMethod, String url, String user) /*-{
+    this.open(httpMethod, url, true, user);
+  }-*/;
+
+  /**
+   * Opens an asynchronous connection.
+   * 
+   * @param httpMethod the HTTP method to use
+   * @param url the URL to be opened
+   * @param user user to use in the URL
+   * @param password password to use in the URL
+   * @see http://www.w3.org/TR/XMLHttpRequest/#open
+   */
+  public final native void open(String httpMethod, String url, String user,
+      String password) /*-{
+    this.open(httpMethod, url, true, user, password);
+  }-*/;
+
+  /**
+   * Initiates a request.
+   * 
+   * @see http://www.w3.org/TR/XMLHttpRequest/#send
+   */
+  public final native void send() /*-{
+    this.send();
+  }-*/;
+
+  /**
+   * Initiates a request with data.
+   * 
+   * @param requestData the data to be sent with the request
+   * @see http://www.w3.org/TR/XMLHttpRequest/#send
+   */
+  public final native void send(String requestData) /*-{
+    this.send(requestData);
+  }-*/;
+
+  /**
+   * Sets the {@link ReadyStateChangeHandler} to be notified when the object's
+   * ready-state changes.
+   * 
+   * <p>
+   * Note: Applications <em>must</em> call {@link #clearOnReadyStateChange()}
+   * when they no longer need this object, to ensure that it is cleaned up
+   * properly. Failure to do so will result in memory leaks on some browsers.
+   * </p>
+   * 
+   * @param handler the handler to be called when the ready state changes
+   * @see #clearOnReadyStateChange()
+   * @see http://www.w3.org/TR/XMLHttpRequest/#onreadystatechange
+   */
+  public final native void setOnReadyStateChange(ReadyStateChangeHandler handler) /*-{
+    // The 'this' context is always supposed to point to the xhr object in the
+    // onreadystatechange handler, but we reference it via closure to be extra sure.
+    var _this = this;
+    this.onreadystatechange = function() {
+      handler.@com.google.gwt.xhr.client.ReadyStateChangeHandler::onReadyStateChange(Lcom/google/gwt/xhr/client/XMLHttpRequest;)(_this);
+    };
+  }-*/;
+
+  /**
+   * Sets a request header.
+   * 
+   * @param header the header to be set
+   * @param value the header's value
+   * @see http://www.w3.org/TR/XMLHttpRequest/#setrequestheader
+   */
+  public final native void setRequestHeader(String header, String value) /*-{
+    this.setRequestHeader(header, value);
+  }-*/;
+}
diff --git a/user/test/com/google/gwt/http/HTTPSuite.java b/user/test/com/google/gwt/http/HTTPSuite.java
index 379a093..b7659bc 100644
--- a/user/test/com/google/gwt/http/HTTPSuite.java
+++ b/user/test/com/google/gwt/http/HTTPSuite.java
@@ -15,14 +15,15 @@
  */
 package com.google.gwt.http;
 
+import junit.framework.Test;
+
+import com.google.gwt.http.client.HTTPRequestTest;
 import com.google.gwt.http.client.RequestBuilderTest;
 import com.google.gwt.http.client.RequestTest;
 import com.google.gwt.http.client.ResponseTest;
 import com.google.gwt.http.client.URLTest;
 import com.google.gwt.junit.tools.GWTTestSuite;
 
-import junit.framework.Test;
-
 /**
  * TODO: document me.
  */
@@ -31,6 +32,7 @@
     GWTTestSuite suite = new GWTTestSuite(
         "Test for suite for the com.google.gwt.http module");
 
+    suite.addTestSuite(HTTPRequestTest.class);
     suite.addTestSuite(URLTest.class);
     suite.addTestSuite(RequestBuilderTest.class);
     suite.addTestSuite(RequestTest.class);
diff --git a/user/test/com/google/gwt/http/client/HTTPRequestTest.java b/user/test/com/google/gwt/http/client/HTTPRequestTest.java
new file mode 100644
index 0000000..f689406
--- /dev/null
+++ b/user/test/com/google/gwt/http/client/HTTPRequestTest.java
@@ -0,0 +1,42 @@
+package com.google.gwt.http.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.HTTPRequest;
+import com.google.gwt.user.client.ResponseTextHandler;
+
+public class HTTPRequestTest extends GWTTestCase {
+
+  private static final int TEST_TIMEOUT = 10000;
+
+  private static String getTestBaseURL() {
+    return GWT.getModuleBaseURL() + "testRequestBuilder/";
+  }
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.http.RequestBuilderTest";
+  }
+
+  public void testAsyncGet() {
+    delayTestFinish(TEST_TIMEOUT);
+    HTTPRequest.asyncGet(getTestBaseURL() + "send_GET",
+        new ResponseTextHandler() {
+          public void onCompletion(String responseText) {
+            assertEquals(RequestBuilderTest.SERVLET_GET_RESPONSE, responseText);
+            finishTest();
+          }
+        });
+  }
+
+  public void testAsyncPost() {
+    delayTestFinish(TEST_TIMEOUT);
+    HTTPRequest.asyncPost(getTestBaseURL() + "simplePost",
+        "method=test+request", new ResponseTextHandler() {
+          public void onCompletion(String responseText) {
+            assertEquals(RequestBuilderTest.SERVLET_POST_RESPONSE, responseText);
+            finishTest();
+          }
+        });
+  }
+}
diff --git a/user/test/com/google/gwt/http/client/RequestBuilderTest.java b/user/test/com/google/gwt/http/client/RequestBuilderTest.java
index 5a7d84e..5ade8cd 100644
--- a/user/test/com/google/gwt/http/client/RequestBuilderTest.java
+++ b/user/test/com/google/gwt/http/client/RequestBuilderTest.java
@@ -24,6 +24,11 @@
 public class RequestBuilderTest extends GWTTestCase {
   private static final int TEST_FINISH_DELAY = 10000;
 
+  public static final String SERVLET_GET_RESPONSE = "get";
+  public static final String SERVLET_POST_RESPONSE = "post";
+  public static final String SERVLET_HEAD_RESPONSE = "head";
+  public static final String SERVLET_PUT_RESPONSE = "put";
+
   private static String getTestBaseURL() {
     return GWT.getModuleBaseURL() + "testRequestBuilder/";
   }
@@ -155,6 +160,7 @@
       }
 
       public void onResponseReceived(Request request, Response response) {
+        assertEquals(SERVLET_GET_RESPONSE, response.getText());
         assertEquals(200, response.getStatusCode());
         finishTest();
       }
@@ -177,6 +183,7 @@
       }
 
       public void onResponseReceived(Request request, Response response) {
+        assertEquals(SERVLET_POST_RESPONSE, response.getText());
         assertEquals(200, response.getStatusCode());
         finishTest();
       }
@@ -200,6 +207,7 @@
       }
 
       public void onResponseReceived(Request request, Response response) {
+        assertEquals(SERVLET_GET_RESPONSE, response.getText());
         assertEquals(200, response.getStatusCode());
         finishTest();
       }
@@ -222,6 +230,7 @@
       }
 
       public void onResponseReceived(Request request, Response response) {
+        assertEquals(SERVLET_POST_RESPONSE, response.getText());
         assertEquals(200, response.getStatusCode());
         finishTest();
       }
@@ -316,6 +325,7 @@
       }
 
       public void onResponseReceived(Request request, Response response) {
+        assertEquals(SERVLET_GET_RESPONSE, response.getText());
         assertEquals(200, response.getStatusCode());
         finishTest();
       }
@@ -345,6 +355,7 @@
       }
 
       public void onResponseReceived(Request request, Response response) {
+        assertEquals(SERVLET_GET_RESPONSE, response.getText());
         assertEquals(200, response.getStatusCode());
         finishTest();
       }
@@ -374,6 +385,7 @@
       }
 
       public void onResponseReceived(Request request, Response response) {
+        assertEquals(SERVLET_GET_RESPONSE, response.getText());
         assertEquals(200, response.getStatusCode());
         fail("Test did not timeout");
       }
diff --git a/user/test/com/google/gwt/http/client/RequestTest.java b/user/test/com/google/gwt/http/client/RequestTest.java
index b0c52df..8f3121f 100644
--- a/user/test/com/google/gwt/http/client/RequestTest.java
+++ b/user/test/com/google/gwt/http/client/RequestTest.java
@@ -17,7 +17,7 @@
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.junit.client.GWTTestCase;
-import com.google.gwt.user.client.impl.HTTPRequestImpl;
+import com.google.gwt.xhr.client.XMLHttpRequest;
 
 /**
  * TODO: document me.
@@ -76,29 +76,28 @@
     };
 
     try {
-      Request request = new Request(null, 0, callback);
+      new Request(null, 0, callback);
       fail();
     } catch (NullPointerException ex) {
-      // Success.
+      // Success (The Request ctor explicitly throws an NPE).
     }
 
-    HTTPRequestImpl impl = (HTTPRequestImpl) GWT.create(HTTPRequestImpl.class);
     try {
-      Request request = new Request(impl.createXmlHTTPRequest(), -1, callback);
+      new Request(XMLHttpRequest.create(), -1, callback);
       fail();
     } catch (IllegalArgumentException ex) {
       // Success.
     }
 
     try {
-      Request request = new Request(impl.createXmlHTTPRequest(), -1, null);
+      new Request(XMLHttpRequest.create(), -1, null);
       fail();
     } catch (NullPointerException ex) {
-      // Success.
+      // Success (The Request ctor explicitly throws an NPE).
     }
 
     try {
-      Request request = new Request(impl.createXmlHTTPRequest(), 0, callback);
+      new Request(XMLHttpRequest.create(), 0, callback);
     } catch (Throwable ex) {
       fail(ex.getMessage());
     }
diff --git a/user/test/com/google/gwt/http/server/RequestBuilderTestServlet.java b/user/test/com/google/gwt/http/server/RequestBuilderTestServlet.java
index f0efd75..7adda6d 100644
--- a/user/test/com/google/gwt/http/server/RequestBuilderTestServlet.java
+++ b/user/test/com/google/gwt/http/server/RequestBuilderTestServlet.java
@@ -22,6 +22,8 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import com.google.gwt.http.client.RequestBuilderTest;
+
 /**
  * Servlet component of the
  * {@link com.google.gwt.http.client.RequestBuilderTest RequestBuilderTest}.
@@ -41,42 +43,46 @@
   @Override
   protected void doGet(HttpServletRequest request, HttpServletResponse response)
       throws IOException {
-    String method = request.getMethod();
     String pathInfo = request.getPathInfo();
     if (pathInfo.equals(getPathInfoBase() + "setRequestHeader")) {
       String value = request.getHeader("Foo");
-      response.getWriter().print("Hello");
       if (value.equals("Bar1")) {
         response.setStatus(HttpServletResponse.SC_OK);
+        response.getWriter().print(RequestBuilderTest.SERVLET_GET_RESPONSE);
       } else {
         response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
       }
     } else if (pathInfo.equals(getPathInfoBase() + "send_GET")) {
       response.setStatus(HttpServletResponse.SC_OK);
-      response.getWriter().write("<html><body>hello</body></html>");
-      response.setContentType("text/html");
+      response.getWriter().write(RequestBuilderTest.SERVLET_GET_RESPONSE);
     } else if (pathInfo.equals(getPathInfoBase() + "sendRequest_GET")) {
       response.setStatus(HttpServletResponse.SC_OK);
-      response.getWriter().write("<html><body>hello</body></html>");
-      response.setContentType("text/html");
+      response.getWriter().write(RequestBuilderTest.SERVLET_GET_RESPONSE);
     } else if (pathInfo.equals(getPathInfoBase() + "setTimeout/timeout")) {
       // cause a timeout on the client
       try {
         Thread.sleep(5000);
       } catch (InterruptedException e) {
-        e.printStackTrace();
       }
       response.setStatus(HttpServletResponse.SC_OK);
+      response.getWriter().print(RequestBuilderTest.SERVLET_GET_RESPONSE);
     } else if (pathInfo.equals(getPathInfoBase() + "setTimeout/noTimeout")) {
       // wait but not long enough to timeout
       try {
         Thread.sleep(1000);
       } catch (InterruptedException e) {
-        // TODO Auto-generated catch block
-        e.printStackTrace();
       }
-      response.getWriter().print("setTimeout/noTimeout");
       response.setStatus(HttpServletResponse.SC_OK);
+      response.getWriter().print(RequestBuilderTest.SERVLET_GET_RESPONSE);
+    } else if (pathInfo.equals(getPathInfoBase() + "user/pass")) {
+      String auth = request.getHeader("Authorization");
+      if (auth == null) {
+        response.setHeader("WWW-Authenticate", "BASIC");
+        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+      } else {
+        response.setStatus(HttpServletResponse.SC_OK);
+        response.getWriter().print(RequestBuilderTest.SERVLET_GET_RESPONSE);
+      }
     } else {
       response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
     }
@@ -84,27 +90,35 @@
 
   @Override
   protected void doHead(HttpServletRequest request, HttpServletResponse response) {
-    response.setStatus(HttpServletResponse.SC_OK);
+    try {
+      response.setStatus(HttpServletResponse.SC_OK);
+      response.getWriter().print(RequestBuilderTest.SERVLET_HEAD_RESPONSE);
+    } catch (IOException e) {
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+    }
   }
 
   @Override
   protected void doPost(HttpServletRequest request, HttpServletResponse response) {
-    String parameter = request.getParameter("method");
-    if ("test request".equals(parameter)) {
-      try {
+    try {
+      String parameter = request.getParameter("method");
+      if ("test request".equals(parameter)) {
         /*
          * On Safari 2.0.4, if the HTTP response does not include any response
          * text the status message will be undefined. So, we make sure that the
          * post returns some data. See
          * http://bugs.webkit.org/show_bug.cgi?id=3810.
          */
-        response.getWriter().println("Hello");
+        response.getWriter().print(RequestBuilderTest.SERVLET_POST_RESPONSE);
         response.setStatus(HttpServletResponse.SC_OK);
-      } catch (IOException e) {
-        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+      } else if (request.getPathInfo().equals(getPathInfoBase() + "simplePost")) {
+        response.getWriter().print(RequestBuilderTest.SERVLET_POST_RESPONSE);
+        response.setStatus(HttpServletResponse.SC_OK);
+      } else {
+        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
       }
-    } else {
-      response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+    } catch (IOException e) {
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
     }
   }
 
@@ -114,6 +128,7 @@
     BufferedReader reader = request.getReader();
     String content = reader.readLine();
     if (content.equals("<html><body>Put Me</body></html>")) {
+      response.getWriter().print(RequestBuilderTest.SERVLET_PUT_RESPONSE);
       response.setStatus(HttpServletResponse.SC_OK);
     } else {
       response.setStatus(HttpServletResponse.SC_BAD_REQUEST);