1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @constructor 26 * @class 27 * This static class provides an interface for send requests to a server. It 28 * essentially wraps {@link AjxRpcRequest}. This {@link AjxRpc} link maintains a cache of 29 * {@link AjxRpcRequest} objects which it attempts to reuse before allocating additional 30 * objects. It also has a mechanism whereby if an {@link AjxRpcRequest} object is 31 * in a "busy" state for a extended period of time, it will reap it appropriately. 32 * 33 * @author Ross Dargahi 34 * @author Conrad Damon 35 * 36 * @see AjxRpcRequest 37 */ 38 AjxRpc = function() { 39 }; 40 41 AjxRpc.__rpcCache = []; // The pool of RPC contexts available 42 AjxRpc.__rpcOutstanding = {}; // The pool of RPC contexts in use 43 44 AjxRpc.__RPC_CACHE_MAX = 50; // maximum number of busy contexts we can have 45 AjxRpc.__RPC_ID = 0; // used for context IDs 46 AjxRpc.__RPC_IN_USE = 0; // number of contexts that are busy 47 AjxRpc.__RPC_HIGH_WATER = 0; // high water mark for busy contexts 48 AjxRpc.__RPC_REAP_AGE = 300000; // 5 minutes; mark any context older than this (in ms) as free 49 AjxRpc.__RPC_REAP_INTERVAL = 1800000; // 30 minutes; run the reaper this often 50 51 /** 52 * Submits a request to a URL. The request is handled through a pool of request 53 * contexts (each a wrapped XmlHttpRequest). The context does the real work. 54 * 55 * @param {string} [requestStr] the HTTP request string/document 56 * @param {string} serverUrl the request target 57 * @param {array} [requestHeaders] an array of HTTP request headers 58 * @param {AjxCallback} callback the callback for asynchronous requests. This callback 59 * will be invoked when the requests completes. It will be passed the same 60 * values as when this method is invoked synchronously (see the return values 61 * below) with the exception that if the call times out (see timeout param 62 * below), then the object passed to the callback will be the same as in the 63 * error case with the exception that the status will be set to 64 * {@link AjxRpcRequest.TIMEDOUT}. 65 * @param {Constant} [method] the HTTP method -- GET, POST, PUT, DELETE. if <code>true</code>, use get method for backward compatibility 66 * @param {number} [timeout] the timeout (in milliseconds) after which the request is canceled 67 * 68 * @return {object|hash} if invoking in asynchronous mode, then it will return the id of the 69 * underlying {@link AjxRpcRequest} object. Else if invoked synchronously, if 70 * there is no error (i.e. we get a HTTP result code of 200 from the server), 71 * an object with the following attributes is returned 72 * <ul> 73 * <li>text - the string response text</li> 74 * <li>xml - the string response xml</li> 75 * <li>success - boolean set to true</li> 76 * </ul> 77 * If there is an error, then the following will be returned 78 * <ul> 79 * <li>text - the string response text<li> 80 * <li>xml - the string response xml </li> 81 * <li>success - boolean set to <code>false</code></li> 82 * <li>status - HTTP status</li> 83 * </ul> 84 * 85 * @throws {AjxException.NETWORK_ERROR} a network error occurs 86 * @throws {AjxException.UNKNOWN_ERROR} an unknown error occurs 87 * 88 * @see AjxRpcRequest#invoke 89 * 90 */ 91 AjxRpc.invoke = 92 function(requestStr, serverUrl, requestHeaders, callback, method, timeout) { 93 94 var asyncMode = (callback != null); 95 var rpcCtxt = AjxRpc.__getFreeRpcCtxt(); 96 97 try { 98 var response = rpcCtxt.invoke(requestStr, serverUrl, requestHeaders, callback, method, timeout); 99 } catch (ex) { 100 var newEx = new AjxException(); 101 newEx.method = "AjxRpc.prototype._invoke"; 102 if (ex instanceof Error) { 103 newEx.detail = ex.message; 104 newEx.code = AjxException.NETWORK_ERROR; 105 newEx.msg = "Network error"; 106 } else if (ex.code == 101){ 107 // Chrome 108 newEx.detail = ex.message; 109 newEx.code = AjxException.NETWORK_ERROR; 110 newEx.msg = "Network error"; 111 } else { 112 newEx.detail = ex.toString(); 113 newEx.code = AjxException.UNKNOWN_ERROR; 114 newEx.msg = "Unknown Error"; 115 } 116 // exception hit: we're done whether sync or async, free the context 117 AjxRpc.freeRpcCtxt(rpcCtxt); 118 throw newEx; 119 } 120 if (!asyncMode) { 121 // we've returned from a sync request, free the context 122 AjxRpc.freeRpcCtxt(rpcCtxt); 123 } 124 return response; 125 }; 126 127 /** 128 * @private 129 */ 130 AjxRpc.freeRpcCtxt = 131 function(rpcCtxt) { 132 // we're done using this rpcCtxt. Add it back to the pool 133 if (AjxRpc.__rpcOutstanding[rpcCtxt.id]) { 134 AjxDebug.println(AjxDebug.RPC, AjxDebug._getTimeStamp() + " --- freeing rpcCtxt " + rpcCtxt.id); 135 AjxRpc.__rpcCache.push(rpcCtxt); 136 delete AjxRpc.__rpcOutstanding[rpcCtxt.id]; 137 AjxRpc.__RPC_IN_USE--; 138 } 139 }; 140 141 AjxRpc.removeRpcCtxt = 142 function(rpcCtxt) { 143 AjxDebug.println(AjxDebug.RPC, AjxDebug._getTimeStamp() + " REMOVE rpcCtxt " + rpcCtxt.id); 144 if (AjxRpc.__rpcOutstanding[rpcCtxt.id]) { 145 delete AjxRpc.__rpcOutstanding[rpcCtxt.id]; 146 AjxRpc.__RPC_IN_USE--; 147 } 148 AjxUtil.arrayRemove(AjxRpc.__rpcCache, rpcCtxt); 149 }; 150 151 /** 152 * Returns the request from the RPC context with the given ID. 153 * 154 * @param {String} id RPC context ID 155 * 156 * @return The <i>AjxRpcRequest</i> object associated with <code>id</code> or null 157 * if no object exists for the supplied id 158 * @type AjxRpcRequest 159 * 160 * @private 161 */ 162 AjxRpc.getRpcRequestById = 163 function(id) { 164 return (AjxRpc.__rpcOutstanding[id]); 165 }; 166 167 /** 168 * Factory method for getting context objects. 169 * 170 * @private 171 */ 172 AjxRpc.__getFreeRpcCtxt = 173 function() { 174 var rpcCtxt; 175 176 if (AjxRpc.__rpcCache.length > 0) { 177 rpcCtxt = AjxRpc.__rpcCache.pop(); 178 AjxDebug.println(AjxDebug.RPC, AjxDebug._getTimeStamp() + " reusing RPC ID " + rpcCtxt.id); 179 } else { 180 if (AjxRpc.__RPC_IN_USE < AjxRpc.__RPC_CACHE_MAX) { 181 // we haven't reached our limit, so create a new AjxRpcRequest 182 var id = "__RpcCtxt_" + AjxRpc.__RPC_ID; 183 rpcCtxt = new AjxRpcRequest(id); 184 AjxRpc.__RPC_ID++; 185 AjxDebug.println(AjxDebug.RPC, AjxDebug._getTimeStamp() + " Created RPC " + id); 186 } else { 187 // yikes, we're out of rpc's! Look for an old one to kill. 188 rpcCtxt = AjxRpc.__reap(); 189 190 // if reap didn't find one either, bail. 191 if (!rpcCtxt) { 192 var text = []; 193 for (var i in AjxRpc.__rpcOutstanding) { 194 var rpcCtxt = AjxRpc.__rpcOutstanding[i]; 195 text.push(rpcCtxt.methodName); 196 } 197 var detail = text.join("<br>") + "<br>"; 198 AjxDebug.println(AjxDebug.RPC, AjxDebug._getTimeStamp() + " Out of RPC cache!!! Outstanding requests: " + detail); 199 throw new AjxException("Out of RPC cache", AjxException.OUT_OF_RPC_CACHE, "AjxRpc.__getFreeRpcCtxt", detail); 200 } 201 } 202 } 203 204 AjxRpc.__rpcOutstanding[rpcCtxt.id] = rpcCtxt; 205 AjxRpc.__RPC_IN_USE++; 206 if (AjxRpc.__RPC_IN_USE > AjxRpc.__RPC_HIGH_WATER) { 207 AjxRpc.__RPC_HIGH_WATER = AjxRpc.__RPC_IN_USE; 208 AjxDebug.println(AjxDebug.RPC, AjxDebug._getTimeStamp() + " High water mark: " + AjxRpc.__RPC_HIGH_WATER); 209 } 210 211 // always reset timestamp before returning rpcCtxt 212 rpcCtxt.timestamp = (new Date()).getTime(); 213 return rpcCtxt; 214 }; 215 216 /** 217 * Frees expired contexts. 218 * 219 * @param {boolean} all if true, frees all expired contexts; otherwise, returns the first one it finds 220 * @private 221 */ 222 AjxRpc.__reap = 223 function(all) { 224 var rpcCtxt; 225 var time = (new Date()).getTime(); 226 AjxDebug.println(AjxDebug.RPC, AjxDebug._getTimeStamp() + " Running RPC context reaper"); 227 for (var i in AjxRpc.__rpcOutstanding) { 228 rpcCtxt = AjxRpc.__rpcOutstanding[i]; 229 if ((rpcCtxt.timestamp + AjxRpc.__RPC_REAP_AGE) < time) { 230 DBG.println(AjxDebug.DBG1, "AjxRpc.__reap: cleared RPC context " + rpcCtxt.id); 231 AjxDebug.println(AjxDebug.RPC, AjxDebug._getTimeStamp() + " AjxRpc.__reap: cleared RPC context " + rpcCtxt.id); 232 rpcCtxt.cancel(); 233 delete AjxRpc.__rpcOutstanding[i]; 234 AjxRpc.__RPC_IN_USE--; 235 if (!all) { 236 return rpcCtxt; 237 } 238 } 239 } 240 return null; 241 }; 242 243 window.setInterval(AjxRpc.__reap.bind(null, true), AjxRpc.__RPC_REAP_INTERVAL); 244