| /* |
| * 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 |
| */ |
| 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). |
| */ |
| 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"; |
| } |
| }-*/; |
| } |