001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.http.client;
017:
018: import com.google.gwt.core.client.GWT;
019: import com.google.gwt.core.client.JavaScriptObject;
020: import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
021: import com.google.gwt.user.client.Timer;
022:
023: /**
024: * An HTTP request that is waiting for a response. Requests can be queried for
025: * their pending status or they can be canceled.
026: *
027: * <h3>Required Module</h3>
028: * Modules that use this class should inherit
029: * <code>com.google.gwt.http.HTTP</code>.
030: *
031: * {@gwt.include com/google/gwt/examples/http/InheritsExample.gwt.xml}
032: *
033: */
034: public class Request {
035: /**
036: * Creates a {@link Response} instance for the given JavaScript XmlHttpRequest
037: * object.
038: *
039: * @param xmlHttpRequest xmlHttpRequest object for which we need a response
040: * @return a {@link Response} object instance
041: */
042: private static Response createResponse(
043: final JavaScriptObject xmlHttpRequest) {
044: assert (XMLHTTPRequest.isResponseReady(xmlHttpRequest));
045: Response response = new Response() {
046: @Override
047: public String getHeader(String header) {
048: StringValidator.throwIfEmptyOrNull("header", header);
049:
050: return XMLHTTPRequest.getResponseHeader(xmlHttpRequest,
051: header);
052: }
053:
054: @Override
055: public Header[] getHeaders() {
056: return XMLHTTPRequest.getHeaders(xmlHttpRequest);
057: }
058:
059: @Override
060: public String getHeadersAsString() {
061: return XMLHTTPRequest
062: .getAllResponseHeaders(xmlHttpRequest);
063: }
064:
065: @Override
066: public int getStatusCode() {
067: return XMLHTTPRequest.getStatusCode(xmlHttpRequest);
068: }
069:
070: @Override
071: public String getStatusText() {
072: return XMLHTTPRequest.getStatusText(xmlHttpRequest);
073: }
074:
075: @Override
076: public String getText() {
077: return XMLHTTPRequest.getResponseText(xmlHttpRequest);
078: }
079: };
080: return response;
081: }
082:
083: /**
084: * The number of milliseconds to wait for this HTTP request to complete.
085: */
086: private final int timeoutMillis;
087:
088: /*
089: * Timer used to force HTTPRequest timeouts. If the user has not requested a
090: * timeout then this field is null.
091: */
092: private final Timer timer;
093:
094: /*
095: * JavaScript XmlHttpRequest object that this Java class wraps. This field is
096: * not final because we transfer ownership of it to the HTTPResponse object
097: * and set this field to null.
098: */
099: private JavaScriptObject xmlHttpRequest;
100:
101: /**
102: * Constructs an instance of the Request object.
103: *
104: * @param xmlHttpRequest JavaScript XmlHttpRequest object instance
105: * @param timeoutMillis number of milliseconds to wait for a response
106: * @param callback callback interface to use for notification
107: *
108: * @throws IllegalArgumentException if timeoutMillis < 0
109: * @throws NullPointerException if xmlHttpRequest, or callback are null
110: */
111: Request(JavaScriptObject xmlHttpRequest, int timeoutMillis,
112: final RequestCallback callback) {
113: if (xmlHttpRequest == null) {
114: throw new NullPointerException();
115: }
116:
117: if (callback == null) {
118: throw new NullPointerException();
119: }
120:
121: if (timeoutMillis < 0) {
122: throw new IllegalArgumentException();
123: }
124:
125: this .timeoutMillis = timeoutMillis;
126:
127: this .xmlHttpRequest = xmlHttpRequest;
128:
129: if (timeoutMillis > 0) {
130: // create and start a Timer
131: timer = new Timer() {
132: @Override
133: public void run() {
134: fireOnTimeout(callback);
135: }
136: };
137:
138: timer.schedule(timeoutMillis);
139: } else {
140: // no Timer required
141: timer = null;
142: }
143: }
144:
145: /**
146: * Cancels a pending request. If the request has already been canceled or if
147: * it has timed out no action is taken.
148: */
149: public void cancel() {
150: /*
151: * There is a strange race condition that occurs on Mozilla when you cancel
152: * a request while the response is coming in. It appears that in some cases
153: * the onreadystatechange handler is still called after the handler function
154: * has been deleted and during the call to XmlHttpRequest.abort(). So we
155: * null the xmlHttpRequest here and that will prevent the
156: * fireOnResponseReceived method from calling the callback function.
157: *
158: * Setting the onreadystatechange handler to null gives us the correct
159: * behavior in Mozilla but crashes IE. That is why we have chosen to fixed
160: * this in Java by nulling out our reference to the XmlHttpRequest object.
161: */
162: if (xmlHttpRequest != null) {
163: JavaScriptObject xmlHttp = xmlHttpRequest;
164: xmlHttpRequest = null;
165:
166: XMLHTTPRequest.abort(xmlHttp);
167:
168: cancelTimer();
169: }
170: }
171:
172: /**
173: * Returns true if this request is waiting for a response.
174: *
175: * @return true if this request is waiting for a response
176: */
177: public boolean isPending() {
178: if (xmlHttpRequest == null) {
179: return false;
180: }
181:
182: int readyState = XMLHTTPRequest.getReadyState(xmlHttpRequest);
183:
184: /*
185: * Because we are doing asynchronous requests it is possible that we can
186: * call XmlHttpRequest.send and still have the XmlHttpRequest.getReadyState
187: * method return the state as XmlHttpRequest.OPEN. That is why we include
188: * open although it is not *technically* true since open implies that the
189: * request has not been sent.
190: */
191: switch (readyState) {
192: case XMLHTTPRequest.OPEN:
193: case XMLHTTPRequest.SENT:
194: case XMLHTTPRequest.RECEIVING:
195: return true;
196: }
197:
198: return false;
199: }
200:
201: /*
202: * Stops the current HTTPRequest timer if there is one.
203: */
204: private void cancelTimer() {
205: if (timer != null) {
206: timer.cancel();
207: }
208: }
209:
210: /*
211: * Method called when the JavaScript XmlHttpRequest object's readyState
212: * reaches 4 (LOADED).
213: *
214: * NOTE: this method is called from JSNI
215: */
216: @SuppressWarnings("unused")
217: private void fireOnResponseReceived(RequestCallback callback) {
218: UncaughtExceptionHandler handler = GWT
219: .getUncaughtExceptionHandler();
220: if (handler != null) {
221: fireOnResponseReceivedAndCatch(handler, callback);
222: } else {
223: fireOnResponseReceivedImpl(callback);
224: }
225: }
226:
227: private void fireOnResponseReceivedAndCatch(
228: UncaughtExceptionHandler handler, RequestCallback callback) {
229: try {
230: fireOnResponseReceivedImpl(callback);
231: } catch (Throwable e) {
232: handler.onUncaughtException(e);
233: }
234: }
235:
236: private void fireOnResponseReceivedImpl(RequestCallback callback) {
237: if (xmlHttpRequest == null) {
238: // the request has timed out at this point
239: return;
240: }
241:
242: cancelTimer();
243:
244: /*
245: * We cannot use cancel here because it would clear the contents of the
246: * JavaScript XmlHttpRequest object so we manually null out our reference to
247: * the JavaScriptObject
248: */
249: final JavaScriptObject xmlHttp = xmlHttpRequest;
250: xmlHttpRequest = null;
251:
252: String errorMsg = XMLHTTPRequest
253: .getBrowserSpecificFailure(xmlHttp);
254: if (errorMsg != null) {
255: Throwable exception = new RuntimeException(errorMsg);
256: callback.onError(this , exception);
257: } else {
258: Response response = createResponse(xmlHttp);
259: callback.onResponseReceived(this , response);
260: }
261: }
262:
263: /*
264: * Method called when this request times out.
265: *
266: * NOTE: this method is called from JSNI
267: */
268: private final void fireOnTimeout(RequestCallback callback) {
269: if (xmlHttpRequest == null) {
270: // the request has been received at this point
271: return;
272: }
273:
274: cancel();
275:
276: callback.onError(this , new RequestTimeoutException(this,
277: timeoutMillis));
278: }
279: }
|