Implementing Resources
Prudence offers two options for implementing resources in which the content dynamically changes: "manual" resources are "raw," giving you complete low-level control over the behavior and format of the encapsulated resource, while "template" resources are simplified and highly optimized for cached textual resources, such as HTML web pages. Prudence also provides comprehensive support for static (unchanging) resources, just like conventional web servers.
Programmable Resources
These notes apply to both manual and template resources:
- Resources can be implemented using any of the supported programming languages.
- Uncaught exceptions in any entry point or scriptlet will result in the conversation ending and a 500 ("internal server error") HTTP status code returned to the user. To help you debug these errors, turn on debug mode, which will enable a detailed debug representation. Otherwise, you can set up a custom error page, or, in the last resort, a simple error page will be shown, that at least notifies the user that something went wrong.
- Your source code is parsed/compiled on-the-fly only when necessary. A reparsing/recompilation is triggered when the file is modified, or when any of its dependent files are modified. To keep track of dependencies at any distance, Prudence internally maintains a dependency tree for each document. Note that checking for file modification dates involves an operating system API call: the call is usually cached by the OS and very fast, however you can configure the minimum time between validity checks if necessary.
- If you've captured a URI template, you can access the captured parts of the template via conversation.locals or conversation.wildcard.
Manual Resources
These are implemented as a set of encapsulated entry points in any of the supported programming languages. The entry points all receive the current conversation API namespace as their only argument.
Prudence does the encapsulation for you: it treats the entry points as together belonging to a single logical RESTful resource, and ensures that the same conversation namespace is used for every user request.
There are two ways to define this encapsulation, depending on which routing paradigm you're using:
- Resource mapping: Here, the entry points are in the global scope, probably defined within the file, though they can be imported via a library or created programmatically (as closures, for example). What an "entry point" means exactly may vary per programming language: it's usually a function, method, closure, etc. See the programming guide for examples in all supported languages.
- Resource dispatching: The default dispatchers attempt an object-oriented encapsulation, which again varies per programming language. The dispatch ID defines an object instance, which must in turn implement the entry points.
The implementation is in essence the same for both styles. For simplicity, our examples below will be tuned to resource mapping.
Configuration
Add support for manual resources using the "manual" route type in your routing.js, mapping it to a URI template ending in a wildcard:
app.routes = { '/*': 'manual' }
The default configuration for "manual" will map all files from your application's "/resources/" subdirectory with the ".m." pre-extension. Here is the above configuration with all the defaults fleshed out:
app.routes = { '/*': { type: 'manual', root: 'resources', passThroughs: [], preExtension: 'm', trailingSlashRequired: true, internalUri: '/_manual/', clientCachingMode: 'conditional', maxClientCachingDuration: -1, compress: true } }
General configuration for your code is done in setting.js.
handleInit
This is the only required entry point. It is called once for every user request, and always before any of the other entry points.
The main work is to initialize supported media types via the conversation.addMediaType APIs, in order of preference. For example:
function handleInit(conversation) { conversation.addMediaTypeByName('application/json') conversation.addMediaTypeByName('text/plain') }
Note that you can also add language information:
function handleInit(conversation) { conversation.addMediaTypeByNameWithLanguage('text/plain', 'en') conversation.addMediaTypeByNameWithLanguage('text/plain', 'fr') }
Prudence will use these values for content negotiation, choosing the best media type and language according to list of acceptable and preferred formats sent by the client and this list.
handleInit is also where you should set up caching, if you're using it:
function handleInit(conversation) { conversation.addMediaTypeByName('application/xml') caching.duration = '5s' caching.tags.add('blog') }
You might wonder why we add these supported media types via API calls for each request, since they are usually always the same for a resource. Why not simply configure them into the resource permanently?
The reason is that they should not always permanent. In handleInit, you can check for various conditions of the conversation, or even external to the conversation, to decide which media types and languages to support. For example, you might not want to support XHTML for old browsers, but you'd want it at the top of the list for new browsers. Or, you might not be able to support PDF in case an external conversion service is down. In which case, you won't want it on the list at all, and instead want content negotiation to choose a different format that the client supports, such as DVI.
So, building your content negotiation table via API gives you a lot flexibility, at no real expense: these API calls are very lightweight.
Note that handleInit is called even if your resource is cached (on the server), exactly because you need to set up content negotiation before casting the cache key template.
handleGet
Handles HTTP "GET" requests.
In a conventional resource-oriented architecture, clients will not be expecting the resource to be altered in any way by a GET operation.
What you'll usually do here is construct a representation of the resource, possibly according to specific parameters of the request, and then return this representation to the client, possibly with directions for client-side caching.
There are many kinds of payloads you can return to the client: they are discussed in depth in the web data chapter. However, here's a reference of the supported return types:
- Numbers: Returns the number as an HTTP status code to the client. If you you wish to set your own return representation, too, you can use conversation.setResponseText or conversation.setResponseBinary. Note that if the status code is an error, then the error capturing mechanism may override your response. If you wish to bypass this mechanism, set conversation.statusPassthrough to true.
- Arrays of bytes: Used for returning binary representations. Note that some languages (JavaScript, for example) have their own implementations of arrays, which are not compatible with JVM arrays. In such cases, you have to make sure to return JVM arrays. Internally, Prudence represents these values with a ByteArrayRepresentation.
- Representation instances: You can construct and return a directly.
- Other return values: If the conversation.mediaType is "application/internal" then the value will be wrapped in an InternalRepresentation. Otherwise, it will be converted into a string if it isn't a string already, and returned to the client as a StringRepresentation.
If your resource has been cached (on the server) then handleGet will not be called. The cache entry will be returned to the client instead.
It's possible to return template resources as your payload. This could be useful if you're implementing the Model-View-Controller (MVC) pattern. See the MVC chapter for complete examples.
handlePost
Handles HTTP "POST" requests.
In a conventional resource-oriented architecture, POST is the "update" operation (well, not exactly: see note below). Clients will expect the resource to already exist for the POST operation to succeed. That is, a call to GET before the POST may succeed. Clients expect you to return a modified representation, in the selected media type, if the POST succeeded. Subsequent GET operations would then return the same modified representation. A failed POST should not alter the resource: only a success status code should indicate a change. That means that ideally you should roll back changes if the entire operation fails along the way.
Note that the entity sent by the client does not have to be identical in format or content to what you return. In fact, it's likely that the client will send smaller delta updates in a POST, rather than a comprehensive representation.
What you'll usually do here is alter existing data according to data sent by the client.
The most important thing to realize is that POST is the only HTTP operation that is not "idempotent," which means that multiple identical POST operations on a resource may yield results that are different from that of a single POST operation. This is why web browsers warn you if you try to refresh or go back to a web page that is the result of a POST operation. As such, POST is the correct operation to use for manipulations of a resource that cannot be repeated. So, if you're thinking in terms of CRUD, POST can mean either "update" or "create": it depends on whether or not "create" is a repeatable operation in your specific data semantics. See this blog post by John Calcote for one explanation.
Return value behavior is identical to that in handleGet. In fact, you may want handlePost to share the same code path as handleGet for creating the representation.
Note that the default return status for successful operations, 200 ("ok"), is indeed OK. However, it is better to return a 201 ("created") status if indeed the resource was created, and also return the full representation. If you cannot handle the operation at the moment, you should return a 202 ("accepted") status, signifying that the operation has been queued for later.
handlePut
Handles HTTP "PUT" requests.
In a conventional resource-oriented architecture, PUT is the "create" operation (well, not exactly: see note below). Clients will expect whatever current data exists in the resource to be discarded, and for you to return a representation of the new resource in the selected media type. A failed PUT should not alter the resource.
Note that the entity sent by the client does not have to be identical in format or content to what you return.
What you'll usually do here is parse and store the data sent by the client, overwriting data if it already exists.
PUT, like most HTTP operations, is "idempotent," which means that multiple identical PUT operations on a resource are expected to yield the same result as a single PUT operation. PUT should thus overwrite any existing data. If you are implementing a "create" operation that cannot be repeated, then you should use POST instead. See note in POST.
See handleGet for supported return types. In fact, you may want handlePut to share the same code path as handleGet for creating the representation.
Note that the default return status for successful operations, 200 ("ok"), is indeed OK. However, it is better to return a 201 ("created") status if indeed the resource was created, and also return the full representation. If you cannot handle the operation at the moment, you should return a 202 ("accepted") status, signifying that the operation has been queued for later.
handleDelete
Handles HTTP "DELETE" requests.
In a conventional resource-oriented architecture, clients expect subsequent GET operations to fail with a 404 ("not found") code. A DELETE should fail with 404 if the resource is not already there; it should not silently succeed. A failed DELETE should not alter the resource.
What you'll usually do here is make sure the identified resource exists, and if it does, remove or mark it somehow as deleted
The following return types are supported:
- Null: Signifies success.
- Number: Returns the number as an HTTP status code to the client, with no other content: for example, returning 404 means "not found." Note that error capturing can let you take over and return an appropriate error page to the client.
Though some languages return null if no explicit return statement is used, others return the value of the last executed operation, which could be a number, which would in turn become an HTTP status code for the client. This can lead to some very bizarre bugs, as clients receive apparently random status codes! It's thus good practice to always explicitly return null in handleDelete, if only to add clarity to your code's intent.
If you cannot handle the operation at the moment, you should return a 202 ("accepted") status, signifying that the operation has been queued for later.
handleGetInfo
Handles HTTP "GET" requests before handleGet.
This entry point, if it exists, is called before handleGet in order to provide Prudence with information required for conditional HTTP requests. Only if conditions are not met—for example if our resource is newer than the version the client has cached, or the tag has changed—does Prudence continue to handleGet. Using handleGetInfo can thus improve on the gains of conditional requests: not only are you saving bandwidth, but you are also avoiding a potentially costly handleGet call. Note that if the client is not doing a conditional request, then handleGetInfo will not be called.
The use of handleGetInfo discussed in detail in the caching chapter. However, for the sake of completion, here's a reference of the supported return types:
- Null: Means that you wish to continue directly to handleGet.
- Numbers or JVM Date instances: Considered as Unix timestamps, and converted into the modification date.
- Strings or Tag instances: Considered as HTTP tags.
- RepresentationInfo instances: Returned as is.
Note that even though you can only return the modification date or the tag, it is possible set both together by returning one and setting the other via the APIs.
If you implement handleGetInfo, you should be returning the same conditional information in your handleGet implementation, so that the client would know how to tag the data. The return value from handleGetInfo does not, in fact, ever get to the client: it is only used internally by Prudence to process conditional requests.
Template Resources
Though especially suitable for web pages (HTML), template resources can be used for any kind of textual asset. They are indeed intended and optimized for arbitrary textual formats: HTML, XML, plain text, etc. The design goal is to make it as easy as possible to generate the text dynamically, by allowing developers and designers to mix static elements with dynamic elements into a single file.
Using the default "scriptlets" parser, this means that the file is static text optionally embedded with delimited programming source code—these are the scriptlets. There are a few built-in shortcut scriptlets for common tasks, and it's also easy to write plugins to implement your own custom shortcuts.
The "scriptlets" parser is intended to be as straightforward and flexible as possible, mimicking the familiar paradigm of PHP/ASP/JSP, which allows for raw code that is executed in-place. However, it's possible to replace this parser with your own should you desire. For example, you might prefer declarative rather than procedural templates (mimicking the structure of HTML/XML), and rather than have opaquely delimited areas, you might prefer structural parsing of XML attributes. The parser system is pluggable and easy to extend.
Whatever parser you use, it will compile the whole file into one or more "programs" that are executed in sequence. Thus, unlike manual resources, template resources have no "entry points," and work in quite a different execution environment, and indeed each "program" can be in a different programming language, allowing for mixed-language templates.
Configuration
Add support for template resources using the "templates" route type in your routing.js, mapping it to a URI template ending in a wildcard:
app.routes = { '/*': 'templates' }
The default configuration for "templates" will map all files from your application's "/resources/" subdirectory with the ".t." pre-extension. Here is the above configuration with all the defaults fleshed out:
app.routes = { '/*': { type: 'templates', root: 'resources', includeRoot: ['libraries', 'includes'], passThroughs: [], preExtension: 't', trailingSlashRequired: true, defaultDocumentName: 'index', defaultExtension: 'html', clientCachingMode: 'conditional', maxClientCachingDuration: -1, compress: true } }
General configuration for templates is in setting.js, as are the general configuration for code.
MIME Types and Compression
Prudence will handle HTTP content negotiation for your template resources, and will assume a single MIME type per resource. That MIME type is determined by the filename extension. For example, a resource named "profile.t.html" will have the "text/html" MIME type.
Prudence recognizes many common file types by default, but you can add your own mappings in you application's settings.js, using app.settings.mediaTypes.
When "compress" is true, Prudence will negotiate the best compression format (gzip and zip are supported) and compress on the fly. Compression can be configured in settings.js.
Scriptlets
By default, you can use either "<%…%>" (ASP/JSP-style) or "<?…?>" (PHP-style) scriptlet delimiters in your templates. Note that you can only use one or the other, though, in the same template.
The entire template is turned into a single program (or several programs if you are mixing languages: more on that below): the code between the delimiters is output as-is to the program, while the rest will be printed out. For example, the following template:
<% for (var x = 0; x < 10; x++) { %> <p>This is a "line"</p> <% } %>
Will become the following program behind the scenes:
for (var x = 0; x < 10; x++) { print("<p>This is a \"line\"</p>") }
You can see the programs generated behind the scenes by enabling debug mode.
It's important to emphasize that any code can be used in templates: you can import libraries, define functions and classes, etc. It's up to you to decide how much and what kind of programming logic to use in templates. There are indeed strict coding disciplines, such as MVC, that forbid anything but visual logic in template. See the MVC chapter for a complete discussion.
You can specify the language of the scriptlet in its opening delimiter:
<%ruby print 1 + 2 %>
Full language names are supported, as above, as well as shortcuts that are usually the common filename extensions for that language: "rb" for Ruby, "py" for Python, "js" for JavaScript, etc.
If the language of a scriptlet is not specified, it will default to the language of the previous scriptlet, so that you only need to specify the language if you are changing languages. If the first scriptlet does not specify a language, it will use the "defaultLanguageTag" setting. The default for that is JavaScript.
Built-In Shortcuts
Shortcuts use a special marker after the scriptlet's opening delimiter.
To output an expression:
<%= x*2 %>
The above is equivalent to:
<% print(x*2) %>
To include another template:
<%& '/header/' %>
The above is equivalent to:
<% document.include('/header/'); %>
A shortcut to print out conversation.base:
<%.%>
Scriptlet comments can be used for human-readable explanations, or to temporarily disable scriptlets:
<%# This scriptlet will be ignored by the parser. %>
All the above shortcuts work for all supported programming languages, generating source code in that language. To combine a language specification with a shortcut symbol, put the shortcut symbol first:
<%=rb x*2 %>
A few other built-in shortcuts are introduced under inheritance, and it's also possible create your own shortcuts.
Fragments
You can compose your templates out of reusable "fragments" by placing them in the "/libraries/includes/" directory, and then including them in other templates using the the "<%&…%>" shortcut (or the document.include API directly). For example, we can create this fragment in "/libraries/includes/lists/simple.t.html":
<li><%= line %></li>
And then use it like so:
<% for (int l in lines) { var line = lines[l] %> <%& '/lists/simple/' %> <% } %>
Fragments don't have to include scriptlets: they can be purely textual. On the other hand, they can contain sophisticated code to generate their HTML. Fragments can also be nested to any depth: a fragment can include others, those can include others, and so on.
Fragments in Prudence are not merely "server-side includes": in fact, each fragment is cached separately, with its own cache key and cache duration. This allows you to create sophisticated and extremely efficient fine-grained caching strategies. See the caching chapter.
To share fragments with all applications, put them in your container's "/libraries/prudence-includes/" directory. Files in your application's "/libraries/includes/" will always override the shared versions.
Blocks and Inheritance
This feature is very similar to fragments, but can be understood as its inverse: rather than inserting a fragment into the current location using "<%&…%>", you define blocks that will only later be inserted into a template. Unlike fragments, blocks cannot be individually cached, but they can otherwise provide additional flexibility in assembling your page. It's a good idea to use both: blocks when you need design flexibility and fragments when you need fine-grained caching.
There are special scriptlets for blocks. As an example, let's define a block and then include the actual template:
<%{ title %> <h1>My Title</h1> <%}%> <%& '/templates/main/' %>
The block definition is enclosed in "<%{…%>" and "<%}%>" scriptlets: instead of being printed out to the page, it will be saved for later use. Indeed, the template above generates no actual output in itself.
Now, let's see the actual template, in "/includes/templates/main.t.html":
<html> <body> <%== title %> </body> </html>
The "<%==…%>" scriptlet is a shortcut to print any conversation.local: indeed, our block was simply a temporary capturing of output into a conversation.local. Behind the scenes, blocks are simply calls to document.startCapture and document.endCapture.
But what if you want to give the block a default value in the template? There are special scriptlets for that, too. Let's change our "main.t.html":
<html> <body> <%[ title %> <h1>Default Title</h1> <%]%> </body> </html>
The "<%[…%>" and "<%]%>" scriptlets are like a conditional "<%==…%>": the code between them will not be executed if the conversation.local is already defined. If it is defined, then it will simply be printed out.
You may need blocks that change their content according to external parameters. Nothing magical about these: they are simply functions! For example:
<% function header1(content) { %> <h1><%= content %></h1> <% } %>
To use it, just call it:
<% header1('Hello'); %>
You can send block content as an argument to the function:
<%{ myArgument %> <a href="/">This HTML code will be sent as an argument</a> <%}%> <% header1(conversation.locals.get('myArgument')); %>
Templating Languages
Scriptlets can be used not only with programming languages, but also with templating languages. Succinct and Velocity are both supported. Here's an example using Velocity and JavaScript together:
<%js conversation.locals.put('test', 'TEST') %> <%velocity #macro(hello $name) <div>Hello, $name!</div> #end <div> #hello('Mozart') #hello('Bach') Here is a conversation local: $!conversation.locals.test </div> %>
As with programming languages, you will need to install the engine first:
sincerity add velocity : install
You might prefer to use templating engines independently of template resources, as MVC "views." See the MVC chapter for a guide.
HTML Markup Languages
You can also render the following HTML markup language via scriptlets: Markdown, Confluence, MediaWiki, TWiki, Trac and Textile. Here's a Markdown example:
<html> <body> <%md Our Conference ============== * The first item of business * The second item of business %> </body> </html>
As with programming languages, you will need to install the engine first:
sincerity add org.pegdown pegdown : install
Here's a list of package identifiers for all supported languages (note there are two implementation options for Markdown):
Language | Identifier |
Markdown | org.pegdown pegdown |
Markdown | org.eclipse.mylyn wikitext-markdown |
Confluence | org.eclipse.mylyn wikitext-confluence |
MediaWiki | org.eclipse.mylyn wikitext-mediawiki |
TWiki | org.eclipse.mylyn wikitext-twiki |
Trac | org.eclipse.mylyn wikitext-trac |
Textile | org.eclipse.mylyn wikitext-textile |
PHP
Of all the supported programming languages, PHP is special in that it already has a scriptlet parser. In Prudence, PHP code will work pretty much as is, as it mimics the PHP format. You actually have the choice of using the standard PHP-style delimiters, "<?…?>", or the ASP/JSP-style delimiters instead: "<%…%>".
For example:
<?php for($i = 0; $i < 10; $i++) { print '<p>' . $i . '</p>'; } ?>
PHP is special also in that it is designed for server-side web programming. Thus, though you can use all of Prudence's APIs in PHP, Prudence also explicitly supports many of PHP's predefined variables as a more standard alternative:
<?php print $_GET['id'] ?>
The convention when programming in PHP is to use ".php" extensions for files, even though the MIME type is "text/html". This is easy to achieve in Prudence by using a ".t.php" for your files, while also mapping the extension to the MIME type in your settings.js:
app.settings = { ... mediaTypes: { php: 'text/html' } }
Scriptlet Plugins
The default "scriptlets" parser comes with various useful shortcuts, but it's easy to create your own.
Configure your scriptlet plugins in settings.js using a dict that maps the shortcut codes to the library that will handle them. For this tutorial, we'll define two shortcuts in the same library:
app.settings = { ... templates: { plugins: { '_': '/plugins/custom/', '&?': '/plugins/custom/' } } }
The "_" shortcut will be used to print out localized text strings. The "&?" shortcut will be a conditional include. Let's now implement them in "/libraries/plugins/custom.js":
function handleGetScriptlet(code, languageAdapter, content) { if (code == '_') { return "print(application.globals.get('text.' + conversation.locals.get('locale') + '." + content.trim() + "'));" } else if (code == '&?') { return "if(conversation.locals.get('include')===true) { document.include(" + content + ") }" } return '' }
As you can see, the "handleGetScriptlet" function returns JavaScript source code that will be embedded into the generated program. You can optionally use the content of the scriptlet via the "content" param. If you wish to support multiple programming languages, you can test for them using "languageAdapter.attributes.get('language.name')".
Let's now use our shortcuts in a template resource:
<% conversation.locals.put('locale', 'en') conversation.locals.put('include', false) %> <p> How to say "hello" in your language: <%_ basic.hello %> </p> <p> This fragment will not be included: <%&? '/hello/' %> </p>
Our "_" shortcut expects certain application.global definitions, so let's define them in our settings.js:
app.globals = { text: { en: { basic: { hello: 'Hi!' } }, es: { basic: { hello: '¡Hola!' } } } }
- It's up to you, of course, to make sure that the code you generate is compilable.
- Plugins are tested for before the built-in shortcuts, allowing you to override the built-in ones.
- If you change your plugin code, it will not cause all template resources that use it to recompile. To force a recompile, you will need to change the modification date of those files, possibly by using the "touch" tool (on *nix).
Static Resources
Prudence works fine as a static web server: it's fast, supports non-blocking chunking, and has many useful features detailed below.
Of course, there are servers out there that specialize in serving static files and might do a better job, but you might be surprised by how far Prudence can take you.
Note that if Internet scalability is really important to you, it's better to even not use a standard web server at all, but instead rely on a CDN (Content Delivery Network) product or service with true global reach.
Configuration
Add support for static resources using the "static" route type in your routing.js, mapping it to a URI template ending in a wildcard:
app.routes = { '/*': 'static' }
The default configuration for "static" will map all files from your application's "/resources/" subdirectory, as well as the container's "/libraries/web/" directory. Here is the above configuration with all the defaults fleshed out:
app.routes = { '/*': { type: 'static', roots: [ 'resources', sincerity.container.getLibrariesFile('web') ], listingAllowed: false, negotiate: true, compress: true } }
Note that the "roots" (pluralized) param is a shortcut to create a chain of two "static" instances. The above is equivalent to:
app.routes = { '/*': [ {type: 'static', root: 'resources'}, {type: 'static', root: sincerity.container.getLibrariesFile('web')} ] }
If you want to also support manual and template resources, make sure to chain "static" after them, so it will catch whatever doesn't have the special ".m." and ".t." pre-extensions:
app.routes = { '/*': [ 'manual', 'templates', 'static' ] }
MIME Types and Compression
When "negotiate" is true, Prudence will handle HTTP content negotiation for your static resources, and will assume a single MIME type per resource. That MIME type is determined by the filename extension. For example, a resource named "logo.png" will have the "image/png" MIME type.
Prudence recognizes many common file types by default, but you can add your own mappings in you application's settings.js, using app.settings.mediaTypes.
When "compress" is also true, Prudence will negotiate the best compression format (gzip and zip are supported) and compress on the fly. Compression can be configured in settings.js.
Client-Side Caching
Prudence adds modification timestamp headers to all static resources, which allow clients, such as web browsers, to cache the contents and use conditional HTTP requests to later check if the cache needs to be refreshed.
Conditional HTTP is efficient and fast, but you can go one step further and tell clients to avoid even that check. Use the "cacheControl" filter before your "static" route type:
app.routes = { '/*': { type: 'cacheControl', mediaTypes: { 'image/*': '10m', 'text/css': '10m', 'application/x-javascript': '10m' }, next: 'static' } }
With the above, Prudence will ask web browsers to cache common image types, CSS and JavaScript for 10 minutes before sending conditional HTTP requests.
Make sure you understand the implications of this: after the client's first hit, for 10 minutes it will not be able to see changes to that static resource. The client's web browser would continue using the older version of the resource until its cache expires.
For a full discussion of client-side caching, see the caching chapter.
There is a widely-used trick that lets you use client-side caching while still letting you propagate changes immediately. It makes use of the fact that the client cache uses the complete URL as the cache key, which includes the query matrix. If you use a query param with the URL, the "static" resource will ignore it, but the client will still consider it a new resource in terms of caching. For example, let's say you include an image in an HTML page:
<img src="/media/logo.png" />
If you made a change to the "logo.png" file, and you want to bypass the client cache, then just change the HTML to this:
<img src="/media/logo.png?_=1" />
Voila: it's a new URL, so older cached values will not be used. For Prudence, the query makes no difference. You can then simply increase the value of the "_" query param every time you make a change.
This trick works so well that, if you use it, it's recommended that you actually ask clients to cache these resources forever. "Forever" is not actually supported, but it's customary to use 10 years in the future as a practical equivalent. Use "farFuture" as a shortcut in "cacheControl":
app.routes = { '/*': { type: 'cacheControl', mediaTypes: { 'image/*': 'farFuture' }, next: 'static' } }
Remembering to increase the query param in all uses of the resource might be too cumbersome and error-prone. Consider using the Diligence Assets service, or something similar, instead: it calculates digests for the resource file contents and use them as the query param. Thus, any change to the file contents will result in a new, unique URL.
What happens to cache entries that have been marked to expire in 10 years, but are no longer used by your site? They indeed will linger in your client's web browser cache. This isn't too bad: web browsers normally are configured with a maximum cache size and the disk space will be cleared and reused if needed. It's still an inelegant waste, for which unfortunately there is no solution in HTTP and HTML.
JavaScript and CSS Optimization
When writing JavaScript code, you likely want to use a lot of spacing, indentation and comments to keep the code clear and manageable. You would likely also want to divide a large code base among multiple files. Unfortunately, this is not so efficient, because clients must download these files.
Prudence's "javaScriptUnifyMinify" filter can help. To configure:
app.routes = { '/*': { type: 'javaScriptUnifyMinify', next: 'static' } }
The filter will catch URLs ending in "/all.js" or "/all.min.js", the former being unified and the latter unified and minified. The contents to be used, by default, will be all the ".js" files under your application's "/resources/scripts/" subdirectory as well as those under your container's "/libraries/web/scripts/" directory. The filter writes out the generated "all.js" and "all.min.js" files to "/resources/scripts/", and makes sure to update these files (unifying and minifying again) if any one of the source files are changed.
Note that the files are unified in alphabetical order, so make sure to rename them accordingly if order of execution is important.
Here is the above configuration with all the defaults fleshed out:
app.routes = { '/*': { type: 'javaScriptUnifyMinify', roots: [ 'resources/scripts', sincerity.container.getLibrariesFile('web', 'scripts') ], next: 'static' } }
For a usage example, let's say we have the following three files under the application's subdirectory:
/resources/scripts/jquery.js /resources/scripts/jquery.highlight.js /resources/scripts/jquery.popup.js
Your HTML files can then include something like this:
<head> ... <script src="/scripts/all.min.js"></script> </head>
Note that the first entry in the "roots" array is where the generated "all.js" and "all.min.js" files are stored.
The "cssUnifyMinify" filter does the same for CSS files, with the default roots being the application's "/resources/style/" subdirectory and the container's "/libraries/web/style/" directory. The relevant files are "all.css" and "all.min.css". Note, however, that similar functionality is provided by using LESS.
Here's an example with both filters configured:
app.routes = { '/*': { type: 'javaScriptUnifyMinify', next: { type: 'cssUnifyMinify', next: 'static' } } }
Usage in HTML:
<head> ... <link rel="stylesheet" type="text/css" href="/style/all.min.css" /> </head>
LESS to CSS
LESS is an extended CSS language. It greatly increases the power of CSS by allowing for code re-usability, variables and expressions, as well as nesting CSS. Using the the "less" filter you can compile ".less" files to CSS, and also apply the same minifier used by "cssUnifyMinify".
To configure:
app.routes = { '/*': { type: 'less', next: 'static' } }
The filter works by catching all URLs ending in ".css" or ".min.css". It will then try to find an equivalent ".less" file, and if it does, will compile it and produce the equivalent ".css" or ".min.css" file. It makes sure to recompile if the source ".less" file is changed.
For a usage example, let's say we have a "/resources/style/dark/main.less" file the application's subdirectory. Your HTML files can then include something like this:
<head> ... <link rel="stylesheet" type="text/css" href="/style/dark/main.min.css" /> </head>
The "less" filter can be configured with a "roots" param similarly to the "javaScriptMinifyUnify" and "cssUnifyMinify".
See the FAQ as to why Prudence supports LESS but not SASS.
Other Effects
Because static resources don't allow for code (unlike manual and template resources), the way to program your own effects is to add filters.
For example, let's say we want to force all ".pdf" files to be downloadable (by default, web browsers might prefer to display the file using a browser plugin). We'll be using the technique described here to change the response's "disposition".
Here's our filter code, in "/libraries/filters/download-pdf.js":
function handleAfter(conversation) { var entity = conversation.response.entity if (entity.mediaType.name == 'application/pdf') { entity.disposition.type = 'attachment' } }
To install it in routing.js:
app.routes = { '/*': { type: 'filter', library: '/filters/download-pdf/', next: 'static' } }
On-the-Fly Resources
You can add support for on-the-fly resources to your application via the "execute" route type. This powerful—and dangerous—resource executes all POST payloads as if they were template resources in the application, and is very useful for debugging and maintenance.
Another possibly exciting use case is to allow an especially rich API: instead of exposing your facilities via a RESTful API, you can allow certain clients full access to, well, everything. It's hard to recommend this usage for most applications due to its severe security risks, as well as limitations to scalability, but for some internally running applications it could prove extremely useful.
Diligence comes with the console feature, which offers a user-friendly variation of this functionality: it's a web-based mini-IDE that features persistent programs, JavaScript syntax coloring, and log tailing/filtering.
To install it, modify your application's routing.js and create a route for the "execute" type:
app.routes = { ... '/execute/': 'execute' }
Because it allows execution of arbitrary code, you very likely do not want its URL publicly exposed. Make sure to protect its URL on publicly available machines!
Example use with cURL command line:
curl --data '<% println(1+2) %>' http://localhost:8080/myapp/execute/
Note that if you use cURL with a file, you need to send it as binary, otherwise curl will strip your newlines:
curl --data-binary @myscriptfile http://localhost:8080/myapp/execute/
Where "myscriptfile" could be something like this:
<% document.require('/sincerity/templates/') println(Hello, {0}'.cast('Linus')) %>
Almost all the usual template resource APIs work (with the exception of caching, which isn't supported):
<% document.require('/sincerity/templates/') var name = conversation.query.get('name') || 'Linus' println('Hello, {0}'.cast(name)) %>
For the above, you could then POST with a query param:
curl --data-binary @myscriptfile 'http://localhost:8080/myapp/execute/?name=Richard'
Note that you can, as usual, use scriptlets in any supported programming language:
<%python name = conversation.query['name'] or 'Linus' print 'Hello, %s' % name %>
Also note that the default response MIME type is "text/plain", but you can modify it with the conversation.mediaType APIs:
<% document.require('/sincerity/json/') conversation.mediaTypeName = 'application/json' println(Sincerity.JSON.to({greeting: 'Hello'}, true)) %>
Java Resources
You may prefer to implement some of your manual resources directly in Java, by inheriting ServerResource, or perhaps you are relying a JVM library that already comes with such resources. To use them, set them up in your routing.js via the "resource" route type.
Some notes:
- Sure, Java can offer better performance than dynamic languages, but it's very rare for language performance to be the bottleneck: communication with a database server, for example, is orders of magnitude slower. Don't "drop down" to Java in order to optimize unless you've really confirmed that language performance is a problem.
- Prudence's manual resources are not merely dynamic-language implementations of ServerResource, but also provide extra features such as integrated server-side caching. If you switch to Java, you would have to implement such features on your own.
Resource Type Comparison Table
Manual | Template | Static | |
Supports URI Mapping | Yes | Yes | Yes |
Supports URI Dispatching | Yes | No | No |
Filename Extension | Determines programming language | Determines MIME type | Determines MIME type |
Filename Pre-extension | *.m.* | *.t.* | n/a |
Programming Languages | Determined by filename extension | Determined by scriptlet tags (multiple languages possible per resource) | n/a |
Content Negotiation | Manually determined in handleInit; multiple MIME types possible; multiple compression types automatically supported and cached | Single MIME type determined by filename extension; multiple compression types automatically supported and cached | Single MIME type determined by filename extension; multiple compression types automatically supported |
Server-Side Caching | Manual (via API) | Automatic (determined by server-side caching) | n/a |
Client-Side Caching | Manual (via API) | Automatic (determined by server-side caching) | Can be added with CacheControlFilter |
The Prudence Manual is provided for you under the terms of the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. The complete manual is available for download as a PDF.