5 * (C) 2006 by Derrell Lipman
9 * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/
13 * This is a simple JSON-RPC server.
16 /* Bring in the json format/parse functions */
17 jsonrpc_include("json.esp");
19 /* Bring in the date class */
20 jsonrpc_include("jsondate.esp");
22 /* bring the string functions into the global frame */
25 /* Bring the system functions into the global frame */
30 print(vsprintf(arguments));
35 * All of our manipulation of JSON RPC methods will be through this object.
36 * Each class of methods will assign to here, and all of the constants will
37 * also be in this object.
39 jsonrpc = new Object();
40 jsonrpc.Constant = new Object();
41 jsonrpc.Constant.ErrorOrigin = new Object(); /* error origins */
42 jsonrpc.Constant.ErrorCode = new Object(); /* server-generated error codes */
43 jsonrpc.method = new Object(); /* methods available in requested class */
47 * ScriptTransport constants
49 jsonrpc.Constant.ScriptTransport = new Object();
50 jsonrpc.Constant.ScriptTransport.NotInUse = -1;
54 * JSON-RPC error origin constants
56 jsonrpc.Constant.ErrorOrigin.Server = 1;
57 jsonrpc.Constant.ErrorOrigin.Application = 2;
58 jsonrpc.Constant.ErrorOrigin.Transport = 3;
59 jsonrpc.Constant.ErrorOrigin.Client = 4;
64 * JSON-RPC server-generated error code constants
68 * Error code, value 0: Unknown Error
70 * The default error code, used only when no specific error code is passed to
71 * the JsonRpcError constructor. This code should generally not be used.
73 jsonrpc.Constant.ErrorCode.Unknown = 0;
76 * Error code, value 1: Illegal Service
78 * The service name contains illegal characters or is otherwise deemed
79 * unacceptable to the JSON-RPC server.
81 jsonrpc.Constant.ErrorCode.IllegalService = 1;
84 * Error code, value 2: Service Not Found
86 * The requested service does not exist at the JSON-RPC server.
88 jsonrpc.Constant.ErrorCode.ServiceNotFound = 2;
91 * Error code, value 3: Class Not Found
93 * If the JSON-RPC server divides service methods into subsets (classes), this
94 * indicates that the specified class was not found. This is slightly more
95 * detailed than "Method Not Found", but that error would always also be legal
96 * (and true) whenever this one is returned. (Not used in this implementation)
98 jsonrpc.Constant.ErrorCode.ClassNotFound = 3; // not used in this implementation
101 * Error code, value 4: Method Not Found
103 * The method specified in the request is not found in the requested service.
105 jsonrpc.Constant.ErrorCode.MethodNotFound = 4;
108 * Error code, value 5: Parameter Mismatch
110 * If a method discovers that the parameters (arguments) provided to it do not
111 * match the requisite types for the method's parameters, it should return
112 * this error code to indicate so to the caller.
114 jsonrpc.Constant.ErrorCode.PaameterMismatch = 5;
117 * Error code, value 6: Permission Denied
119 * A JSON-RPC service provider can require authentication, and that
120 * authentication can be implemented such the method takes authentication
121 * parameters, or such that a method or class of methods requires prior
122 * authentication. If the caller has not properly authenticated to use the
123 * requested method, this error code is returned.
125 jsonrpc.Constant.ErrorCode.PermissionDenied = 6;
128 * Error code, value 7: Unexpected Output
130 * The called method illegally generated output to the browser, which would
131 * have preceeded the JSON-RPC data.
133 jsonrpc.Constant.ErrorCode.UnexpectedOutput = 7;
141 function sendReply(reply, scriptTransportId)
143 /* If not using ScriptTransport... */
144 if (scriptTransportId == jsonrpc.Constant.ScriptTransport.NotInUse)
146 /* ... then just output the reply. */
151 /* Otherwise, we need to add a call to a qooxdoo-specific function */
153 "qx.io.remote.ScriptTransport._requestFinished(" +
154 scriptTransportId + ", " + reply +
161 function _jsonValidRequest(req)
163 if (req == undefined)
168 if (req.id == undefined)
173 if (req.service == undefined)
178 if (req.method == undefined)
183 if (req.params == undefined)
190 jsonrpc.validRequest = _jsonValidRequest;
191 _jsonValidRequest = null;
196 * This class allows service methods to easily provide error information for
197 * return via JSON-RPC.
199 function _JsonRpcError_create(origin, code, message)
201 var o = new Object();
203 o.data = new Object();
204 o.data.origin = origin;
206 o.data.message = message;
207 o.scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
208 o.__type = "_JsonRpcError";
210 function _origin(origin)
212 this.origin = origin;
214 o.setOrigin = _origin;
216 function _setError(code, message)
219 this.message = message;
221 o.setError = _setError;
229 function _setScriptTransportId(id)
231 this.scriptTransportId = id;
233 o.setScriptTransportId = _setScriptTransportId;
239 var ret = new Array(2);
240 ret.error = this.data;
242 sendReply(Json.encode(ret), this.scriptTransportId);
249 jsonrpc.createError = _JsonRpcError_create;
250 _JsonRpcError_create = null;
253 * 'input' is the user-provided json-encoded request
254 * 'jsonInput' is that request, decoded into its object form
257 var jsonInput = null;
259 /* Allocate a generic error object */
260 error = jsonrpc.createError(jsonrpc.Constant.ErrorOrigin.Server,
261 jsonrpc.Constant.ErrorCode.Unknown,
264 /* Assume (default) we're not using ScriptTransport */
265 scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
267 /* What type of request did we receive? */
268 if (request["REQUEST_METHOD"] == "POST" &&
269 request["CONTENT_TYPE"] == "text/json")
271 /* We found literal POSTed json-rpc data (we hope) */
272 input = request["POST_DATA"];
273 jsonInput = Json.decode(input);
275 else if (request["REQUEST_METHOD"] == "GET" &&
276 form["_ScriptTransport_id"] != undefined &&
277 form["_ScriptTransport_data"] != undefined)
279 /* We have what looks like a valid ScriptTransport request */
280 scriptTransportId = form["_ScriptTransport_id"];
281 error.setScriptTransportId(scriptTransportId);
282 input = form["_ScriptTransport_data"];
283 jsonInput = Json.decode(input);
286 /* Ensure that this was a JSON-RPC service request */
287 if (! jsonrpc.validRequest(jsonInput))
290 * This request was not issued with JSON-RPC so echo the error rather than
291 * issuing a JsonRpcError response.
293 write("JSON-RPC request expected; service, method or params missing<br>");
298 * Ok, it looks like JSON-RPC, so we'll return an Error object if we encounter
299 * errors from here on out.
301 error.setId(jsonInput.id);
303 /* Service and method names may contain these characters */
305 "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
307 /* The first letter of service and method names must be a letter */
308 var nameFirstLetter =
309 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
312 * Ensure the method name is kosher. A meethod name should be:
314 * - first character is in [a-zA-Z]
315 * - other characters are in [_a-zA-Z0-9]
318 /* First check for legal characters */
319 if (strspn(jsonInput.method, nameChars) != strlen(jsonInput.method))
321 /* There's some illegal character in the service name */
322 error.setError(JsonRpcError.MethodNotFound,
323 "Illegal character found in method name.");
328 /* Now ensure that it begins with a letter */
329 if (strspn(substr(jsonInput.method, 0, 1), nameFirstLetter) != 1)
331 error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
332 "The method name does not begin with a letter");
338 * Ensure the requested service name is kosher. A service name should be:
340 * - a dot-separated sequences of strings; no adjacent dots
341 * - first character of each string is in [a-zA-Z]
342 * - other characters are in [_a-zA-Z0-9]
345 /* First check for legal characters */
346 if (strspn(jsonInput.service, "." + nameChars) != strlen(jsonInput.service))
348 /* There's some illegal character in the service name */
349 error.setError(JsonRpcError.IllegalService,
350 "Illegal character found in service name.");
356 * Now ensure there are no double dots.
358 * Frustration with ejs. Result must be NULL, but we can't use the ===
359 * operator: strstr() === null so we have to use typeof. If the result isn't
360 * null, then it'll be a number and therefore not type "pointer".
362 if (typeof(strstr(jsonInput.service, "..")) != "pointer")
364 error.setError(JsonRpcError.IllegalService,
365 "Illegal use of two consecutive dots in service name");
370 /* Explode the service name into its dot-separated parts */
371 var serviceComponents = split(".", jsonInput.service);
373 /* Ensure that each component begins with a letter */
374 for (var i = 0; i < serviceComponents.length; i++)
376 if (strspn(substr(serviceComponents[i], 0, 1), nameFirstLetter) != 1)
378 error.setError(jsonrpc.Constant.ErrorCode.IllegalService,
379 "A service name component does not begin with a letter");
386 * Now replace all dots with slashes so we can locate the service script. We
387 * also retain the split components of the path, as the class name of the
388 * service is the last component of the path.
390 var servicePath = join("/", serviceComponents) + ".esp";
392 /* Load the requested class */
393 if (jsonrpc_include(servicePath))
395 /* Couldn't find the requested service */
396 error.setError(jsonrpc.Constant.ErrorCode.ServiceNotFound,
397 "Service class `" + servicePath + "` does not exist.");
403 * Find the requested method.
405 * What we really want to do here, and could do in any reasonable language,
408 * method = jsonrpc.method[jsonInput.method];
409 * if (method && typeof(method) == "function") ...
411 * The following completely unreasonable sequence of commands is because:
413 * (a) ejs evaluates all OR'ed expressions even if an early one is false, and
414 * bars on the typeof(method) call if method is undefined
416 * (b) ejs does not allow comparing against the string "function"!!! What
417 * the hell is special about that particular string???
419 * E-gad. What a mess.
421 var method = jsonrpc.method[jsonInput.method];
422 var valid = (method != undefined);
425 var type = typeof(method);
426 if (substr(type, 0, 1) != 'f' || substr(type, 1) != "unction")
434 error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
435 "Method `" + method + "` not found.");
440 /* Most errors from here on out will be Application-generated */
441 error.setOrigin(jsonrpc.Constant.ErrorOrigin.Application);
443 /* Call the requested method passing it the provided params */
444 var retval = method(jsonInput.params, error);
446 /* See if the result of the function was actually an error object */
447 var wasError = (retval["__type"] != undefined);
450 wasError = retval.__type == "_JsonRpcError";
454 /* Yup, it was. Return the error */
459 /* Give 'em what they came for! */
460 var ret = new Object();
462 ret.id = jsonInput.id;
463 sendReply(Json.encode(ret), scriptTransportId);