Hosted by Three Crickets

Prudence
Scalable REST Platform
For the JVM

Prudence logo: bullfinch in flight

Filters

In Prudence, "filters" are route types used to add effects to resources. Though you can often code the same effect directly into a resource, filters are decoupled from resources, allowing you to reuse an effect on many resources, which is especially useful with mapping route types.
Furthermore, filters are the only way to add effects to static resources, which cannot themselves be programmed.
Because it's easy to enable and disable filters just by editing routing.js, filters are often used to add debugging and testing effects.

Tutorial

Filters are implemented similarly to manual resources: as source code files (in any supported language) with either or both filter entry points: handleBefore and handleAfter. The former is called before all requests reach the next (downstream) route type, and the latter is called on the way back, after the downstream finishes its work.
Let's start with configuring our filter, using the "filter" route type in routing.js. We'll put it in front of all our main mapping resources:
app.routes = {
	...
	'/*': {
		type: 'filter',
		library: '/my-filter/',
		next: [
			'manual',
			'templates',
			'static'
		]
	}
}
Now let's create the actual filter in "/libraries/my-filter.js". We'll start with a trivial implementation of the handleAfter entry point:
function handleAfter(conversation) {
	application.logger.info('We got a request for a resource at: ' + conversation.reference)
}
Our filter doesn't do much yet, but it's easy to test that the code is being called by looking at the log. Of course you can do many other things here, as detailed in the examples below. Most of the conversation APIs are available to you, including the redirection APIs.
Note that while you need to restart your application for the changes in routing.js to take hold, you are free to edit my-filter.js and have the changes picked up on-the-fly.
handleBefore is a bit more sophisticated than handleAfter, in that it also requires a return value:
function handleBefore(conversation) {
	application.logger.info('We got a request for a resource at: ' + conversation.reference)
	return 'continue'
}
Three literal return values are supported, as either a string or a number:
Again, you may define both a handleBefore and a handleAfter in the same filter.

Examples

Changing the Request

It may be useful to change user requests for testing purposes. Specifically, we can affect content negotiation by changing the accepted formats declared by the user.
For example, let's say we want to disable compression for all resources, even if clients declare that they are capable of handling it:
function handleBefore(conversation) {
	conversation.client.acceptedEncodings.clear()
	return 'continue'
}
Easy! Note that this would be much harder to achieve retroactively, by changing the response: we would have to decompress all compressed responses. Some effects are much better implemented in handleBefore.

Overriding the Response

Filters can be useful for overriding the response under certain conditions.
The following example always sets the response to a web page displaying "blocked!", unless a special "admin" cookie is used with a magic value. It can be used to make sure that certain resources are unavailable for users who are not administrators:
function handleAfter(conversation) {
	if (!isAuthorized(conversation)) {
		var content = '<html><body>' + conversation.reference + ' is blocked to you!</body></html>'
		conversation.setResponseText(content, 'text/html', 'en', 'UTF-8')
	}
}
​
function isAuthorized(conversation) {
	var cookie = conversation.getCookie('admin')
	return (null !== cookie) && (cookie.value == 'magic123')
}
Another, simpler trick, would be to redirect the response:
function handleAfter(conversation) {
	if (!isAuthorized(conversation)) {
		conversation.redirectSeeOther(conversation.base + '/blocked/')
	}
}
Note on changing the response: You might think that filters could be useful to affect the content of responses, for example to "filter out" data from HTML pages. Actually, Prudence filters are not a good way to do this, because there's no guarantee that response payloads returned from downstream resources are textual, even if the content is text: they could very well be compressed (gzip) and also chunked. You would then need to decode, disassemble, make your changes, and then reassemble such responses, which is neither trivial nor efficient. Content filtering should best be handled at the level of the resource code itself, before the response payload is created.

Side Effects

Filters don't have to change anything about the request or the response. They can be useful for gathering statistics or other debugging information.
In this example, we'll gather statistics about agent self-identification: specifically web browser product names and operating systems (via the conversation.client API):
document.require('/sincerity/templates/')
​
importClass(
	java.util.concurrent.ConcurrentHashMap,
	java.util.concurrent.atomic.AtomicInteger)
​
var logger = application.getSubLogger('statistics')
​
function handleBefore(conversation) {
	var agent = conversation.client.agentName
	var os = conversation.client.agentAttributes.get('osData')
​
	getCounter('agent', agent).incrementAndGet()
	getCounter('os', os).incrementAndGet()
​
	logger.info('Agent stats: ' + application.globals.get('counters.agent'))
	logger.info('OS stats: ' + application.globals.get('counters.os'))
​
	return 'continue'
}
​
function getCounter(section, name) {
	var counters = application.getGlobal('counters.' + section, new ConcurrentHashMap())
	var counter = counters.get(name)
	if (null === counter) {
		counter = new AtomicInteger()
		var existing = counters.putIfAbsent(name, counter)
		if (null !== existing) {
			counter = existing
		}
	}
	return counter
}
Here we're storing statistics in memory and sending them to the log, but for your uses you might prefer to store them in a database using atomic operations.

Built-in Filters

Prudence comes with a few built-in filters, each with its own route type. Many of them are useful specifically with static resources, and are discussed in that chapter. A few others are more generally useful, and are discussed here.

Inversion of Control (IoC) via Injection

You already know that you can configure parts of your application via application.global presets. Globals, of course, affect the entire application. However, you may sometimes need local configurations: the ability to a specific instance of a resources differently from others. That's where the "injector" route type comes in.
Note that because IoC is most often used together with capturing and dispatching, there is a shortcut notation to apply to the "capture" and "dispatch" route types. However, you can also use injection independently. An example for routing.js:
app.routes = {
	...
	'/user/{name}/': {
		type: 'injector',
		locals: {
			deployment: 'production'
		},
		next: '@user'
	}
}
To access the injected value in your resource code, simply use the conversation.locals API:
var deployment = conversation.locals.get('deployment')
if (deployment == 'production') {
	...
}
You can inject any kind of object using an injector, though keep in mind that the native types of JavaScript may not be easily accessible in other programming languages. For example, if you're injecting a dict or an array, it would not be automatically converted to, say, a Python dict or vector. However, primitive types such as strings and numbers would be OK for all supported languages.
Templates
Note that injectors specially recognize Template instances and casts them before injecting. This allows you to interpolate conversation attributes into strings:
app.routes = {
	...
	'/user/{name}/': {
		type: 'injector',
		locals: {
			protocol: new org.restlet.routing.Template('{p}'),
			deployment: 'production'
		},
		next: '@user'
	}
}
(The above example is not that useful: you can just as easily access the protocol using conversation.request.protocol.name.)

HTTP Authentication

You can implement simple HTTP authentication using the "basicHttpAuthenticator" route type:
app.routes = {
	...
	'/*': {
		type: 'basicHttpAuthenticator',
		realm: 'Authorized users only!',
		credentials: {
			moderator: 'moderatorpassword',
			admin: 'adminpassword'
		},
		next: [
			'manual',
			'templates'
		]
	}
}
Note that the implementation relies on basic authentication (BA), which is unencrypted. It is thus strongly recommended that you use it only with HTTPS.

Cross-Origin Resource Sharing (CORS)

app.routes = {
	...
	'/*': {
		type: 'cors',
		allowOrigin: '*',
		allowMethods: ['GET', 'POST'],
		allowHeaders: ['Content-Type', 'Last-Modified', 'Expires'],
		maxAge: 'farFuture',
		next: [
			'manual',
			'templates'
		]
	}
}
Note that "magAge" is in seconds, and must be greater than zero. You can specify it as as either a number or a string, or use "farFuture" as a shortcut for 10 years.

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.

Download manual as PDF Creative Commons License