Prudence
The Scalable REST/JVM
Web Development Platform

Creative Commons License

Routing

In Prudence, "routing" refers to the decision-making process by which an incoming client request reaches its handler on the server. Usually, information in the request itself is used to make the decision, such as the URI, cookies, the client type, capabilities and geolocation. But routing can also take server-side and other circumstances into account. For example, a round-robin load-balancing router might send each incoming request to a different handler in sequence. The decision on which handler to use has nothing to do with the a client's particular request.
A request normally goes through many routers before reaching its handler. Filters along the way can change information in the request, which could also affect routing. Filtering should thus also be thought of as a routing tool.
This abstract, flexible routing mechanism is one of Prudence's most powerful features, but it's important to understand these basic principles. A common misconception is that routing is based on the hierarchical structure of URIs, such that a child URI's routing is somehow affected by its parent URI. While it's possible to route by parent URI in Prudence, routing is primarily to be understood in terms of the order of routers and filters along the way. A parent and child URI could thus use entirely different handlers.
When writing applications for Prudence, you will mostly be interested in application-level routing, which we will cover in-depth below. However, to give you a better understanding of how Prudence routing works, let's follow the journey of a request, starting with routing at the instance level.

Instance Routing

Before a request reaches your application, it is routed by your Prudence instance.

Step 1: Servers

Requests come in from servers. Prudence instances have at the minimum one server, but can have more than one. Each server listens at a particular HTTP port, and multiple servers may in turn be restricted to particular network interfaces on your machine. By default, Prudence has a single server that listens to HTTP requests on port 8080 coming in from all network interfaces.
You can configure your servers in "/instance/servers.*".

Step 2: The Component

There is only one component per Prudence instance, and all servers route to it. This allows Prudence a unified mechanism to deal with all incoming requests.

Step 3: Virtual Hosts

The component's router decides which virtual host should receive the request. The decision is often made according to the domain name in the URL, but can also take into account which server it came from. Virtual hosting is a tool to let you host multiple sites on the same Prudence instance, but it can be used for more subtle kinds of routing, too.
At the minimum you must have one virtual host. By default, Prudence has one that accepts all incoming requests from all servers. If you have multiple servers and want to treat them differently, you can create a virtual host for each.
You can configure your virtual hosts in "/instance/hosts.*".

Step 4: Applications

Using "hosts" in your application's "settings.*", you can configure which virtual hosts your application will be attached to, and the base URI for the application on each virtual host. An application can accept requests from several virtual hosts at once.
To put it another way, there's a many-to-many relationship between virtual hosts and applications: one host can have many applications, and the same application can be attached to many hosts.
Note that you can create a "nested" URI scheme for your applications. For example, one application might be attached at the root URI at a certain virtual host, "/", while other applications might be at different URIs beneath the root, "/wackywiki" and "/wackywiki/support/forum". The root application will not "steal" requests from the other applications, because the request is routed to the right application by the virtual host. The fact that the latter URI is the hierarchical descendant of the former makes no difference to the virtual host router.

A Complete Route

Let's assume a client from the Internet send a request to URI "http://www.wacky.org/wackywicki/support/forum/thread/12/."
Our machine has two network interfaces, one facing the Internet and one facing the intranet, and we have two servers to listen on each. This particular request has come in through the external server. The request reachers the component's router.
We have a few virtual hosts: one to handle "www.wacky.org", our organization's main site, and another to handle "support.wacky.org", a secure site where paying customers can open support tickets.
Our forum application (in the /applications/forum/ subdirectory) is attached to both virtual hosts, but at different URIs. It's at "www.wacky.org/wackywiki/support/forum" and at "support.wacky.org/forum". In this case, our request is routed to the first virtual host. Though there are a few applications installed at this virtual host, our request follows the route to the forum application.
The remaining part of the URI, "/thread/12/" will be further routed inside the forum application, as detailed below.

Application Routing

Step 5: Application Handlers

Prudence applications come with default support for three kinds of handlers: resources (in the /resources/ subdirectory), dynamic web pages (in the /web/dynamic/ subdirectory) and static web resources (in the /web/static/ subdirectory). By default, all three handlers are attached at the root URI, "/", of the application (which may vary per virtual host). However, it is possible to change this in your application's "settings.*", see resourcesBaseURL, dynamicWebBaseURL and staticWebBaseURL. For example, you may want your resources to be routed under "/rest-interface/".
You may ask, for any given request, how can the application's router know which handler to send it to, if all handlers by default assume the same "/" base URL? The answer is that it doesn't. It tries each handler in sequence, and if one handler cannot handle the request, it falls back to the next one. For example, a "style/main.css" URI will be tried first as a resource. If that resource doesn't exist, it will be tried as a dynamic web page. Finally, it will be sent to the static web handler.
Be careful: this system allows for previous handlers in the sequence to supersede later handlers. For example, if you decide to remove scriptlets from a file named /dynamic/web/about.html and move it to /static/web/about.html, make sure to delete the former, or else the latter will not be reached.

Subdirectories and Filenames As URI Segments

The three application handlers—resources, dynamic web pages and static web resources—are all routed by mapping the filesystem structure to a URI. Each subdirectory path or filename is directly translated into a URI segment.
This is exactly the scheme used by most static web servers, and it has the benefit of using a readily-available, easy-to-use hierarchical structure—the filesystem—as a straightforward way for creating URIs.
There's one deficiency to this scheme: by directly mapping filenames, it can allow for "ugly" URIs that include filename extensions. For example, you're probably used to seeing many web sites with URLs that end in ".html", ".php" and ".jsp". While these extensions are meaningful to the site developer, they complicate the URIs and expose internal implementation details to outsiders.

Pretty URIs

To allow for prettier URIs, Prudence does a few things:
Filename Extension Hiding
Prudence ignores filename extensions for the purpose of mapping to URIs. For example, the file "/web/dynamic/wiki/table-of-contents.html" will be mapped to the URI "/wiki/table-of-contents/".
Default Files
In /web/dynamic/ and /web/static/, if a file "index.html" exists in a subdirectory, it is mapped to the URI for the subdirectory itself. In /resources/, the default file is "default.*".
For example, the file "/web/dynamic/wiki/contributors/index.html" is mapped to the URI "/wiki/contributors/". Note that another way to create the same URI is to use "/web/dynamic/wiki/contributors.html" (the ".html" is hidden by Prudence).
Default files are simply another organizational option for you. In very large applications, it can help keep your files well-organized without having any effect on the URIs that exposed externally. You can even change your scheme as your application evolves.
The name "index.html" is used by web servers for archaic reasons: it was conceived of as a place where you could list the contents, or "index," the subdirectory. These days, however, we tend to have a more general understanding of URIs. By default, we've followed this convention in Prudence. You can change this with the "com.threecrickets.prudence.GeneratedTextResource.defaultName" application attribute.
Trailing Slash Requirement
You'll note that we used "/wiki/contributors/" for the above, rather than "/wiki/contributors" (the difference is the trailing slash). This is because Prudence requires trailing slashes by default: trying to access "/wiki/table-of-contents" would permanently redirect to "/wiki/table-of-contents/".
Prudence requires a trailing slash for two main reasons:
  1. To keep the URI space consistent, whether you use subdirectories or filenames to create the URI segments.
  2. This is Prudence's way of fighting to the "trailing slash" problem, which plagues the use of relative URIs in HTML and CSS.
You can turn off the trailing slash requirement via the "com.threecrickets.prudence.GeneratedTextResource.trailingSlashRequired" and "com.threecrickets.prudence.DelegatedResource.trailingSlashRequired" application.globals.
In Prudence 1.1, the filename extension hiding and trailing slash requirement work only for /resources/ and /web/dynamic/ URLs. /web/static/ URLs still map full filenames. A future version of Prudence may extend these features to all resources.
If you'd like a specific non-trailing-slash URL to automatically redirect to the trailing-slash equivalent, use the application's "urlAddTrailingSlash" setting.
Overriding Pretty URIs
You can override all the automatic prettifying techniques mentioned above by explicitly capturing URIs. For example:
router.capture('/sitemap.xml', '/sitemap/')

Filename Extensions

Though Prudence hides the filename extensions from the URIs, they do have two important functions:
Filename Extensions and MIME Types
Filename extensions define the default media type for "GET" requests to pages in /web/dynamic/ and /web/static/. For example, a ".xml" file will be mapped to the "application/xml" MIME type.
Every application has its own filename extension mapping table.
Additionally, you can explicitly set the MIME type in a code via conversation.mediaTypeName, overriding the default.
Filename Extensions and Programming Languages
In /resources/, and for configuration scripts, the filename extension tells Prudence the programming language of the source code. Prudence supports ".py", ".rb", ".js", ".php", ".clj" and ".php" files.
Filename Extensions Preference
In /resources/, the Prudence flavor you are using will determine which file to use in the case of ambiguity. For example, the Groovy flavor will prefer "process.gv" file even if "process.js" is present. This allows you to write and deploy applications that can run in multiple Prudence flavors. The same is true for configuration scripts.
However, in /web/dynamic/ there is no explicit preference, and behavior is undefined if you have more than one file with the same name but different extension in the same subdirectory. For example, contents.html and contents.xml.

Custom Routing

The straightforward URI routing based on directories and filenames is useful, but you'll likely need other kinds of routing, too. That's what your application's "routing.*" is for.
Actually, all of this API can also be called at runtime, allowing you to attach and change routes dynamically while the Prudence instance and its applications are up and running.

Hiding

The following will make sure that the given URI always returns a 404, even if the page or resource exists. For example, in "routing.*" (JavaScript flavor):
router.hide('/administration/')
The resource will still be available internally, via document.internal, and for capturing.

Static Capturing

Prudence lets you "capture" arbitrary URI patterns into any internal URI you wish, whether they're implemented as /resources/, /web/dynamic/ or /web/static/. This lets you turn a single URI implementation to into a multitude of URIs, vastly expanding your URI-space.
Static capturing is handled in your application's "routing.*", via the router.capture API. The first argument is the pattern your want to capture, the second is the internal URI to which the request will be redirected. You use curly-bracket-delimited tags in the URI to parse URI segments and store them in conversation.locals. For example, in "routing.*" (JavaScript flavor):
// Implement defaults
document.execute('/defaults/application/routing/')
​
router.capture('/forum/help/{topic}/', '/forum/help/')
And then, in "/web/dynamic/forum/help.html":
<html>
<body>
<p>You are viewing help topic <%= conversation.locals.get('topic') %>.</p>
</body>
</html>
Capturing might look like redirection, but in fact it's an internal redirection, similar to how the document.internal API works. The client remains entirely ignorant as to what internal URIs you might be using.
It's important to understand this distinction: the client might be seeing an entirely different URI than your internal one. Thus, if you're using HTML and CSS, you need to make sure that your relative references reach the right place. This is easy in Prudence with the conversation.pathToBase API, which will use the captive client URI. For example:
<html>
<body>
<p>You are viewing help topic <%= conversation.locals.get('topic') %>.</p>
<img src="<%= conversation.pathToBase %>/images/help.png"/>
</body>
</html>
Capturing in this manner is often used in conjunction with URI hiding. By hiding "/forum/help/", users would be able to access "/forum/help/faq/" via HTTP, but not "/forum/help/". This lets you effectively control the exposed URI space. In fact, capturing and hiding is common enough that it has its own shortcut API:
router.captureAndHide('/forum/help/{topic}/', '/forum/help/')
​
The above is identical to:
​
router.capture('/forum/help/{topic}/', '/forum/help/')
router.hide('/forum/help/')
Another feature of internal redirection is that your code can check for internal access and enforce it. As an example, let's implement URI hiding via a scriptlet:
<%
if(!conversation.internal) {
	conversation.statusCode = 404 // resource not found
}
%>
<html>
<body>
<p>You are viewing help topic <%= conversation.locals.get('topic') %>.</p>
</body>
</html>
Prudence also lets you capture into a different application in the instance. Refer to applications using their subdirectory name. For example:
// Implement defaults
document.execute('/defaults/application/routing/')
​
router.captureOther('/forum/help/{topic}/', 'wackyhelp', '/help/')
In addition to parsing URI segments, you can use predefined curly-bracket-delimited variables to route based on attributes of the request, via URI patterns. For example, we can route with the "{m}" variable (HTTP method) to elegantly handle HTTP forms:
router.capture('/message/', '/message/{m}/')
We can then have a dynamic web page named "/message/GET.html":
<html>
<body>
<form method="post">
	Enter your message:<br/>
	<textarea name="message"></textarea><br/>	
	<input type="submit" />
</form>
</body>
</html>
and one named "/message/POST.html":
<html>
<body>
	<%= conversation.form.get('message') %>
</body>
</html>
Another example of capturing with URI patterns:
router.capture('/data/client/', '/data/client/{emt}/')
Because "{emt}" is the MIME type of data sent by the client, and the slash used in MIME types is compatible with the slash used in filesystem path separation, we can have files such as these:
  • /resources/data/client/text/html/default.js
  • /resources/data/client/application/json/default.js
  • /resources/data/client/default.js (this will be used if no client data is sent, for example in an HTTP GET)
Of course, you might prefer to check the MIME type in code your code via conversation.mediaTypeName, but the above is a good example of how powerful routing can be with URI patterns.

Dynamic Capturing

Static capturing using patterns is very powerful, but sometimes you need more than patterns: you need to check attributes of the client's request, such as the contents of cookies, or do a database lookup to find out the destination internal URI. To allow for this, Prudence also lets you capture by returning a URI in a filter.
Here's an example of a filter that redirects to a wiki page only if that page exists in the database. First, let's install the filter in our "routing.js":
// Implement defaults
document.execute('/defaults/application/routing/')
​
router.filterBase(dynamicWebBaseURL, '/page-filter/', dynamicWeb)
Here is "/handlers/page-filter.js":
function handleBefore(conversation) {
	// Let's look for the page name in remaining part of the URI
	var pageName = String(conversation.reference.getRemainingPart(true, false))
​
	// Fetch the page from the database
	var page = getPageFromDatabase(pageName)
	if (page) {
		// Cache the fetched page in conversation.locals
		conversation.locals.put('page', page)
​
		// Capture!
		return '/page/'
	}
​
	// This was not a page, so continue as usual
	return 'continue'
}
You would then have a "/web/dynamic/page.html" that can get the pre-fetched page from conversation.locals.get('page') and renders it.

Capturing Errors

A special case of capturing is for errors. "Errors" can be set explicitly by you: for example, we can set conversation.statusCode to 404, as we did above. However, a 500 error occurs automatically for uncaught exceptions in your code.
If you have debug mode enabled, the user would see the special debug page for 500 errors; see debugging. On a production site, you may instead prefer to capture the 500 error and provide a friendlier page. (You'd also want to test carefully and make sure your code never throws exceptions…)
You can capture errors both at the application level or at the instance level, which the former taking precedence. It may be a good idea to always capture at the instance level, in case applications don't capture for their own custom error pages.
Examples from "/instance/routing.*" (JavaScript flavor):
// Implement defaults
document.execute('/defaults/instance/routing/')
​
// 404 errors
component.statusService.capture(404, 'wackyhelp', '/help/main/',
	component.context)
​
// 500 errors
component.statusService.captureHostRoot(500, 'wackywiki', '/oops/',
	component.context)
Examples from an application's "routing.*":
// Implement defaults
document.execute('/defaults/application/routing/')
​
// 404 errors
applicationInstance.statusService.capture(404, 'wackyhelp', '/help/main/',
	applicationInstance.context)
Notes for error capturing:
  • You always need to specify the application name (like router.captureOther).
  • As with regular capturing, you can hide these pages if you don't want users to be able to access them directly. (Though, if you hide your 404 page, users would still get to it because it's what would be displayed for hidden pages! The difference is that it would appear to users with a 404 status, not a 200 success status.)
  • The difference between statusService.capture and statusService.captureHostRoot is in how the base URI is set, which affects the conversation.pathToBase API. statusService.capture uses to the application's base URI on the current virtual host, while statusService.captureHostRoot uses to the virtual host root itself.
  • For 500 error capturing, you should prefer a /web/static/ page, which has the least chance of generating an exception and causing a 500 error again, resulting in a loop.

Static Redirection

By "static" here is meant that redirection is configured into the application's "routing.*" script. (You can also call this API on a running application, though you should realize that it essentially changed your application's routing.) "Dynamic" routing, from within /dynamic/web/ or /resources/, is described in the next section.
Because static redirection supports URI patterns, this API can actually handle complex "dynamic" redirections. For example, even though most clients will support relative URI paths for redirection, you can force a complete URI using the "{rp}" pattern, which equals is the request URI path.
The following redirection status codes are supported:
  • 301, permanent: Most clients will cache this redirection, thus avoiding subsequent requests to the original URI
  • 302, client found: This should be a temporary redirect, like 307, but many clients treat it as a 303. Best to avoid, unless you enjoy confusion.
  • 303, see other: Clients assume that you have processed their request, and should send a GET to the new URI in order to receive the results of the processing. Use with care!
  • 307, temporary: The default (because it's the safest)
Note that behind the scenes, static redirection is handled by attaching a Redirector restlet.
Here are a few examples (JavaScript):
// Implement defaults
document.execute('/defaults/application/routing/')
​
// Simple redirection
router.redirectClient('/bug/', 'http://wackywiki.org/contact-us/bug/')
​
// Redirection to add a query to the request URI
router.redirectClient('/forum/', '{rp}?debug=true')
​
// Permanent redirection using URI segments
router.redirectClient('/contact/{reason}/', 'http://wackywiki.org/contact-us/{reason}', 301)
​
// The above is all Prudence sugar. Here's an example of what happens behind the scenes:
importClass(org.restlet.routing.Redirector)
router.attach('/bug/',
	new Redirector(applicationInstance.context, 'http://wackywiki.org/contact-us/bug/',
	Redirector.MODE_CLIENT_SEE_OTHER))

Dynamic Redirection

You can handle redirection at either /resources/ or /web/dynamic/ with the following API:
  • For 301: conversation.response.redirectPermanent
  • For 305: conversation.response.redirectSeeOther (use this if you've already processed the client's request, and want to redirect it to a new location in order to GET the results)
  • For 307: conversation.response.redirectTemporary
For example:
<%
if (!conversation.internal) {
	conversation.response.redirectSeeOther('../contact-us/')
}
conversation.stop()
%>
Note that we are checking that the conversation is not internal. As of Prudence 1.1, internal redirects will cause exceptions!
Also, they will likely ignore any data in the redirected response, so you can just return null in /resources/ or empty text in /web/dynamic/. This is the reason we call conversation.stop in the example above.
For other redirections in the 300 family, follow this example:
conversation.response.locationRef = 'http://wackywiki.org/report-a-bug/'
conversation.statusCode = 302
conversation.stop()
See also static redirection.

Attaching

This is the lowest-level routing API. It allows you to route URI patterns to any custom "restlet" (a REST conversation handler), as well as Restlet resources (which internally use a Finder restlet). Do your attachments in the application's "routing.*" configuration script. Prudence offers some attachment "sugar" in addition the standard Restlet API.
Here are a few examples (JavaScript):
// Implement defaults
document.execute('/defaults/application/routing/')
​
// Attach a directory instance
importClass(org.restlet.resource.Directory)
router.attach('/forum/info/',
	new Directory(applicationInstance.context,
	'file:///user/info/files/'))
	.matchingMode = Template.MODE_STARTS_WITH
​
// Or, you can use Prudence sugar (equivalent to MODE_STARTS_WITH)
router.attachBase('/forum/help/',
	new Directory(applicationInstance.context,
	'file:///user/data/files/'))
​
// More Prudence sugar: attach a resource via its classname
router.attach('/forum/help/{topic}/', 'org.wackywiki.HelpAccessResource')
​
// Prudence also lets you detach restlet instances
router.detach(staticWeb)
The difference between "attach" and "attachBase" is the default URI template matching pattern: MODE_EQUALS vs. MODE_STARTS_WITH. See the Restlet documentation.

Filtering

Instances of filters, which wrap instances of other restlets, can be attached directly to URIs. The filter can then pass control to the "next" restlet, skip other filters along the chain, or immediately stop routing.
There are many use cases for filters. Here are just a few common ones:
  • Guards: they check if the client's request has the right authorization (has the right cookie, has originated from a white-listed IP address, etc.) before letting it continue. On stopping, they usually set the conversation.statusCode to either 401 ("unauthorized") or 402 ("forbidden").
  • Sanitizers: they remove harmful data or code from the client request, remove private or offensive material from the response, etc.
  • Transformers: they translate, re-encode, decorate or otherwise transform response entities between various formats.
  • Monitors: they can log each request passing through, accumulate statistics, alert administrators, etc.
  • Throttles: they can refuse to pass requests on if the system is overloaded, if the client has exceeded a quota, etc. On stopping, they usually set the conversation.statusCode to 503 ("service unavailable").
  • Caches: they can fetch from a cache according to request attributes, and store in a cache according to response attributes.
  • Post-processors: they can find and interpret special codes, scriptlets, headers, etc., in the response as commands to execute.
In addition to the standard Restlet API, Prudence allows for a few useful shortcuts for filtering.
Here's an example of filtering all static web requests through the JavaScriptUnifyMinifyFilter:
importClass(com.threecrickets.prudence.util.JavaScriptUnifyMinifyFilter)
​
// This will detach staticWeb and attach our filter instead with staticWeb chained to it;
// also note that the context for staticWeb will be used for the filter, so we can specify
// null for the context in the constructor
// (this is Prudence sugar)
​
var filter = new JavaScriptUnifyMinifyFilter(null,
	new File(applicationBasePath + staticWebBasePath), minimumTimeBetweenValidityChecks)
​
router.filterBase(staticWebBaseURL, filter, staticWeb)
​
// This next step is to allows us to easily install further filters before staticWeb,
// which should from now on go before the filter we just installed
​
staticWeb = filter
You can also filter the entire application by replacing its inbound router with a filter. For example:
var myFilter = new MyFilter(applicationInstance.context)
​
myFilter.next = applicationInstance.inboundRoot
applicationInstance.inboundRoot = myFilter
Generally in Prudence, you would want to filter either "dynamicWeb," "staticWeb," "resources" or "applicationInstance.inboundRoot." However, some applications may be using other resources and have more complex routing schemes, allowing you to install filters at many more places.
Custom Filters
Prudence lets you create your own custom filters. The API for routing through them is almost identical to the above shortcut, except that, instead of a filter instance, you use the internal URI of the handler in the application's /handlers/ subdirectory. For example:
dynamicWeb = router.filterBase(dynamicWebBaseURL, '/remove-foul-language/', applicationInstance.context, dynamicWeb).next
Note how we gain access to the created filter instance via the "next" attribute of the created route.
The above is a useful shortcut, but in some cases you may want more direct access to the filter instance. You can explicitly create a DelegatedFilter instance. For example, the above is equivalent to:
importClass(com.threecrickets.prudence.DelegatedFilter)
​
var cleanupFilter = new DelegatedFilter(null,
	'/remove-foul-language/', filtersDocumentSource, languageManager)
​
router.filterBase(dynamicWebBaseURL, cleanupFilter, dynamicWeb)
​
dynamicWeb = cleanupFilter
Custom filters can also be used for dynamic capturing.
Built-In Filters
Prudence comes with a few useful filters: CacheControlFilter, JavaScriptUnifyMinifyFilter, and CssUnifyMinifyFilter.
Filtering for Authentication
Though you can implement an authentication system of your own via custom filters, Restlet comes with a few useful ones.
In this example, we'll filter our entire application through a simple HTTP challenge authenticator. Here's the "routing.js":
importClass(
	org.restlet.security.MapVerifier,
	org.restlet.security.ChallengeAuthenticator,
	org.restlet.data.ChallengeScheme)
​
// Our authenticator will use this simple verifier
// (better verifiers will load the user/password table from storage, our verify against
// network services, such as LDAP)
var verifier = new MapVerifier()
verifier.localSecrets.put('theusername', new java.lang.String('thepassword').toCharArray())
​
// The HTTP challenge authenticator is a filter
var authenticator = new ChallengeAuthenticator(applicationInstance.context, ChallengeScheme.HTTP_BASIC, 'You must log in!')
authenticator.verifier = verifier
​
// Filter root through authenticator
authenticator.next = applicationInstance.inboundRoot
applicationInstance.inboundRoot = authenticator

URI Patterns

The custom routing techniques described above support URI patterns, which are URIs with optional curly-bracket-delimited variable names that are replaced by values per every client request. The use of one pattern can thus refer to many URIs.
Note that the same system is used to generate cache keys, and that some variables can only be used for cache keys. In fact, many of these variables might be more useful for cache keys than for routing.
For more information, see the Restlet Resolver documentation.

Data Attributes

All these refer to the data ("entity") sent by the client or that you are returning to the client. Lowercase is used for request attributes, uppercase for response attributes. We'll note these as pairs:
  • {es} or {ES}: entity size in bytes
  • {emt} or {EMT}: entity media type
  • {ecs} or {ECS}: entity character set
  • {el} or {EL}: entity language
  • {ee} or {EE}: entity encoding
  • {et} or {ET}: entity tag (HTTP ETag)
  • {eed} or {EED}: entity expiration date
  • {emd} or {EMD}: entity modification date

Request Attributes

  • {d}: date (Unix timestamp)
  • {m}: the method (in HTTP, it would be "GET," "POST," "PUT," "DELETE," etc.); see example for handling HTTP forms
  • {cia}: client IP address
  • {ciua}: client upstream IP address (if the request reached us through an upstream load balancer)
  • {cig}: client agent name

Response Attributes

  • {S}: the status code
  • {SIA}: server IP address
  • {SIP}: server port number
  • {SIG}: server agent name

URIs

We'll use a hyphen to show that you need to add one of the modifiers detailed after this list. For example, "{ri}" is the complete actual URI.
  • {p}: the protocol ("http," "https," "ftp," etc.)
  • {r-} or {R-}: actual URI (the capital "R" here refers to the response, which may be different from the request if you're redirecting)
  • {h-}: virtual host URI
  • {o-}: the application's root URI on the virtual host
  • {f-}: the referring URI (usually means that the client clicked a hyperlink or was redirected here)
Add the following modifiers to URI values above in order to access the various parts of the URI:
  • {-i}: the complete URI
  • {-a}: the authority (for URLs, this is the host or IP address)
  • {-h}: the host identifier (protocol + authority)
  • {-p}: the path
  • {-q}: the query
  • {-f}: the fragment
  • {-r}: the remaining part of the path
  • {-e}: the part of the path relative to the application's root URI
Every URI also has a "base" URI, accessed via the "b" modifier. Usually, this is the application's root URI on the virtual host. You can add the URI modifiers above to this URI. For example: "{rbi}".

Cache Key Variables

The following variables are only available for cache key patterns, not for routing:
  • {dn}: the document name (full path from the Prudence instance root)
  • {an}: the application name
  • {ptb}: the path to the base, identical to conversation.pathToBase

conversation.locals

Variables that aren't any of the above will be assumed to be conversation.locals. You can thus inject any data you wish into a pattern.
This feature is especially useful with: