PaddedJsonRpcProxy.java revision ef6fe028fcc667366e8ac30fe63ba314a4b1d745
1package autotest.common; 2 3import com.google.gwt.core.client.GWT; 4import com.google.gwt.core.client.JavaScriptObject; 5import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; 6import com.google.gwt.dom.client.Element; 7import com.google.gwt.json.client.JSONObject; 8import com.google.gwt.user.client.Timer; 9 10import java.util.HashMap; 11import java.util.Map; 12 13/** 14 * JsonRpcProxy that uses "JSON with Padding" (JSONP) to make requests. This allows it to get 15 * around the Same-Origin Policy that limits XmlHttpRequest-based techniques. However, it requires 16 * close coupling with the server and it allows the server to execute arbitrary JavaScript within 17 * the page, so it should only be used with trusted servers. 18 * 19 * See http://code.google.com/docreader/#p=google-web-toolkit-doc-1-5&s=google-web-toolkit-doc-1-5&t=Article_UsingGWTForJSONMashups. 20 * Much of the code here is borrowed from or inspired by that article. 21 */ 22public class PaddedJsonRpcProxy extends JsonRpcProxy { 23 private static final int REQUEST_TIMEOUT_MILLIS = 10000; 24 private static final String SCRIPT_TAG_PREFIX = "__jsonp_rpc_script"; 25 private static final String CALLBACK_PREFIX = "__jsonp_rpc_callback"; 26 27 private static int idCounter = 0; 28 29 private String rpcUrl; 30 31 private static class JsonpRequest { 32 private int requestId; 33 private String requestData; 34 private Element scriptTag; 35 private String callbackName; 36 private Timer timeoutTimer; 37 private JsonRpcCallback rpcCallback; 38 private boolean timedOut = false; 39 40 public JsonpRequest(String requestData, JsonRpcCallback rpcCallback) { 41 requestId = getRequestId(); 42 this.requestData = requestData; 43 this.rpcCallback = rpcCallback; 44 45 callbackName = CALLBACK_PREFIX + requestId; 46 addCallback(this, callbackName); 47 48 timeoutTimer = new Timer() { 49 @Override 50 public void run() { 51 GWT.log("timeout firing " + requestId, null); 52 timedOut = true; 53 cleanup(); 54 notify.showError("Request timed out"); 55 JsonpRequest.this.rpcCallback.onError(null); 56 } 57 }; 58 } 59 60 private String getFullUrl(String rpcUrl) { 61 Map<String, String> arguments = new HashMap<String, String>(); 62 arguments.put("callback", callbackName); 63 arguments.put("request", requestData); 64 return rpcUrl + "?" + Utils.encodeUrlArguments(arguments); 65 } 66 67 public void send(String rpcUrl) { 68 scriptTag = addScript(getFullUrl(rpcUrl), requestId); 69 timeoutTimer.schedule(REQUEST_TIMEOUT_MILLIS); 70 notify.setLoading(true); 71 GWT.log("request sent " + requestId + " <" + requestData + ">", null); 72 } 73 74 public void cleanup() { 75 dropScript(scriptTag); 76 dropCallback(callbackName); 77 timeoutTimer.cancel(); 78 notify.setLoading(false); 79 } 80 81 /** 82 * This method is called directly from native code (the dynamically loaded <script> calls 83 * our callback method, which calls this), so we need to do proper GWT exception handling 84 * manually. 85 * 86 * See the implementation of com.google.gwt.user.client.Timer.fire(), from which this 87 * technique was borrowed. 88 */ 89 public void handleResponse(JavaScriptObject responseJso) { 90 UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler(); 91 if (handler == null) { 92 handleResponseImpl(responseJso); 93 return; 94 } 95 96 try { 97 handleResponseImpl(responseJso); 98 } catch (Throwable throwable) { 99 handler.onUncaughtException(throwable); 100 } 101 } 102 103 public void handleResponseImpl(JavaScriptObject responseJso) { 104 GWT.log("response arrived " + requestId, null); 105 cleanup(); 106 if (timedOut) { 107 GWT.log("already timed out " + requestId, null); 108 return; 109 } 110 111 JSONObject responseObject = new JSONObject(responseJso); 112 GWT.log("handling response " + requestId 113 + " (" + responseJso.toString().length() + ")", 114 null); 115 handleResponseObject(responseObject, rpcCallback); 116 GWT.log("done " + requestId, null); 117 } 118 } 119 120 public PaddedJsonRpcProxy(String rpcUrl) { 121 this.rpcUrl = rpcUrl; 122 } 123 124 private static int getRequestId() { 125 return idCounter++; 126 } 127 128 private static native void addCallback(JsonpRequest request, String callbackName) /*-{ 129 window[callbackName] = function(someData) { 130 request.@autotest.common.PaddedJsonRpcProxy.JsonpRequest::handleResponse(Lcom/google/gwt/core/client/JavaScriptObject;)(someData); 131 } 132 }-*/; 133 134 private static native void dropCallback(String callbackName) /*-{ 135 delete window[callbackName]; 136 }-*/; 137 138 private static Element addScript(String url, int requestId) { 139 String scriptId = SCRIPT_TAG_PREFIX + requestId; 140 Element scriptElement = addScriptToDocument(scriptId, url); 141 return scriptElement; 142 } 143 144 private static native Element addScriptToDocument(String uniqueId, String url) /*-{ 145 var elem = document.createElement("script"); 146 elem.setAttribute("language", "JavaScript"); 147 elem.setAttribute("src", url); 148 elem.setAttribute("id", uniqueId); 149 document.getElementsByTagName("body")[0].appendChild(elem); 150 return elem; 151 }-*/; 152 153 private static native void dropScript(Element scriptElement) /*-{ 154 document.getElementsByTagName("body")[0].removeChild(scriptElement); 155 }-*/; 156 157 @Override 158 protected void sendRequest(JSONObject request, JsonRpcCallback callback) { 159 JsonpRequest jsonpRequest = new JsonpRequest(request.toString(), callback); 160 jsonpRequest.send(rpcUrl); 161 } 162} 163