In ObjectCloud, server-side Javascript can either be used to add additional methods to an object, or as a general script available through HTTP. Each technique has different uses.
When adding methods to an object, the methods provided by ObjectCloud can optionally be disabled. This allows server-side Javascript to lock down the data in an object and verify all operations in a secure manner. This is the "Classes" technique described below.
The other technique is to use a .ocjs file and call it directly from HTTP, such as through classical AJAX or as the target of a form. .ocjs files can be used with both GET and POST requests. They can return any form of data to the browser, including, but not limited to, JSON, strings, or the results of evaluating a template.
This document assumes familiarity with ObjectCloud's Javascript AJAX API.
ObjectCloud uses a multi-process model where Javascript is run in a separate process from the main server. This is to protect the main ObjectCloud process from infinite loops and memory management issues that can occur in server-side Javascript. In the event of one of these issues, the Javascript process is restarted without impacting the server.
ObjectCloud currently uses the Rhino as a Javascript interpreter, although a long-term goal is to switch to V8. Previous versions of ObjectCloud translated Rhino into a CLR-compliant .dll file, although this approach is no longer used because the multi-process model is faster and more stable.
Performance issues are discussed at the end of this page.
ObjectCloud runs .ocjs files through its server-side Javascript interpreter. The result of the last statement is returned to the browser. For security reasons, a .ocjs file must be owned by a member of the Administrators group; ordinary users are prohibited from creating .ocjs files.
Note: A common mistake is to use a return statement at the end of the script. This will result in an "invalid return" error. Such a habit can be hard to break, especially when one has spent a lot of time writing in-browser Javascript functions, Java, C#, C, ect.
Note: Examples are untested!
The following script returns the string hello world:
"Hello World";
The following script returns the sum of the GET arguments a and b. Note the use of the "Number()" conversion function. This is because all GET arguments are initially strings, and thus Javascript will concatenate the strings by default.
var getArgs = getGet();Number(getArgs.a) + Number(getArgs.b);
The following script passes the POST arguments c, d, and e to a template. It assumes that they were passed using the URLEncoded convention, such as if the script is the target of a POST form.
var postArgs = getPost();evaluateTemplate('mytemplate', {c: postArgs.c, d: postArgs.d, e: postArgs.e});
The following script saves a POSTed string as part of a JSON object in the /storeagearea.json object.
var storagearea = open('/storagearea.json');var stored = JSON.parse(storeagearea.ReadAll());stored.fromuser = getPostContents();storagearea.WriteAll(JSON.stringify(stored));"saved: " + stored.fromuser;
ObjectCloud's full API is available. All functions are documented later in this document.
Server-Side Javascript borrows the "class" concept from Object-Oriented programming. All classes are stored in the /Classes folder. Each class is a text file without an extension that contains Javascript. When creating a class, the server-side code applies to all files with the same extension as the class name.
For example, for a class named foo:
Note: If a folder has a Classes sub-folder, the classes in the sub-folder override any classes in the /Classes folder.
Note: All server-side Javascript files must be owned by a member of the administrators group.
When a class is created, the file's object's underlying methods are no longer accessible to the web, and therefore, no longer callable through AJAX. Instead, the Javascript methods are made accessible to the web and AJAX. The server-side Javascript can, however, can call the underlying methods when storing or retrieving data. Within server-side Javascript, the underlying file's object's methods are placed into the "base" object.
To enable access to underlying web-accessible methods, the server-side JavaScript must set some options, as follows:
var options =
{
BlockWebMethods: false
};
Note: Be careful when setting BlockWebMethods to false. If your server-side JavaScript needs to validate data prior to persisting it, then you could open a potential security hole!
All data must be persisted by calling the underlying base object. Data stored in runtime variables is not saved and will be discarded at some point in the future. All users share the same scope, thus care must be taken to not expose users' private data to other users. Furthermore, ObjectCloud supports concurrent access to server-side Javascript, thus care must be taken to ensure that errors are not introduced due to threading issues.
In some cases, it is possible to improve page responsiveness by caching data in server-side Javascript variables. When doing this, care must be taken to ensure that data is loaded and persisted correctly, as the entire scope, and thus variables in it, can be garbage collected at any time.
Whenever a server-side Javascript class is modified; upon making a call to an object of that class, all in-memory objects will have their scope destroyed. This means that, during development, whenever a server-side Javascript class is modified, all in-RAM variables are lost, and there will be a short pause when using the class for the first time while it is compiled.
Note: Feedback on these constraints is welcome, encouraged, and valued. Previous versions of ObjectCloud gave each user a private and isolated scope, although this approach is no longer taken due to performance implications.
Minimal debugging of server-side Javascript classes is supported. To debug, append ?Method=GetServersideJavascriptErrors when viewing a file of a given class. For example, to debug /Classes/foo, visit /MyFiles/myfoo.foo?Method=GetServersideJavascriptErrors
A rudimentary database program, Whisquil, is provided: /Shell/Editors/Whisquil.wchtml. To view a database, visit /Shell/Editors/Whisquil.wchtml?FileName=[file name]. For example, to view /MyFiles/myfoo.foo, visit /Shell/Editors/Whisquil.wchtml?FileName=/MyFiles/myfoo.foo.
By default, all functions in server-side Javascript are not exposed to the web. This is to prevent unanticipated behavior if malicious parties call functions that aren't intended for the web. How ObjectCloud exposes a Javascript function to the web is controlled by setting values on the function.
For example, from /Classes/ibg, the following values control how the updateNode function is exposed to the web:
updateNode.webCallable = "POST_application_x_www_form_urlencoded";
updateNode.minimumWebPermission = "Write";
updateNode.parser_id = "number";
updateNode.webReturnConvention = "JavaScriptObject";
function updateNode(id, version, contents, changetag)
{
The .webCallable is the only value that is needed to expose a function to the web. The rest of the values give finer control over how ObjectCloud accesses the function. These values are described throughout this section.
All functions that are exposed to the web must have their .webCallable value set. This value tells ObjectCloud what HTTP method is used, and how the arguments are encoded. The following are the valid values for .webCallable:
The GET calling conventions should be used for functions that primarily read, and the POST calling conventions should be used for functions that primarily write. This is due to the HTTP semantics of GET versus POST. Typically, GET requests aren't supposed to change the server's state; except for inconsequential actions like logging.
By default, when a function is exposed to the web, only a person with "Administer" permission to the file object can call the function. The minimum permission can be lowered to "Write" or "Read" by specifying the .minimumWebPermission.
The following values are valid for .minimumWebPermission:
Note: In some circumstances, server-side Javascript can call into other objects. In these circumstances, it can utilize an elevated permission. This is described in the Elevate() section. A server-side Javascript function can declare a lower permission with the .minimumLocalPermission value.
Sometimes "Read", "Write", and "Administer" aren't fine grained enough to effectively control who can access what function. Named Permissions allows for more specific permissions for groupings of functions.
The .namedPermissions value takes a comma-seperated list of valid named permissions for a function. Any user who has a named permission for the file, is a member of a group that has a named permission for a file, or has such a named permission at a higher-level directory with inhert turned on can call the function.
TODO: Setting named permissions is currently undocumented, although it is anticipated that named permissions will require some form of a custom GUI in order to set.
Sometimes, ObjectCloud needs a hint about what kind of data the function expects in an argument. By default, ObjectCloud will treat all arguments as strings. Although Javascript's weak typing system can automatically translate some strings into numbers or booleans, this behavior occasionally introduces non-deterministic behavior and unintended side effects. The .parser_* values are used in situations where a number, boolean, or JSON-encoded object is expected. Valid values are:
For example:
myfunc.webCallable = "POST_application_x_www_form_urlencoded";
myfunc.parser_argAsNum = "number";
myfunc.parser_argAsBool = "bool";
myfunc.parser_argAsJSON = "JSON";
function myfunc(argAsNum, argAsBool, argAsJSON)
{
The .webReturnConvention is an optional field that describes how the client will handle the returned data. ObjectCloud uses .webReturnConvention to help Javascript that runs in the browser parse the results of the server-side call. Valid values are:
JSON and JavaScriptObject allow a server-side function to return an object that is transparently serialized and then de-serialized for browser-side Javascript. There is a speed / security tradeoff, described below.
The eventual purpose of server-side Javascript is to save data on the server. All of the underlying object's functions are placed within the base object.
For example, /Classes/ibg turns a name-value pairs file object with a .ibg extension into a linked list. The function updateNode updates the contents of a node in the linked list. Prior to updating the node, it loads the node and verifies that the caller has the correct version:
// updateNode
updateNode.webCallable = "POST_application_x_www_form_urlencoded";
updateNode.minimumWebPermission = "Write";
updateNode.parser_id = "number";
updateNode.webReturnConvention = "JavaScriptObject";
function updateNode(id, version, contents, changetag)
{
var nodeAsString;
elevate(function()
{
nodeAsString = base.Get_Sync( { Name: id });
});
var node = eval('(' + nodeAsString + ')');
throwExceptionOnForbiddenChange(node, contents);
if (node.v != version)
throwWebResultOverrideException(400, "Wrong version");
if (node.c != contents)
{
node.c = contents;
node.v = updateVersionNumber(node.v);
node.t = changetag;
nodeAsString = JSON.stringify(node);
elevate(function()
{
base.Set_Sync( { Name: id, Value: nodeAsString });
});
}
return node;
}
In the above example, the base.Get_Sync() and base.Set_Sync() functions call into the underlying name-value pairs object to persist the node's contents. Calls to base must be made within the context of an elevate() call, documented later.
Every effort is made to keep the Javascript API as similar as possible between Javascript that runs in the browser and Javascript that runs on the server. At this time, however, in-browser Javascript should use the asynchronous calling convention, and server-side Javascript should use the synchronous convention.
The following metadata is available for use at runtime:
Server-side Javascript provides some additional methods to assist in returning more detailed data to the browser and for working with ObjectCloud
The generateWebResult function allows closer control over the status returned to the browser. The first argument, status, takes a number that is the status code returned to the browser. The second argument, which is optional, is the message returned to the client. ObjectCloud treats the message argument as a string.
Note: Feedback on how to handle the message is welcome and appreciated.
The throwWebResultOverrideExcetion function throws a special kind of exception. Assuming that this exception isn't caught in the server-side Javascript, when it is thrown into ObjectCloud, it will abort all calls and return the result directly to the browser. This is useful from within elevate() or callAsOwner().
Redirects the user to the given URL. This will work when it's the last line in a .ocjs file. In a function, redirect's result must be returned.
The /API/json2.js is automatically loaded to provide secure JSON functionality. /API/Prototype.js isn't used in server-side Javascript because it only runs within a browser.
The parse function is a secure way of parsing a JSON object encoded as a string. When handling untrusted data, the parse function will not allow a hacker to inject malicious Javascript.
Note: eval() is much faster then calling parse(). When working with trusted JSON strings, eval() is recommended.
Stringify takes a Javascript object and returns it encoded as a JSON string.
Locks the object and calls the function. When the function is called, it has exclusive access to the underlying object.
Calls the function with an elevated security context. When calling into other objects, the "minimumLocalPermission" instead of "minimumWebPermission" will be used. See the later section, "Calling into other Objects," to learn how to call into other objects.
elevate must be used to call into the base object.
Note: Validate all input before calling this function, as a malicious user could gain access to sensitive data or corrupt sensitive data.
Calls the function as if the object's owner is calling it.
Note: Validate all input before calling this function, as a malicious user could gain access to sensitive data or corrupt sensitive data.
Sanitizes the passed in html. Removes potential cross-site scripting attacks and unclosed tags. This should be called on all HTML that is displayed to the user.
Returns an object with metadata about the current connection, including the current user and IP. Returns an object with the following properties:
Note: For the best CPU utilization, try to only call this function once within a server-side Javascript function. Repeatedly calling this function can lead to high CPU utilization and slower performance.
var foo = getGet().Foo;
Returns all of the current POST parameters as a JavaScript object. In order for this function to work, the current web request must have passed urlencoded POST parameters, such as when a form uses POST. In the event that there are no POST parameters, null will be returned.
var foo = getPost().Foo;
Returns the POSTed data as a string. In the event that this isn't a POST request, null is returned.
var request = JSON.parse(getPostContents());
Returns all of the current headers as a JavaScript object.
var foo = getHeaders().Foo;
Note: This function is untested
Returns all of the current cookies from the browser parameters as a JavaScript object.
var foo = getCookies().Foo;
Note: This function is untested
Sets the cookie in the browser. Currently, there is no way to set the expiration, path, or secure setting.
Note: This function is untested
Loads the given Javascript file if it hasn't already been loaded. Returns any results generated from executing the given Javascript file. The first time this is called, the results are cached and re-returned. The user must have read permission to the file, unless this is called from within the context of callAsOwner, in which case the owner must have read permission to the file.
If the javascript file can not be loaded, no exception occurs. False is returned.
Returns a wrapper that allows accessing the given object. This is similar to using the // Scripts: syntax, except that it's supported at runtime.
If the javascript file can not be loaded, no exception occurs. False is returned.
For more information on using an opened object, see ObjectCloud's API documentation.
Returns a modified version of the given URL that instructs the browser to cache the returned object for a very long time.
This is a powerful function for improving performance and reducing load on a server. How it works is that it calculates the MD5 of the result of the given URL, and then appends a "BrowserCache=..." parameter to the url. When the server sees a request with BrowserCache in the url, it instructs the browser to cache the result for a very long time. This allows a page to display images, reference css, ect, without requiring the browser to download the content every time it is displayed.
In the event that the content at the given url changes, the MD5 will change, and thus force the browser to reload it.
Future versions of ObjectCloud may change algorithms to reduce CPU load.
Returns the result of calling the given url on the server.
Evaluates the specified template. If the arguments argument is included, each named value is passed into the template. For example, the following evaluates the template /mytemplate.oc with the arguments a, b, and c:
evaluateTemplate('/mytemplate.oc', {a: 'aaaa', b: 'bbb', c: 'cc'});
Server-side Javascript uses the same // Scripts: syntax that client-side Javascript uses. This allows another object to be loaded. For example, from /Tests/Classes:
// Scripts: /System/Proxy?Method=GetServersideJavascriptWrapper&assignToVariable=Proxy
The method GetServersideJavascriptWrapper returns an object that grants access to the /System/Proxy object by creating the "Proxy" object in the server-side Javascript. An example of accessing the "Proxy" object is:
TestElevateProxyGet.webCallable = "GET";
function TestElevateProxyGet()
{
return elevate(function()
{
return Proxy.GET("http://slashdot.org", {}).Content;
});
}
In the above example, the Proxy object is used to make a GET request to http://slashdot.org. The /System/Proxy requires elevated security in order to call.
In general, server-side Javascript is compiled and runs rather quickly. There are some constraints which are best understood by briefly describing ObjectCloud's Javascript execution environment.
When ObjectCloud starts, it compiles all classes stored in the global /Classes folder. This takes a second or so, and is generally fast on a modern multi-core computer. In the event that server-side Javascript takes more then 10-15 seconds to return, ObjectCloud kills the process. Every time server-side Javascript calls the ObjectCloud API, the counter is reset. Therefore, long-running CPU-intense server-side Javascript, such as calculating fibonacci sequences, primes, brute-force encryption key breaking, ect should be avoided. As of this writing, it is possible to be stuck in an unstoppable infinite loop that repeatedly calls an ObjectCloud API function.
Another performance constraint has to do with how ObjectCloud manages memory. It's impractical to hold all objects in RAM. ObjectCloud utilizes a first-in, last-out algorithm for memory reclamation. This means that less-used objects will have their Javascript scopes destroyed when they are garbage collected. Furthermore, construction of object's scopes happens on-demand. Thus, using an object that isn't in RAM incurs about a 1/10th of a second delay. As a result, pages and applications that use objects that are most likely to be in RAM will perform faster then pages and applications that use objects that are unlikely to be in RAM. Specifically, applications and pages that primarily consume new user-created data will be fast and instantaneous; but applications and pages that use old data will be slower and require more RAM.