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("json.esp");
18 jsonrpc_include("jsondate.esp");
20 /* bring the string functions into the global frame */
23 /* Bring the system functions into the global frame */
28 print(vsprintf(arguments));
33 * All of our manipulation of JSON RPC methods will be through this object.
34 * Each class of methods will assign to here, and all of the constants will
35 * also be in this object.
37 jsonrpc = new Object();
38 jsonrpc.Constant = new Object();
39 jsonrpc.Constant.ErrorOrigin = new Object(); /* error origins */
40 jsonrpc.Constant.ErrorCode = new Object(); /* server-generated error codes */
41 jsonrpc.method = new Object(); /* methods available in requested class */
45 * ScriptTransport constants
47 jsonrpc.Constant.ScriptTransport = new Object();
48 jsonrpc.Constant.ScriptTransport.NotInUse = -1;
52 * JSON-RPC error origin constants
54 jsonrpc.Constant.ErrorOrigin.Server = 1;
55 jsonrpc.Constant.ErrorOrigin.Application = 2;
56 jsonrpc.Constant.ErrorOrigin.Transport = 3;
57 jsonrpc.Constant.ErrorOrigin.Client = 4;
62 * JSON-RPC server-generated error code constants
66 * Error code, value 0: Unknown Error
68 * The default error code, used only when no specific error code is passed to
69 * the JsonRpcError constructor. This code should generally not be used.
71 jsonrpc.Constant.ErrorCode.Unknown = 0;
74 * Error code, value 1: Illegal Service
76 * The service name contains illegal characters or is otherwise deemed
77 * unacceptable to the JSON-RPC server.
79 jsonrpc.Constant.ErrorCode.IllegalService = 1;
82 * Error code, value 2: Service Not Found
84 * The requested service does not exist at the JSON-RPC server.
86 jsonrpc.Constant.ErrorCode.ServiceNotFound = 2;
89 * Error code, value 3: Class Not Found
91 * If the JSON-RPC server divides service methods into subsets (classes), this
92 * indicates that the specified class was not found. This is slightly more
93 * detailed than "Method Not Found", but that error would always also be legal
94 * (and true) whenever this one is returned. (Not used in this implementation)
96 jsonrpc.Constant.ErrorCode.ClassNotFound = 3; // not used in this implementation
99 * Error code, value 4: Method Not Found
101 * The method specified in the request is not found in the requested service.
103 jsonrpc.Constant.ErrorCode.MethodNotFound = 4;
106 * Error code, value 5: Parameter Mismatch
108 * If a method discovers that the parameters (arguments) provided to it do not
109 * match the requisite types for the method's parameters, it should return
110 * this error code to indicate so to the caller.
112 jsonrpc.Constant.ErrorCode.PaameterMismatch = 5;
115 * Error code, value 6: Permission Denied
117 * A JSON-RPC service provider can require authentication, and that
118 * authentication can be implemented such the method takes authentication
119 * parameters, or such that a method or class of methods requires prior
120 * authentication. If the caller has not properly authenticated to use the
121 * requested method, this error code is returned.
123 jsonrpc.Constant.ErrorCode.PermissionDenied = 6;
126 * Error code, value 7: Unexpected Output
128 * The called method illegally generated output to the browser, which would
129 * have preceeded the JSON-RPC data.
131 jsonrpc.Constant.ErrorCode.UnexpectedOutput = 7;
139 function sendReply(reply, scriptTransportId)
141 /* If not using ScriptTransport... */
142 if (scriptTransportId == jsonrpc.Constant.ScriptTransport.NotInUse)
144 /* ... then just output the reply. */
149 /* Otherwise, we need to add a call to a qooxdoo-specific function */
151 "qx.io.remote.ScriptTransport._requestFinished(" +
152 scriptTransportId + ", " + reply +
159 function _jsonValidRequest(req)
161 if (req == undefined)
166 if (req.id == undefined)
171 if (req.service == undefined)
176 if (req.method == undefined)
181 if (req.params == undefined)
188 jsonrpc.validRequest = _jsonValidRequest;
189 _jsonValidRequest = null;
194 * This class allows service methods to easily provide error information for
195 * return via JSON-RPC.
197 function _JsonRpcError_create(origin, code, message)
199 var o = new Object();
201 o.data = new Object();
202 o.data.origin = origin;
204 o.data.message = message;
205 o.scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
206 o.__type = "_JsonRpcError";
208 function _origin(origin)
210 this.origin = origin;
212 o.setOrigin = _origin;
214 function _setError(code, message)
217 this.message = message;
219 o.setError = _setError;
227 function _setScriptTransportId(id)
229 this.scriptTransportId = id;
231 o.setScriptTransportId = _setScriptTransportId;
237 var ret = new Array(2);
238 ret.error = this.data;
240 sendReply(Json.encode(ret), this.scriptTransportId);
247 jsonrpc.createError = _JsonRpcError_create;
248 _JsonRpcError_create = null;
251 * 'input' is the user-provided json-encoded request
252 * 'jsonInput' is that request, decoded into its object form
255 var jsonInput = null;
257 /* Allocate a generic error object */
258 error = jsonrpc.createError(jsonrpc.Constant.ErrorOrigin.Server,
259 jsonrpc.Constant.ErrorCode.Unknown,
262 /* Assume (default) we're not using ScriptTransport */
263 scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
265 /* What type of request did we receive? */
266 if (request["REQUEST_METHOD"] == "POST" &&
267 request["CONTENT_TYPE"] == "text/json")
269 /* We found literal POSTed json-rpc data (we hope) */
270 input = request["POST_DATA"];
271 jsonInput = Json.decode(input);
273 else if (request["REQUEST_METHOD"] == "GET" &&
274 form["_ScriptTransport_id"] != undefined &&
275 form["_ScriptTransport_data"] != undefined)
277 /* We have what looks like a valid ScriptTransport request */
278 scriptTransportId = form["_ScriptTransport_id"];
279 error.setScriptTransportId(scriptTransportId);
280 input = form["_ScriptTransport_data"];
281 jsonInput = Json.decode(input);
284 /* Ensure that this was a JSON-RPC service request */
285 if (! jsonrpc.validRequest(jsonInput))
288 * This request was not issued with JSON-RPC so echo the error rather than
289 * issuing a JsonRpcError response.
291 write("JSON-RPC request expected; service, method or params missing<br>");
296 * Ok, it looks like JSON-RPC, so we'll return an Error object if we encounter
297 * errors from here on out.
299 error.setId(jsonInput.id);
301 /* Service and method names may contain these characters */
303 "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
305 /* The first letter of service and method names must be a letter */
306 var nameFirstLetter =
307 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
310 * Ensure the method name is kosher. A meethod name should be:
312 * - first character is in [a-zA-Z]
313 * - other characters are in [_a-zA-Z0-9]
316 /* First check for legal characters */
317 if (strspn(jsonInput.method, nameChars) != strlen(jsonInput.method))
319 /* There's some illegal character in the service name */
320 error.setError(JsonRpcError.MethodNotFound,
321 "Illegal character found in method name.");
326 /* Now ensure that it begins with a letter */
327 if (strspn(substr(jsonInput.method, 0, 1), nameFirstLetter) != 1)
329 error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
330 "The method name does not begin with a letter");
336 * Ensure the requested service name is kosher. A service name should be:
338 * - a dot-separated sequences of strings; no adjacent dots
339 * - first character of each string is in [a-zA-Z]
340 * - other characters are in [_a-zA-Z0-9]
343 /* First check for legal characters */
344 if (strspn(jsonInput.service, "." + nameChars) != strlen(jsonInput.service))
346 /* There's some illegal character in the service name */
347 error.setError(JsonRpcError.IllegalService,
348 "Illegal character found in service name.");
354 * Now ensure there are no double dots.
356 * Frustration with ejs. Result must be NULL, but we can't use the ===
357 * operator: strstr() === null so we have to use typeof. If the result isn't
358 * null, then it'll be a number and therefore not type "pointer".
360 if (typeof(strstr(jsonInput.service, "..")) != "pointer")
362 error.setError(JsonRpcError.IllegalService,
363 "Illegal use of two consecutive dots in service name");
368 /* Explode the service name into its dot-separated parts */
369 var serviceComponents = split(".", jsonInput.service);
371 /* Ensure that each component begins with a letter */
372 for (var i = 0; i < serviceComponents.length; i++)
374 if (strspn(substr(serviceComponents[i], 0, 1), nameFirstLetter) != 1)
376 error.setError(jsonrpc.Constant.ErrorCode.IllegalService,
377 "A service name component does not begin with a letter");
384 * Now replace all dots with slashes so we can locate the service script. We
385 * also retain the split components of the path, as the class name of the
386 * service is the last component of the path.
388 var servicePath = join("/", serviceComponents) + ".esp";
390 /* Load the requested class */
391 if (jsonrpc_include(servicePath))
393 /* Couldn't find the requested service */
394 error.setError(jsonrpc.Constant.ErrorCode.ServiceNotFound,
395 "Service class `" + servicePath + "` does not exist.");
401 * Find the requested method.
403 * What we really want to do here, and could do in any reasonable language,
406 * method = jsonrpc.method[jsonInput.method];
407 * if (method && typeof(method) == "function") ...
409 * The following completely unreasonable sequence of commands is because:
411 * (a) ejs evaluates all OR'ed expressions even if an early one is false, and
412 * bars on the typeof(method) call if method is undefined
414 * (b) ejs does not allow comparing against the string "function"!!! What
415 * the hell is special about that particular string???
417 * E-gad. What a mess.
419 var method = jsonrpc.method[jsonInput.method];
420 var valid = (method != undefined);
423 var type = typeof(method);
424 if (substr(type, 0, 1) != 'f' || substr(type, 1) != "unction")
432 error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
433 "Method `" + method + "` not found.");
438 /* Most errors from here on out will be Application-generated */
439 error.setOrigin(jsonrpc.Constant.ErrorOrigin.Application);
441 /* Call the requested method passing it the provided params */
442 var retval = method(jsonInput.params, error);
444 /* See if the result of the function was actually an error object */
445 var wasError = (retval["__type"] != undefined);
448 wasError = retval.__type == "_JsonRpcError";
452 /* Yup, it was. Return the error */
457 /* Give 'em what they came for! */
458 var ret = new Object();
460 ret.id = jsonInput.id;
461 sendReply(Json.encode(ret), scriptTransportId);