In ESXX, the difference between a web service (a program that produces XML or JSON intended for other programs) and a web application or browser service (a program that produces HTML documents intended to be viewed by a human using a web browser) is minimal. Basically, it's only the data format (XML/JSON vs HTML) that differs.
It's a good idea to remember this when you build a web application. The response from your request handlers
should be raw XML objects that includes all information that is unique for this particular web page or form, but nothing more. Layout, common headers and footers, static sidebars … everything that is common to all pages should be added by the XSLT stylesheet and/or external CSS stylesheets.
(Similarly, if you use filters to plug in a JavaScript template engine instead of using ESXX' built-in XSLT engine, your handlers should return plain JavaScript objects, containing the information that is unique for the page in question.)
ESXX provides two tools that are useful for building web applications:
stylesheet handlers and
request filters. Both can be used when building web services as well. Especially filters are useful for both kind of services.
Stylesheet handlersAs mentioned before, stylesheet handlers can be triggered by both HTTP and SOAP handler responses. When such a handler returns XML, the registered stylesheet handlers are searched based on the response's content type and the part of the URI that follows the
.esxx
file.
The handler specifies an XSLT 2.0 stylesheet that will, just like the application itself, be compiled and cached in memory until it times out or the file is modified. The stylesheet will then be applied to the response on every request. The
params property of the
ESXX.Response
object can be used to set stylesheet parameters (
response.params.mode refers to the
<xsl:param name="'mode'/">
XSLT parameter).
If you're used to thinking in
Model/View/Controller terms, the matched stylesheet is a collection of views and the name of the root element in the data returned by the request handler (the controller) is the name of the view to apply.
XSLT 2.0 is much more advanced and useful than XSLT 1.0, which is currently implemented by the browsers, but should the need arise, it's possible to call any JavaScript function from an XPath context using
javascript:
URIs.
For instance, the following JavaScript function, defined by your web application
function MyClass() {}
function MyClass.protoype.getCurrentDate() {
let now = new Date();
return <currentDate>
<day>{now.getDate()}</day>
<month>{now.getMonth() + 1}</month>
<year>{now.getFullYear()}</year>
</currentDate>;
}
var myObject = new MyClass();
can be used by the XSLT stylesheet like this:
The current year is <xsl:value-of my="javascript:myObject" select="my:getCurrentDate()/year">.
Global functions can be called by leaving out the object from the URI:
Today is <xsl:value-of my="javascript:" select="my:Date()">.
It's also possible to call
class or
instance Java methods:
The current year is <xsl:value-of my="java:java.util.Date" select="my:getYear(my:new()) + 1900">.
Request filtersRequest filters differ from request handlers in that more than one filter may be invoked for a single request. They are defined as follows:
<esxx xmlns="http://esxx.org/1.0/">
<handlers>
…
</handlers>
<filters>
<filter method={http-method}
uri={path-info}
handler={object-and-method} />
<filter … />
…
</filters>
</esxx>
For each request, a list of matching filters are built and the first filter in the list is invoked with two parameters: the request object and a function that calls the next filter in the list, or, if the current filter is the last, a function that calls the request handler.
Each filter is supposed invoke the
next function and return its value. This way, the request handler will eventually be invoked and its response will propagate back to the client via the filter handlers. By simply not calling the
next function, a filter can abort the request and return its own response.
(The
next function optionally takes a single argument, which will be passed as the
req parameter to the next filter or handler. If unspecified, the current request object is passed. This way, it's possible to completely replace the request object.)
Here are a few filter handler examples:
function noOpFilter(req, next) {
return next();
}
function forbiddenFilter(req, next) {
// Abort request by not calling next() if cookie not set
if (req.cookies.secret != "Open Sesame") {
return [ESXX.Response.FORBIDDEN, {}, "Access denied"];
}
else {
return next();
}
}
function postFilter(req, next) {
let res = next();
// Set XSLT params
res.params.mode="silver";
// Add HTTP response header
res.headers["Cache-Control"] = "max-age=1800, public";
return res;
}