This repository has been archived by the owner. It is now read-only.
Permalink
Cannot retrieve contributors at this time
/* | |
* Copyright 2008 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.impl.Impl; | |
import com.google.gwt.core.shared.GWT; | |
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 | |
* <code>com.google.gwt.http.HTTP</code>. | |
* | |
* {@gwt.include | |
* com/google/gwt/examples/http/InheritsExample.gwt.xml} | |
* | |
*/ | |
public class Request { | |
/** | |
* Native implementation associated with {@link Request}. User classes should not use this class | |
* directly. | |
*/ | |
static class RequestImpl { | |
/** | |
* Creates a {@link Response} instance for the given JavaScript XmlHttpRequest object. | |
* | |
* @param xmlHttpRequest xmlHttpRequest object for which we need a response | |
* @return a {@link Response} object instance | |
*/ | |
Response createResponse(final XMLHttpRequest xmlHttpRequest) { | |
return new ResponseImpl(xmlHttpRequest); | |
} | |
} | |
/** | |
* Special {@link RequestImpl} for IE6-9 to work around some IE specialities. | |
*/ | |
static class RequestImplIE6To9 extends RequestImpl { | |
@Override | |
Response createResponse(XMLHttpRequest xmlHttpRequest) { | |
return new ResponseImpl(xmlHttpRequest) { | |
@Override | |
public int getStatusCode() { | |
/* | |
* http://code.google.com/p/google-web-toolkit/issues/detail?id=5031 | |
* | |
* The XMLHTTPRequest object in IE will return a status code of 1223 and drop some | |
* response headers if the server returns a HTTP/204. | |
* | |
* This issue is fixed in IE10. | |
*/ | |
int statusCode = super.getStatusCode(); | |
return (statusCode == 1223) ? SC_NO_CONTENT : statusCode; | |
} | |
}; | |
} | |
} | |
/* | |
* Although Request is a client-side class, it's a transitive dependency of | |
* some GWT servlet code. Because GWT.create() isn't safe to call on the | |
* server, we use the "Initialization On Demand Holder" idiom to lazily | |
* initialize the RequestImpl. | |
*/ | |
private static class ImplHolder { | |
private static final RequestImpl impl = GWT.create(RequestImpl.class); | |
public static RequestImpl get() { | |
return impl; | |
} | |
} | |
/** | |
* Creates a {@link Response} instance for the given JavaScript XmlHttpRequest | |
* object. | |
* | |
* @param xmlHttpRequest xmlHttpRequest object for which we need a response | |
* @return a {@link Response} object instance | |
*/ | |
private static Response createResponse(final XMLHttpRequest xmlHttpRequest) { | |
return ImplHolder.get().createResponse(xmlHttpRequest); | |
} | |
private static native int createTimeout(Request request, RequestCallback callback, int timeoutMillis) /*-{ | |
return @com.google.gwt.core.client.impl.Impl::setTimeout(Lcom/google/gwt/core/client/JavaScriptObject;I)( | |
$entry(function() { | |
request.@com.google.gwt.http.client.Request::fireOnTimeout(Lcom/google/gwt/http/client/RequestCallback;)(callback); | |
}), | |
timeoutMillis); | |
}-*/; | |
/** | |
* The number of milliseconds to wait for this HTTP request to complete. | |
*/ | |
private final int timeoutMillis; | |
/** | |
* ID of the timer used to force HTTPRequest timeouts. Only meaningful if | |
* timeoutMillis > 0. | |
*/ | |
private final int timerId; | |
/** | |
* JavaScript XmlHttpRequest object that this Java class wraps. This field is | |
* not final because we transfer ownership of it to the HTTPResponse object | |
* and set this field to null. | |
*/ | |
private XMLHttpRequest xmlHttpRequest; | |
/** | |
* Only used for building a | |
* {@link com.google.gwt.user.client.rpc.impl.FailedRequest}. | |
*/ | |
protected Request() { | |
timeoutMillis = 0; | |
xmlHttpRequest = null; | |
timerId = 0; | |
} | |
/** | |
* Constructs an instance of the Request object. | |
* | |
* @param xmlHttpRequest JavaScript XmlHttpRequest object instance | |
* @param timeoutMillis number of milliseconds to wait for a response | |
* @param callback callback interface to use for notification | |
* | |
* @throws IllegalArgumentException if timeoutMillis < 0 | |
* @throws NullPointerException if xmlHttpRequest, or callback are null | |
*/ | |
public Request(XMLHttpRequest xmlHttpRequest, int timeoutMillis, | |
final RequestCallback callback) { | |
if (xmlHttpRequest == null) { | |
throw new NullPointerException(); | |
} | |
if (callback == null) { | |
throw new NullPointerException(); | |
} | |
if (timeoutMillis < 0) { | |
throw new IllegalArgumentException(); | |
} | |
this.timeoutMillis = timeoutMillis; | |
this.xmlHttpRequest = xmlHttpRequest; | |
if (timeoutMillis > 0) { | |
// create and schedule a cancel command | |
timerId = createTimeout(this, callback, timeoutMillis); | |
} else { | |
// no Timer required | |
timerId = 0; | |
} | |
} | |
/** | |
* Cancels a pending request. If the request has already been canceled or if | |
* it has timed out no action is taken. | |
*/ | |
public void cancel() { | |
/* | |
* There is a strange race condition that occurs on Mozilla when you cancel | |
* a request while the response is coming in. It appears that in some cases | |
* the onreadystatechange handler is still called after the handler function | |
* has been deleted and during the call to XmlHttpRequest.abort(). So we | |
* null the xmlHttpRequest here and that will prevent the | |
* fireOnResponseReceived method from calling the callback function. | |
* | |
* Setting the onreadystatechange handler to null gives us the correct | |
* behavior in Mozilla but crashes IE. That is why we have chosen to fixed | |
* this in Java by nulling out our reference to the XmlHttpRequest object. | |
*/ | |
if (xmlHttpRequest != null) { | |
XMLHttpRequest xmlHttp = xmlHttpRequest; | |
xmlHttpRequest = null; | |
xmlHttp.clearOnReadyStateChange(); | |
xmlHttp.abort(); | |
cancelTimer(); | |
} | |
} | |
/** | |
* Returns true if this request is waiting for a response. | |
* | |
* @return true if this request is waiting for a response | |
*/ | |
public boolean isPending() { | |
if (xmlHttpRequest == null) { | |
return false; | |
} | |
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 nottechnically true since open implies that the | |
* request has not been sent. | |
*/ | |
switch (readyState) { | |
case XMLHttpRequest.OPENED: | |
case XMLHttpRequest.HEADERS_RECEIVED: | |
case XMLHttpRequest.LOADING: | |
return true; | |
} | |
return false; | |
} | |
/* | |
* Method called when the JavaScript XmlHttpRequest object's readyState | |
* reaches 4 (LOADED). | |
*/ | |
public void fireOnResponseReceived(RequestCallback callback) { | |
if (xmlHttpRequest == null) { | |
// the request has timed out at this point | |
return; | |
} | |
cancelTimer(); | |
/* | |
* We cannot use cancel here because it would clear the contents of the | |
* JavaScript XmlHttpRequest object so we manually null out our reference to | |
* the JavaScriptObject | |
*/ | |
final XMLHttpRequest xhr = xmlHttpRequest; | |
xmlHttpRequest = null; | |
String errorMsg = getBrowserSpecificFailure(xhr); | |
if (errorMsg != null) { | |
Throwable exception = new RuntimeException(errorMsg); | |
callback.onError(this, exception); | |
} else { | |
Response response = createResponse(xhr); | |
callback.onResponseReceived(this, response); | |
} | |
} | |
/* | |
* Stops the current HTTPRequest timer if there is one. | |
*/ | |
private void cancelTimer() { | |
if (timeoutMillis > 0) { | |
Impl.clearTimeout(timerId); | |
} | |
} | |
/* | |
* Method called when this request times out. | |
* | |
* NOTE: this method is called from JSNI | |
*/ | |
private void fireOnTimeout(RequestCallback callback) { | |
if (xmlHttpRequest == null) { | |
// the request has been received at this point | |
return; | |
} | |
cancel(); | |
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"; | |
} | |
}-*/; | |
} |