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 date class */
17 jsonrpc_include("jsondate.esp");
19 /* bring the string functions into the global frame */
22 /* Bring the system functions into the global frame */
27 print(vsprintf(arguments));
34 request = new Array();
38 * All of our manipulation of JSON RPC methods will be through this object.
39 * Each class of methods will assign to here, and all of the constants will
40 * also be in this object.
42 jsonrpc = new Object();
43 jsonrpc.Constant = new Object();
44 jsonrpc.Constant.ErrorOrigin = new Object(); /* error origins */
45 jsonrpc.Constant.ErrorCode = new Object(); /* server-generated error codes */
46 jsonrpc.method = new Object(); /* methods available in requested class */
50 * ScriptTransport constants
52 jsonrpc.Constant.ScriptTransport = new Object();
53 jsonrpc.Constant.ScriptTransport.NotInUse = -1;
57 * JSON-RPC error origin constants
59 jsonrpc.Constant.ErrorOrigin.Server = 1;
60 jsonrpc.Constant.ErrorOrigin.Application = 2;
61 jsonrpc.Constant.ErrorOrigin.Transport = 3;
62 jsonrpc.Constant.ErrorOrigin.Client = 4;
67 * JSON-RPC server-generated error code constants
71 * Error code, value 0: Unknown Error
73 * The default error code, used only when no specific error code is passed to
74 * the JsonRpcError constructor. This code should generally not be used.
76 jsonrpc.Constant.ErrorCode.Unknown = 0;
79 * Error code, value 1: Illegal Service
81 * The service name contains illegal characters or is otherwise deemed
82 * unacceptable to the JSON-RPC server.
84 jsonrpc.Constant.ErrorCode.IllegalService = 1;
87 * Error code, value 2: Service Not Found
89 * The requested service does not exist at the JSON-RPC server.
91 jsonrpc.Constant.ErrorCode.ServiceNotFound = 2;
94 * Error code, value 3: Class Not Found
96 * If the JSON-RPC server divides service methods into subsets (classes), this
97 * indicates that the specified class was not found. This is slightly more
98 * detailed than "Method Not Found", but that error would always also be legal
99 * (and true) whenever this one is returned. (Not used in this implementation)
101 jsonrpc.Constant.ErrorCode.ClassNotFound = 3; // not used in this implementation
104 * Error code, value 4: Method Not Found
106 * The method specified in the request is not found in the requested service.
108 jsonrpc.Constant.ErrorCode.MethodNotFound = 4;
111 * Error code, value 5: Parameter Mismatch
113 * If a method discovers that the parameters (arguments) provided to it do not
114 * match the requisite types for the method's parameters, it should return
115 * this error code to indicate so to the caller.
117 jsonrpc.Constant.ErrorCode.PaameterMismatch = 5;
120 * Error code, value 6: Permission Denied
122 * A JSON-RPC service provider can require authentication, and that
123 * authentication can be implemented such the method takes authentication
124 * parameters, or such that a method or class of methods requires prior
125 * authentication. If the caller has not properly authenticated to use the
126 * requested method, this error code is returned.
128 jsonrpc.Constant.ErrorCode.PermissionDenied = 6;
131 * Error code, value 7: Unexpected Output
133 * The called method illegally generated output to the browser, which would
134 * have preceeded the JSON-RPC data.
136 jsonrpc.Constant.ErrorCode.UnexpectedOutput = 7;
144 function sendReply(reply, scriptTransportId)
146 /* If not using ScriptTransport... */
147 if (scriptTransportId == jsonrpc.Constant.ScriptTransport.NotInUse)
149 /* ... then just output the reply. */
154 /* Otherwise, we need to add a call to a qooxdoo-specific function */
156 "qx.io.remote.ScriptTransport._requestFinished(" +
157 scriptTransportId + ", " + reply +
167 * This class provides the JSON encoder and decoder, and some utility
173 function _jsonDecode(s)
175 var o = new Object();
177 o.service = "qooxdoo.test";
179 o.params = new Array(1);
180 o.params[0] = "hello world";
185 Json.decode = _jsonDecode;
188 function _jsonEncode(o)
190 return "{ result: \"hello world\" }"
194 Json.encode = _jsonEncode;
196 function _jsonValidRequest(req)
198 if (req == undefined)
203 if (req.id == undefined)
208 if (req.service == undefined)
213 if (req.method == undefined)
218 if (req.params == undefined)
225 jsonrpc.validRequest = _jsonValidRequest;
226 _jsonValidRequest = null;
231 * This class allows service methods to easily provide error information for
232 * return via JSON-RPC.
234 function _JsonRpcError_create(origin, code, message)
236 var o = new Object();
238 o.data = new Object();
239 o.data.origin = origin;
241 o.data.message = message;
242 o.scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
243 o.__type = "_JsonRpcError";
245 function _origin(origin)
247 this.origin = origin;
249 o.setOrigin = _origin;
251 function _setError(code, message)
254 this.message = message;
256 o.setError = _setError;
264 function _setScriptTransportId(id)
266 this.scriptTransportId = id;
268 o.setScriptTransportId = _setScriptTransportId;
274 var ret = new Array(2);
275 ret.error = this.data;
277 sendReply(Json.encode(ret), this.scriptTransportId);
284 jsonrpc.createError = _JsonRpcError_create;
285 _JsonRpcError_create = null;
288 * 'input' is the user-provided json-encoded request
289 * 'jsonInput' is that request, decoded into its object form
292 var jsonInput = null;
294 /* Allocate a generic error object */
295 error = jsonrpc.createError(jsonrpc.Constant.ErrorOrigin.Server,
296 jsonrpc.Constant.ErrorCode.Unknown,
299 /* Assume (default) we're not using ScriptTransport */
300 scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
302 /* What type of request did we receive? */
303 if (server["REQUEST_METHOD"] == "POST" &&
304 server["CONENT_TYPE"] == "text/json")
306 /* We found literal POSTed json-rpc data (we hope) */
307 input = request["POST_DATA"];
308 jsonInput = Json.decode(input);
310 else if (server["REQUEST_METHOD"] == "GET" &&
311 form["_ScriptTransport_id"] != undefined &&
312 form["_ScriptTransport_data"] != undefined)
314 /* We have what looks like a valid ScriptTransport request */
315 scriptTransportId = form["_ScriptTransport_id"];
316 error.setScriptTransportId(scriptTransportId);
317 input = form["_ScriptTransport_data"];
318 jsonInput = Json.decode(input);
322 jsonInput = Json.decode(input);
325 /* Ensure that this was a JSON-RPC service request */
326 if (! jsonrpc.validRequest(jsonInput))
329 * This request was not issued with JSON-RPC so echo the error rather than
330 * issuing a JsonRpcError response.
332 printf("JSON-RPC request expected; service, method or params missing<br>");
337 * Ok, it looks like JSON-RPC, so we'll return an Error object if we encounter
338 * errors from here on out.
340 error.setId(jsonInput.id);
342 /* Service and method names may contain these characters */
344 "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
346 /* The first letter of service and method names must be a letter */
347 var nameFirstLetter =
348 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
351 * Ensure the method name is kosher. A meethod name should be:
353 * - first character is in [a-zA-Z]
354 * - other characters are in [_a-zA-Z0-9]
357 /* First check for legal characters */
358 if (strspn(jsonInput.method, nameChars) != strlen(jsonInput.method))
360 /* There's some illegal character in the service name */
361 error.setError(JsonRpcError.MethodNotFound,
362 "Illegal character found in method name.");
367 /* Now ensure that it begins with a letter */
368 if (strspn(substr(jsonInput.method, 0, 1), nameFirstLetter) != 1)
370 error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
371 "The method name does not begin with a letter");
377 * Ensure the requested service name is kosher. A service name should be:
379 * - a dot-separated sequences of strings; no adjacent dots
380 * - first character of each string is in [a-zA-Z]
381 * - other characters are in [_a-zA-Z0-9]
384 /* First check for legal characters */
385 if (strspn(jsonInput.service, "." + nameChars) != strlen(jsonInput.service))
387 /* There's some illegal character in the service name */
388 error.setError(JsonRpcError.IllegalService,
389 "Illegal character found in service name.");
395 * Now ensure there are no double dots.
397 * Frustration with ejs. Result must be NULL, but we can't use the ===
398 * operator: strstr() === null so we have to use typeof. If the result isn't
399 * null, then it'll be a number and therefore not type "pointer".
401 if (typeof(strstr(jsonInput.service, "..")) != "pointer")
403 error.setError(JsonRpcError.IllegalService,
404 "Illegal use of two consecutive dots in service name");
409 /* Explode the service name into its dot-separated parts */
410 var serviceComponents = split(".", jsonInput.service);
412 /* Ensure that each component begins with a letter */
413 for (var i = 0; i < serviceComponents.length; i++)
415 if (strspn(substr(serviceComponents[i], 0, 1), nameFirstLetter) != 1)
417 error.setError(jsonrpc.Constant.ErrorCode.IllegalService,
418 "A service name component does not begin with a letter");
425 * Now replace all dots with slashes so we can locate the service script. We
426 * also retain the split components of the path, as the class name of the
427 * service is the last component of the path.
429 var servicePath = join("/", serviceComponents) + ".esp";
431 /* Load the requested class */
432 if (jsonrpc_include(servicePath))
434 /* Couldn't find the requested service */
435 error.setError(jsonrpc.Constant.ErrorCode.ServiceNotFound,
436 "Service class `" + servicePath + "` does not exist.");
442 * Find the requested method.
444 * What we really want to do here, and could do in any reasonable language,
447 * method = jsonrpc.method[jsonInput.method];
448 * if (method && typeof(method) == "function") ...
450 * The following completely unreasonable sequence of commands is because:
452 * (a) ejs evaluates all OR'ed expressions even if an early one is false, and
453 * bars on the typeof(method) call if method is undefined
455 * (b) ejs does not allow comparing against the string "function"!!! What
456 * the hell is special about that particular string???
458 * E-gad. What a mess.
460 var method = jsonrpc.method[jsonInput.method];
461 var valid = (method != undefined);
464 var type = typeof(method);
465 if (substr(type, 0, 1) != 'f' || substr(type, 1) != "unction")
473 error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
474 "Method `" + method + "` not found.");
479 /* Most errors from here on out will be Application-generated */
480 error.setOrigin(jsonrpc.Constant.ErrorOrigin.Application);
482 /* Call the requested method passing it the provided params */
483 var retval = method(jsonInput.params, error);
485 /* See if the result of the function was actually an error object */
486 var wasError = (retval["__type"] != undefined);
489 wasError = retval.__type == "_JsonRpcError";
493 /* Yup, it was. Return the error */
498 /* Give 'em what they came for! */
499 var ret = new Object();
501 ret.id = jsonInput.id;
502 sendReply(Json.encode(ret), scriptTransportId);