Prudence
The Scalable REST/JVM
Web Development Platform

Creative Commons License

Static Web

Prudence attaches your application's /web/static/ subdirectory to URLs, making files there available via HTTP GET. In other words, /static/web/ is a "web server." It's a convenient place for storing immutable resources that your clients will need to run your application: images, styles, scripts, etc.
You're likely, though, wondering if /web/static/ is merely a convenience, and if you'd be better off using Apache or other dedicated web servers instead of Prudence to serve your the static files.
Our recommendation is to take that route only if those web servers offer important features that you won't find in Prudence. Remember that Prudence has many powerful features, including URL rewriting and redirection, smart compression, and that Prudence's non-blocking I/O does a great job at scaling. You will likely not see significantly better performance or scalability by replacing /web/static/ with standard web servers.

Mapping Files to MIME Types

Files are sent to clients with a MIME type determined according to the filename's extension.
See application configuration for instructions on how to configure this mapping.

Replacing Jetty

Jetty can be replaced with Netty or Grizzly, non-blocking I/O HTTP servers with different performance characteristics. See the Restlet documentation for more information.

CacheControlFilter

Prudence handles conditional HTTP automatically for /web/static/: when clients request a new file, they tell Prudence which version they have cached, and Prudence makes sure not to send them the file if they already have the most recent version. Unfortunately, this still does require a short round-trip HTTP request between client and server.
To avoid even these short requests, Prudence comes with a handy filter that can selectively set cache control headers according to file type. By default, the filter sets the max age cache header to the "far future" (ten years) for all files. For most web browsers, and many other clients that support local caching, this effectively causes the files to be cached "forever." Thus, the client would only request the file again if the cache entry is deleted due to the cache being full, or if the cache is manually reset.
This can allow for faster page rendering in web browsers, save you in bandwidth costs, and help you scale. Sounds perfect? Unfortunately, it also affects your ability to make changes to files. Ten years means ten years: the client does not expect the file to change in that time. Since you likely cannot ask your users to reset their browser caches every time you make a change, what you can do instead is rename the file on your end: because clients cache files according to their URLs, and filenames are mapped to URLs, the client would consider it a fresh file. Of course, you would also have to update references to the URL in HTML and CSS.
Another trick to force clients to re-request an updated file is to add query strings to the URL. For example, consider this HTML code:
<img src="images/logo.png?_=128799073903458">
The query string is ignored by Prudence's static web handler, but since it does signify a new URL, the client will use a separate cache entry for it. This lets you keep your filenames, but does require you to alter URLs.
Since the above techniques require considerable discipline from the application maintainers, the cache control filter is not enabled by default in Prudence. The following is an example on how to enable it in an application's "routing.js", while disabling it for file types that are frequently updated:
importClass(
	org.restlet.data.MediaType,
	com.threecrickets.prudence.util.CacheControlFilter)
​
var staticWebCacheControlFilter = new CacheControlFilter(null)
​
// Disable caching for CSS and JavaScript files
staticWebCacheControlFilter.maxAgeForMediaType.put(MediaType.TEXT_CSS, -1)
staticWebCacheControlFilter.maxAgeForMediaType.put(MediaType.APPLICATION_JAVASCRIPT, -1)
​
router.filterBase(staticWebBaseURL, staticWebCacheControlFilter, staticWeb)
You can use this filter for /dynamic/web/, too, but you're better off using its built-in support.

JavaScriptUnifyMinifyFilter

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 your code into multiple files. However, if this code is meant to be run on the client, this could mean sending several overly large files to it.
Prudence comes with a handy filter that lets you "unify" several JavaScript files into one, and to "minify" it so that superfluous comments and whitespace are removed. Bandwidth savings can be significant.
The filter works by detecting a user request for a file named "all.min.js" in a directory which contains ".js" files. If the file does not exist, it collects all the JavaScript files in the directory, unifies them in alphabetical order, and minifies the result into a file called "all.min.js". The filter picks up your changes on the fly, and will only regenerate "all.min.js" if you've made a change to any of the base files. You can control the minimum time required to pass between these checks via a constructor argument (see example below).
If order of source files within the unified file is important, make sure to name so that an alphabetical sort would put them in the right order.
importClass(com.threecrickets.prudence.util.JavaScriptUnifyMinifyFilter)
​
router.filterBase(
	staticWebBaseURL,
	new JavaScriptUnifyMinifyFilter(null, new File(applicationBasePath + staticWebBasePath),
		minimumTimeBetweenValidityChecks), staticWeb)
An example of loading the unified-minified file from HTML:
<html>
<head>
	<script type="text/javascript" src="script/all.min.js"></script>
</head>
</html>

CssUnifyMinifyFilter

This filter does for CSS what the JavaScriptUnifyMinifyFilter does for JavaScript. Example:
importClass(com.threecrickets.prudence.util.CssUnifyMinifyFilter)
​
router.filterBase(
	staticWebBaseURL,
	new CssUnifyMinifyFilter(null, new File(applicationBasePath + staticWebBasePath),
		minimumTimeBetweenValidityChecks), staticWeb)
You can use JavaScriptUnifyMinifyFilter and CssUnifyMinifyFilter together. The order of filtering should not matter.