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.JavaScriptObject;
019:
020: /**
021: * Utility class that serves as the one place where we interact with the
022: * JavaScript <code>XmlHttpRequest</code> object.
023: */
024: final class XMLHTTPRequest {
025:
026: public static final int UNITIALIZED = 0;
027: public static final int OPEN = 1;
028: public static final int SENT = 2;
029: public static final int RECEIVING = 3;
030: public static final int LOADED = 4;
031:
032: /*
033: * NOTE: Testing discovered that for some bizarre reason, on Mozilla, the
034: * JavaScript <code>XmlHttpRequest.onreadystatechange</code> handler function
035: * maybe still be called after it is deleted. The theory is that the callback
036: * is cached somewhere. Setting the handler to null has the desired effect on
037: * Mozilla but it causes IE to crash during the assignment. The solution is to
038: * set it to an empty function.
039: */
040: static native void abort(JavaScriptObject xmlHttpRequest) /*-{
041: xmlHttpRequest.onreadystatechange = @com.google.gwt.user.client.impl.HTTPRequestImpl::nullFunc;
042: xmlHttpRequest.abort();
043: }-*/;
044:
045: static native String getAllResponseHeaders(
046: JavaScriptObject xmlHttpRequest) /*-{
047: return xmlHttpRequest.getAllResponseHeaders();
048: }-*/;
049:
050: /**
051: * Tests if the JavaScript <code>XmlHttpRequest.status</code> property is
052: * readable. This can return failure in two different known scenarios:
053: *
054: * <ol>
055: * <li>On Mozilla, after a network error, attempting to read the status code
056: * results in an exception being thrown. See <a
057: * href="https://bugzilla.mozilla.org/show_bug.cgi?id=238559">https://bugzilla.mozilla.org/show_bug.cgi?id=238559</a>.
058: * </li>
059: * <li>On Safari, if the HTTP response does not include any response text.
060: * See <a
061: * href="http://bugs.webkit.org/show_bug.cgi?id=3810">http://bugs.webkit.org/show_bug.cgi?id=3810</a>.
062: * </li>
063: * </ol>
064: *
065: * @param xmlHttpRequest the JavaScript <code>XmlHttpRequest</code> object
066: * to test
067: * @return a String message containing an error message if the
068: * <code>XmlHttpRequest.status</code> code is unreadable or null if
069: * the status code could be successfully read.
070: */
071: static native String getBrowserSpecificFailure(
072: JavaScriptObject xmlHttpRequest) /*-{
073: try {
074: if (xmlHttpRequest.status === undefined) {
075: return "XmlHttpRequest.status == undefined, please see Safari bug " +
076: "http://bugs.webkit.org/show_bug.cgi?id=3810 for more details";
077: }
078: return null;
079: } catch (e) {
080: return "Unable to read XmlHttpRequest.status; likely causes are a " +
081: "networking error or bad cross-domain request. Please see " +
082: "https://bugzilla.mozilla.org/show_bug.cgi?id=238559 for more " +
083: "details";
084: }
085: }-*/;
086:
087: /**
088: * Returns an array of headers built by parsing the string of headers returned
089: * by the JavaScript <code>XmlHttpRequest</code> object.
090: *
091: * @param xmlHttpRequest
092: * @return array of Header items
093: */
094: static Header[] getHeaders(JavaScriptObject xmlHttpRequest) {
095: String allHeaders = getAllResponseHeaders(xmlHttpRequest);
096: String[] unparsedHeaders = allHeaders.split("\n");
097: Header[] parsedHeaders = new Header[unparsedHeaders.length];
098:
099: for (int i = 0, n = unparsedHeaders.length; i < n; ++i) {
100: String unparsedHeader = unparsedHeaders[i];
101:
102: if (unparsedHeader.length() == 0) {
103: continue;
104: }
105:
106: int endOfNameIdx = unparsedHeader.indexOf(':');
107: if (endOfNameIdx < 0) {
108: continue;
109: }
110:
111: final String name = unparsedHeader.substring(0,
112: endOfNameIdx).trim();
113: final String value = unparsedHeader.substring(
114: endOfNameIdx + 1).trim();
115: Header header = new Header() {
116: @Override
117: public String getName() {
118: return name;
119: }
120:
121: @Override
122: public String getValue() {
123: return value;
124: }
125:
126: @Override
127: public String toString() {
128: return name + " : " + value;
129: }
130: };
131:
132: parsedHeaders[i] = header;
133: }
134:
135: return parsedHeaders;
136: }
137:
138: static native int getReadyState(JavaScriptObject xmlHttpRequest) /*-{
139: return xmlHttpRequest.readyState;
140: }-*/;
141:
142: static native String getResponseHeader(
143: JavaScriptObject xmlHttpRequest, String header) /*-{
144: try {
145: return xmlHttpRequest.getResponseHeader(header);
146: } catch (e) {
147: // purposely ignored
148: }
149: return null;
150: }-*/;
151:
152: static native String getResponseText(JavaScriptObject xmlHttpRequest) /*-{
153: return xmlHttpRequest.responseText;
154: }-*/;
155:
156: static native int getStatusCode(JavaScriptObject xmlHttpRequest) /*-{
157: return xmlHttpRequest.status;
158: }-*/;
159:
160: static native String getStatusText(JavaScriptObject xmlHttpRequest) /*-{
161: return xmlHttpRequest.statusText;
162: }-*/;
163:
164: static boolean isResponseReady(JavaScriptObject xmlHttpRequest) {
165: return getReadyState(xmlHttpRequest) == LOADED;
166: }
167:
168: /**
169: * Opens the request and catches any exceptions thrown. If an exception is
170: * caught, its string representation will be returned. This is the only signal
171: * that an error has occurred.
172: *
173: * @param xmlHttpRequest JavaScript <code>XmlHttpRequest</code> object
174: * @param httpMethod the method to use for open call
175: * @param url the URL to use for the open call
176: * @param async true if we should do an asynchronous open
177: * @return error message if an exception is thrown or null if there is none
178: */
179: static native String open(JavaScriptObject xmlHttpRequest,
180: String httpMethod, String url, boolean async) /*-{
181: try {
182: xmlHttpRequest.open(httpMethod, url, async);
183: return null;
184: } catch (e) {
185: return e.message || e.toString();
186: }
187: }-*/;
188:
189: /**
190: * Opens the request and catches any exceptions thrown. If an exception is
191: * caught, its string representation will be returned. This is the only signal
192: * that an error has occurred.
193: *
194: * @param xmlHttpRequest JavaScript <code>XmlHttpRequest</code> object
195: * @param httpMethod the method to use for open call
196: * @param url the URL to use for the open call
197: * @param async true if we should do an asynchronous open
198: * @param user user to use in the URL
199: * @return error message if an exception is thrown or null if there is none
200: */
201: static native String open(JavaScriptObject xmlHttpRequest,
202: String httpMethod, String url, boolean async, String user) /*-{
203: try {
204: xmlHttpRequest.open(httpMethod, url, async, user);
205: return null;
206: } catch (e) {
207: return e.message || e.toString();
208: }
209: }-*/;
210:
211: /**
212: * Opens the request and catches any exceptions thrown. If an exception is
213: * caught, its string representation will be returned. This is the only signal
214: * that an error has occurred.
215: *
216: * @param xmlHttpRequest JavaScript <code>XmlHttpRequest</code> object
217: * @param httpMethod the method to use for open call
218: * @param url the URL to use for the open call
219: * @param async true if we should do an asynchronous open
220: * @param user user to use in the URL
221: * @param password password to use in the URL
222: * @return error message if an exception is thrown or null if there is none
223: */
224: static native String open(JavaScriptObject xmlHttpRequest,
225: String httpMethod, String url, boolean async, String user,
226: String password) /*-{
227: try {
228: xmlHttpRequest.open(httpMethod, url, async, user, password);
229: return null;
230: } catch (e) {
231: return e.message || e.toString();
232: }
233: }-*/;
234:
235: /*
236: * Creates a closure that calls the HTTPRequest::fireOnResponseRecieved method
237: * when the server's response is received and sends the data.
238: */
239: static native String send(JavaScriptObject xmlHttpRequest,
240: Request httpRequest, String requestData,
241: RequestCallback callback) /*-{
242: xmlHttpRequest.onreadystatechange = function() {
243: if (xmlHttpRequest.readyState == @com.google.gwt.http.client.XMLHTTPRequest::LOADED) {
244: xmlHttpRequest.onreadystatechange = @com.google.gwt.user.client.impl.HTTPRequestImpl::nullFunc;
245: httpRequest.@com.google.gwt.http.client.Request::fireOnResponseReceived(Lcom/google/gwt/http/client/RequestCallback;)(callback);
246: }
247: };
248: try {
249: xmlHttpRequest.send(requestData);
250: return null;
251: } catch (e) {
252: xmlHttpRequest.onreadystatechange = @com.google.gwt.user.client.impl.HTTPRequestImpl::nullFunc;
253: return e.message || e.toString();
254: }
255: }-*/;
256:
257: static native String setRequestHeader(
258: JavaScriptObject xmlHttpRequest, String header, String value) /*-{
259: try {
260: xmlHttpRequest.setRequestHeader(header, value);
261: return null;
262: } catch (e) {
263: return e.message || e.toString();
264: }
265: }-*/;
266:
267: private XMLHTTPRequest() {
268: }
269: }
|