This article will summarize the main features of JSS:
For information about the API available to JSS developers please refer to the JSS API Guide.
A basic HTTP request may handled simply by calling the write() method of the response object, as follows:
response.write("<body><html>Hello world</html></body>");
which will respond with an HTML page displaying:
Hello world
JSS is ECMAScript 3 compliant, so nearly all Javascript syntax in common use is supported, for example:
function _getMessage(i) {
return "Hello world " + i;
}
var html = "<html><body>";
for (var i=0; i<3; i++)
html += _getMessage(i) + "<br>";
html += "</body></html>";
response.write(html);
Here a function is called when building the HTML response. The underscore is prepended to the function to indicate that it's not to be called via RPC. This script will output:
Hello world 0 Hello world 1 Hello world 2
User-submitted form data and URL arguments may be accessed via the request variable. For example, if a form with a text-box called firstName is submitted, then the following may be used to access the value of this field:
var firstName = request.form.firstName;
response.write("<body><html>Hello " + firstName + "</html></body>");
which, if the value of the firstName field is Jack, will return:
Hello Jack
JSS supports templates, which facilitates the separation of Javascript and HTML code into separate files.
For example, if a template file called hello1.html contains the following:
<html>
<body>
<form method=post>
Name: <input type=text name=firstName value={{firstName}}>
<input type=submit>
</form>
<br>
{{message}}
</body>
</html>
and a JSS file called hello1.jss contains:
var data = {};
if (request.form.firstName != undefined) {
data.firstName = request.form.firstName;
data.message = "Hello " + request.form.firstName;
} else {
data.firstName = "";
data.message = "Please enter your name";
}
response.writeUsingTemplateFile("hello1.html", data);
then a request to hello1.jss will return:
Name: Please enter your name
Notice that the tag {{message}} in hello1.html was substituted by the field with the same name, message, of the Javascript object that was passed to the writeUsingTemplateFile(file, data) method.
Once the field has been filled and the button clicked, the following will be returned:
Name:
Hello world
If a template is to be provided as a string rather than a file then the writeUsingTemplateString(template, data) may be used.
Clients may invoke server-side Javascript functions simply by calling them (or, to be precise, their automatically generated proxies). For example, say the server has a JSS file called hello2.jss with the following contents:
function hello(name) {
return "Hello " + name + " from the server";
}
This is Javascript code that will be run on the server.
Now, say we have an HTML file called hello2.html with the following contents:
<html>
<body>
<script src="hello2.jss"></script>
<script>
var message = hello("world");
document.write(message);
</script>
</body>
</html>
When the HTML is rendered in the browser, the result will be:
Hello world from the server
This message is generated on the server by the Javascript function and returned by the call to the hello() function. Notice that all code, client-side and server-side, is Javascript and that no AJAX code was written. This is possible because the request to hello2.jss returns the Javascript code required to remotely invoke the hello() function in hello2.jss.
The Javascript code that's returned by the request to hello2.jss is compressed for efficiency. The uncompressed code may be viewed by a request to hello2.jss?proxy=uncompressed. It contains the auto-generated proxy-code that the browser needs to call the server function. Note that the JSON-RPC protocol is used (see JSS Remote Procedure Calls (RPC)), so there's no problem modifying the auto-generated proxy-code, or even completely replacing it with, for example, jQuery Ajax code.
Proxies that wrap the functions in a class may be generated by adding a class argument. For example, hello2.jss?class=Hello will wrap the function in a class called Hello such that the hello method may be called as follows:
var helloObject = new Hello(); helloObject.hello();
Proxies that use promises may be generated by adding a proxy argument. For example, hello2.jss?proxy will allow methods to be called as follows:
hello().then(
function(message) {
document.write(message);
},
function(err) {
document.write(err.message);
}
);
If a JSS file that contains RPC functions is also to handle basic HTTP requests then the request-handling code should be placed inside a function called processRequest(). This function's job is to write the HTTP response that will be returned to the client. For example, the following JSS script, hello3.jss, returns the HTML to the client, which then invokes a function by RPC.
function hello(name) {
return "Hello " + name + " from the server";
}
function processRequest() {
var html = "<html><body>\n";
html += "<script src="hello3.jss?proxy></script>\n";
html += "<script>\n";
html += " var message = hello('world');\n";
html += " document.write(message);\n";
html += "</script>\n";
html += "</body></html>";
response.write(html);
}
Note that the Javascript code that's inside the quotes in the processRequest() function will be run on the client, and will call the code that's in the hello() function, which will be run on the server.
It is important to note that this server-side code is called in two different contexts. When hello3.jss is first called by the browser (without parameters), the default processRequest() is called to write the response. But the script referred to in the HTML generated by processRequest() is hello3.jss?proxy. The "proxy" parameter indicates that rather than calling the default processRequest(), the caller wants the RPC proxies generated and returned to the browser so that the hello function can be called via RPC.
Both synchronous and asynchronous remote procedure calls are supported. If an additional argument is provided then it's assumed that the function is to be called asynchronously and that the additional argument is a callback function. For example, in the above example, the hello() function would be called asynchronously like this:
function onHello(message) {
document.write(message);
}
hello("world", onHello);
If a second additional argument is provided, it will be assume to be an error-callback function, which will be called if an error occurs. For example,
function onHelloSuccess(message) {
document.write(message);
}
function onHelloError(error) {
document.write("Error: " + error);
}
hello("world", onHelloSuccess, onHelloError);
If no error-callback is provided then a default error-handling function will be used.
Although JSON-RPC is used by default, calls may optionally be made using form-based RPC (see JSS Remote Procedure Calls (RPC)). This may be done by including a flag after the error-handler, e.g.
hello("world", onHelloSuccess, onHelloError, false);
This tells the proxy-function to use forms instead of JSON-RPC.
JSS makes it easy to upload and download files. If the desired response to a request is to return a file from the virtual file-system then this may be achieved by setting the response.downloadFile property to the path of the file to be downloaded.
If a request contains one or more files to be uploaded (usually via the input type=file element in a form) then CompleteFTP will look for a Javascript function called getUploadPath(fileName, contentType) and call it to find out where (in the virtual file-system) the file is to be uploaded. If no such function is found then the file will be uploaded to the same directory as the script, if possible. If the name of this function is not suitable, an alternative function may be specified by preceding the input type=file element by another element whose name is getUploadPath and whose value is the name of the JSS function to be called.
If an error occurs during upload then a function with the name errorHandler(errorMessage, errorType) will be called, if available. Again, an alternative name may be provided by including an element with the name errorHandler and with the name of the desired error-handling function as its value.
Other JSS files may be included via the include(scriptPath) function. This function evaluates the contents of the included JSS file in the global scope, regardless of what scope it's call from, so any functions and variables declared in the file will have global scope.
When developing a complex JSS application it's important to manage the accessibility of the API. All functions in the global namespace will be available via RPC. If this is not desired then the publish function may be used. For example, if in our hello example a second function was needed but that function wasn't itself to be accessible via RPC, then the following code would be used:
(function() {
function getSource() {
return "server";
}
function hello(name) {
return "Hello " + name + " from the " + getSource();
}
publish("hello", hello);
})();
Since all functions are declared inside a function-block there are no functions in the global namespace until the call to the publish function pushes the hello function into it. Note that the name of the function and the function itself must be provided separately.
Public APIs are vulnerable to denial of service attacks so by default CompleteFTP restricts the number of invocations to 100 per minute. This may be changed by providing an additional argument to the publish function, as follows:
publish("hello", hello, { maxCallsPerMinute: 10 });
Since one JSS file may be included in another JSS file using the include function, it's sometimes desirable to allow another JSS file to invoke a function without also exposing it publically. This may be done by providing a serverOnly argument, as follows:
publish("getSource", getSource, { serverOnly: true });
Functions that are published as serverOnly will not be included in proxies and may only be invoked by other JSS scripts.
JSS can call .NET methods that are defined in a JSS extensions - see here.
Details on the API available in JSS are in JSS API Guide. Information on the RPC protocol used in JSS may be found in JSS Remote Procedure Calls (RPC).