Hosted by Three Crickets

Prudence
Scalable REST Platform
For the JVM

Prudence logo: bullfinch in flight

Web Data

This chapter deals with sending and receiving data to and from the client (as well as external servers) via REST, focusing especially on the particulars for HTTP and HTML. It does not deal with storing data in backend databases.
Prudence is a minimalist RESTful platform, not a data-driven web framework, though such frameworks are built on top of it. Check out our Diligence, which is a full-blown framework based on Prudence and MongoDB. You may also be interested in the Model-View-Controller (MVC) chapter, which guides you through an approach to integrating data backends.

URLs

The simplest way in which a client sends data to the server is via the URL. The main part of the URL is parsed by Prudence and used for routing, but some of it is left for your own uses.
Generally, the whole or parts of the request URL can be accessed via the conversation.reference API.

Query Parameters

This is the matrix of parameters after the "?" in the URI.
For example, consider this URL:
http://mysite.org/myapp/user?name=Albert%20Einstein&enabled=true
Note the "%20" URI encoding for the space character. Query params will be automatically decoded by Prudence.
In JavaScript, you can use Prudence.Resources.getQuery API:
document.require('/prudence/resources/')
​
var query = Prudence.Resources.getQuery(conversation, {
	name: 'string',
	enabled: 'bool'
})
In the case of multiple params with the same name, the API would return the first param that matches the name. Otherwise, you can also retrieve all values into an array:
var query = Prudence.Resources.getQuery(conversation, {
	name: 'string[]',
	enabled: 'bool'
})
Low Level
For non-JavaScript you can use the lower-level conversation.query API:
var query = {
	name: conversation.query.get('name'),
	enabled: conversation.query.get('enabled') == 'true'
}
Use conversation.queryAll if you need to find multiple params with the same name.

Captured Segments

Variables in the URI template you configured in routing.js will be captured into conversation.locals. Note that you can also interpolate the captured variables into the target URI.

The Wildcard

If you've configured a URI template with a wildcard in routing.js, you can access the "*" value using conversation.wildcard. Note that you can also interpolate the wildcard into the target URI.

Fragments

This is whatever appears after the "#" in the URI. Note that for the web fragments are only used for response URLs: those sent from the server to the client. This is enforced: web browsers will normally strip fragments before sending URLs to the server, but the server can send them to web browsers. They are commonly used in HTML anchors:
<a name="top" /><h1>This is the top!</h1>
<p>Click <a href="#top">here</a> to go to the top</p>
But you can also use them in redirects:
conversation.redirectSeeOther(conversation.base + '#top')

Request Payloads

These are used in "POST" and "PUT" verbs.
In JavaScript, you can use Prudence.Resources.getEntity API to extract the data in various formats:
document.require('/prudence/resources/')
​
var data = Prudence.Resources.getEntity(conversation, 'json')
Otherwise, you can use the lower-level conversation.entity API:
document.require('/sincerity/json/')
​
var text = conversation.entity.text
var data = Sincerity.JSON.from(text)
Note that if the payload comes from a HTML "post" form, better APIs are available.

MIME Types

If you wish to support multiple request payload MIME types, be sure to check before retrieving:
var type = conversation.entity.mediaType.name
if (type == 'application/json') {
	var data = Prudence.Resources.getEntity(conversation, 'json')
	...
} else if (type == 'image/png') {
	var data = Prudence.Resources.getEntity(conversation, 'binary')
	...
}

Consumption

Note that you can only retrieve the request payload once. Once the data stream is consumed, its data resources are released. Thus, the following would result in an error:
print(conversation.entity.text)
print(conversation.entity.text)
The simple solution is retrieve once and store in a variable:
var text = conversation.entity.text
print(text)
print(text)

Parsing Formats

Which formats can Prudence parse, and how well?
This depends on which programming language you're using: for example, both Python and Ruby both come with basic JSON support in their standard libraries, and Python supports XML, as well. Sincerity provides JavaScript with support for both. Of course, you can install libraries that handle these and other formats, and even use JVM libraries.
For other formats, you may indeed need to add other libraries.
A decent starting point is Restlet's ecosystem of extensions, which can handle several data formats and conversions. However, these are likely more useful in pure Java Restlet programming, where they can plug into Restlet's sophisticated annotation-based conversion system. In Prudence, you will usually be applying any generic parsing library to the raw textual or binary data. Still, the Restlet extensions are useful for response payloads.

Cookies

Cookies represent a small client-side database, which the server can use to retrieve or store per-client data. Not all clients support cookies, and even those that do (most web browsers) might have the feature disabled, so it's not always a good idea to rely on cookies.

From the Client

Retrieve a specific cookie from those the client sent you according to its name using conversation.getCookie:
var session = conversation.getCookie('session')
if (null !== session) {
	print(session.value)
}
Or use conversation.cookies to iterate through all available cookies.
The following attributes are available:

To the Client

You can ask that a client modify any of the cookies you've retrieved from it, upon a successful response, by calling the "save" method:
var session = conversation.getCookie('session')
if (null !== session) {
	session.value = 'newsession'
	session.save()
}
Ask the client to create a new cookie using conversation.createCookie:
var session = conversation.createCookie('session')
session.value = 'newsession'
session.save()
Note that createCookie will retrieve the cookie if it already exists.
When sending cookies to the client, you can set the following attributes in addition to those mentioned above, but note that you cannot retrieve them later:
You can ask the client to delete a cookie by calling its "remove" method. This is identical to setting maxAge=0 and calling "save".

Security Concerns

You should never store any unencrypted secret data in cookies: though web browsers attempt to "sandbox" cookies, making sure that only the server ("domain") that stored them can retrieve them, they can be hijacked by other means. Better yet, don't store any secrets in cookies, even if encrypted, because even encryptions can be hacked. A cautious exception can be made for short-term secrets: for example, if you store a session ID in a cookie, make sure to expire it on the server so that it cannot be used later by a hacker.
A separate security concern for users is that cookies can be used to surreptitiously track user activity. This works because any resource on a web page—even an image hosted by an advertising company—can use cookies, and can also track your client's IP address. Using various heuristics it is possible to identify individual users and track parts of their browser history.
Because of these security concerns, it is recommended that you devise a "cookie policy" for users and make it public, assuming you require the use of cookies for your site. In particular, let users know which 3rd-party resources you are including in your web pages that may be storing cookies, and for what purpose.
Cookies are a security concern for you, too: you cannot expect all your clients to be standard, friendly web browsers. Clients might not be honoring your requests for cookie modifications, and might be sending you cookies that you did not ask them to store.
Be careful with cookies! They are a hacker's playground.

Custom Headers

The most commonly used request and response HTTP headers are supported by Prudence's standard APIs. For example: conversation.disposition, conversation.maxAge and conversation.client. However, Prudence also let's you use other headers, including your custom headers, via conversation.requestHeaders and conversation.responseHeaders. Note that you must use Prudence's standard APIs for headers if such exist: these APIs will only work for additional headers.
These APIs both return a Series object. (You usually won't need to access the elements directly, but in case you do: they are Header objects.) An example of fetching a request header:
var host = conversation.requestHeaders.getFirstValue('Host')
An example of setting a response header:
conversation.responseHeaders.set('X-Pingback', 'http://mysite.org/pingback/')

Redirection

Client-side redirection in HTTP is handled via response headers.

By Routing

If you need to constantly redirect a specific resource or a URI template, you should configure it in your routing.js, using the "redirect" route type:
app.routes = {
	...
	'/images/*': '>/media/{rw}'
}
Note that in this example we interpolated the wildcard.

By API

You can also redirect programmatically by using the conversation.redirectPermament, conversation.redirectSeeOther or conversation.redirectTemporary APIs:
conversation.redirectSeeOther(conversation.base + '/help/')
Note that if you redirect via API, the client will ignore the response payload if there is one.

In HTML

We're mentioning this here only for completion: via HTML, redirection is handled entirely in the web browser, with no data going to/from the server. A template resource example:
Go <a href="<%.%>/elsewhere/">elsewhere</a>.

Server-Side Redirection

In Prudence, this is called "capturing" and has particular use cases. (It can indeed be confusing that this functionality is often grouped together with client-side redirection.)

HTML Forms

HTML's "form" tag works in two very different modes, depending on the value of its "method" param:
Example form:
<form action="<%.%>/user/" method="post">
	<p>Name: <input type="text" name="name"></p>
	<p>Enabled: <input type="radio" name="enabled" value="true"></p>
	<p>Disabled: <input type="radio" name="enabled" value="false"></p>
	<p><button type="submit">Send</button></p>
</form>
In JavaScript, you can use Prudence.Resources.getForm API:
document.require('/prudence/resources/')
​
var form = Prudence.Resources.getForm(conversation, {
	name: 'string',
	enabled: 'bool'
})
In the case of multiple fields with the same name, the API would return the first fields that matches the name. Otherwise, you can also retrieve all values into an array:
var form = Prudence.Resources.getForm(conversation, {
	name: 'string[]',
	enabled: 'bool'
})
Low Level
For non-JavaScript you can use the lower-level conversation.form API family:
var form = {
	name: conversation.form.get('name'),
	enabled: conversation.form.get('enabled') == 'true'
}
Use conversation.formAll if you need to find multiple fields with the same name.

Accepting Uploads

HTML supports file uploads using forms and the "file" input type. However, the default "application/x-www-form-urlencoded" MIME type for forms will not be able to encode files, so you must change it to "multipart/form-data". For example:
<form action="<%.%>/user/" method="post" enctype="multipart/form-data">
	<p>Name: <input type="text" name="name"></p>
	<p>Upload your avatar (an image file): <input name="avatar" type="file" /></p>
	<p><button type="submit">Send</button></p>
</form>
Prudence has flexible support for handling uploads: you can configure them to be stored in memory, or to disk. See the application configuration guide.
You can access the uploaded data using the conversation.form API family. Here's a rather sophisticated example for displaying the uploaded file to the user:
<%
var name = conversation.form.get(name')
var tmpAvatar = conversation.form.get('avatar').file
​
// The metadata service can provide us with a default extension for the media type
var mediaType = conversation.form.get('avatar').mediaType
var extension = application.application.metadataService.getExtension(mediaType)
​
// We will put all avatars under the "/resources/avatars/" directory, so that they
// can be visible to the world
var avatars = new File(document.source.basePath, 'avatars')
avatars.mkdirs()
var avatar = new File(avatars, name + '.' + extension)
​
// Move the file to the new location
tmpAvatar.renameTo(avatar)
%>
<p>Here's the avatar you uploaded, <%= name %></p>
<img src="<%.%>/avatars/<%= avatar.name %>" />

Response Payloads

This section is mostly applicable to manual resources, although it can prove useful to affect the textual payloads of template resources. For static resources, the response payloads are of course the contents of the resource files.
Two important notes:

Textual and Binary Payloads

Template resources might seem to always return textual payloads. Actually, by default they will negotiate a compression format, which if selected will result in a binary: the compressed version of the text. But all of that is handled automatically by Prudence for that highly-optimized use case.
For manual resources, you can return any arbitrary payload by simply returning a value in handleGet, handlePost or handlePut. Both strings and JVM byte arrays are supported. A textual example:
function handleGet(conversation) {
	return 'My payload'
}
A binary example:
document.require('/sincerity/jvm/')
​
function handleGet(conversation) {
	var payload = Sincerity.JVM.newArray(10, 'byte')
	for (var i = 0; i < 10; i++) {
		payload[i] = i
	}
	return payload
}
Note that if you return a number, it will be treated specially as an HTTP status code. If you wish to return the number as the content of a textual payload, simply convert it to a string:
function handleGet(conversation) {
	return String(404)
}
If you wish to set both the payload and the status code, use an API for either one. Here well use the conversation.status API family. Note that if your status code is an error status code, you'll also want to bypass this error page using conversation.statusPassthrough:
function handleGet(conversation) {
	conversation.statusCode = 404
	conversation.statusPassthrough = true
	return 'Not found!'
}
function handleGet(conversation) {
	conversation.setResponseText('Not found!', null, null, null)
	conversation.statusPassthrough
	return 404
}
Binary Streaming
Streaming using background tasks is not directly supported by Prudence as of version 2.0. However, this feature is planned for a future version, depending on support being added to Restlet.

Restlet Data Extensions

Instead of returning a string or a byte array, you can return an instance of any class inheriting from Representation. Restlet comes with a few basic classes to get you started. Here's a rather boring example:
function handleGet(conversation) {
	return new org.restlet.representation.StringRepresentation('My payload')
}
Where Restlet really shines is in its ecosystem of extensions, which can handle several data formats and conversions. For these extensions to work, you will need to install the appropriate library in your container's "/libraries/jars/" directory, as well as all dependent libraries. Please refer to the Restlet distribution for complete details.
Note that you can also set the response via the conversation.response.entity API:
var payload = new org.restlet.representation.StringRepresentation('My payload')
conversation.response.entity = payload
Or via the conversation.setResponseText API shortcut:
conversation.setResponseText('My payload', null, null, null)

Overriding the Negotiated Format

The response payload's MIME type and language have likely been selected for you automatically by Prudence, via HTTP content negotiation, based on the list of preferences you set up in handleInit. However, it's possible to override these values via the conversation.mediaType and conversation.language API families. This should be done sparingly: content negotiation is the preferred RESTful mechanism for determining the response format, and the negotiated values should be honored. However, it could be useful and even necessary to override it if you cannot use content negotiation, which might be the case if your clients don't support it, and yet you still want to support multiple formats.
In this example, we'll allow a "format=html" query param to override the negotiated MIME type:
function handleInit(conversation) {
	conversation.addMediaTypeByName('text/html')
	conversation.addMediaTypeByName('text/plain')
}
​
function handleGet(conversation) {
	if (conversation.query.get('format') == 'html') {
		conversation.mediaTypeName = 'text/html'
	}
	return conversation.mediaTypeName == 'text/html' ?
		'<html><body>My page</body></html>' :
		'My page'
}
An example of overriding the negotiated language:
function handleInit(conversation) {
	conversation.addMediaTypeByNameWithLanguage('text/html', 'en')
	conversation.addMediaTypeByNameWithLanguage('text/html', 'fr')
}
​
function handleGet(conversation) {
	if (conversation.query.get('language') == 'fr') {
		conversation.languageName = 'fr'
	}
	if (conversation.languageName == 'fr') {
		...
	}
	else {
		...
	}
}
Note that these APIs works just as well for template resources, though again content negotiation should be preferred.
Under the Hood
When the MIME type is "application/internal", Prudence is actually wrapping your return value in an InternalRepresentation. You can also construct it explicitly:
return new com.threecrickets.prudence.util.InternalRepresentation(data)
Note that, of course, if you return an instance of a class inheriting from Representation, Prudence will detect this and not wrap it again in an InternalRepresentation.

Browser Downloads

You can create browser-friendly downloadable responses using the conversation.disposition API. Here's an example using a manual resource:
function handleInit(conversation) {
	conversation.addMediaTypeByName('text/csv')
}
​
function handleGet(conversation) {
	var csv = 'Item,Cost,Sold,Profit\n'
	csv += 'Keyboard,$10.00,$16.00,$6.00\n'
	csv += 'Monitor,$80.00,$120.00,$40.00\n'
	csv += 'Mouse,$5.00,$7.00,$2.00\n'
	csv += ',,Total,$48.00\n'
	conversation.disposition.type = 'attachment'
	conversation.disposition.filename = 'bill.csv'
	return csv
}
Most web browsers would recognize the MIME type and ask the user if they would prefer to either download the file with the suggested "bill.csv" filename, or open it in a supporting application, such as a spreadsheet editor.
Note that the disposition is not cached. If you wish to use this feature, you need to disable caching on the particular resource.

External Requests

Prudence uses the Restlet library to serve RESTful resources, but can also use it to consume them. In fact, the client API nicely mirrors the server API.
Note that Prudence can also handle internal REST requests without going through HTTP or object serialization. There is an entire internal URI-space at your fingertips.
It's not a good idea to send an external request while handling a user request, because it could potentially cause a long delay and hold up the user thread. It would be better to use a background task. A possible exception is requests to servers that you control yourself, and that represent a subsystem of your application. In that case, you should still use short timeouts and fail quickly and gracefully.
For our examples, let's get information about the weather on Mars from MAAS.
In JavaScript, you can use the powerful Prudence.Resources.request API:
document.require('/prudence/resources/')
var weather = Prudence.Resources.request({
	uri: 'http://marsweather.ingenology.com/v1/latest/',
	mediaType: 'application/json'
})
if (null !== weather) {
	print('The max temperature on Mars today is ' + weather.report.max_temp + ' degrees')
}
The API will automatically convert the response according to the media type. In this case, we requested "application/json", so the textual response will be converted from JSON to JavaScript native data. The API will also automatically follow redirects.
Payloads sent to the server, for the "POST" and "PUT" verbs, are also automatically converted:
var newUser = Prudence.Resources.request({
	uri: 'http://mysite.org/user/newton/',
	method: 'put',
	mediaType: 'application/json',
	payload: {
		type: 'json',
		value: {
			name: 'Isaac',
			nicknames: ['Izzy', 'Zacky', 'Sir']
		}
	}
})
Read the API documentation carefully, as it supports many useful parameters.
Low Level
For non-JavaScript you can use the lower-level document.external API:
document.require('/sincerity/json/')
var resource = document.external('http://marsweather.ingenology.com/v1/latest/', 'application/json')
result = resource.get()
if (null !== result) {
	weather = Sincerity.JSON.from(result.text)
	print('The max temperature on Mars today is ' + weather.report.max_temp + ' degrees')
}

Timeout

Surprisingly, you cannot set the timeout per request, but instead you need to configure the timeout globally for the HTTP client. This is due to a limitation in Restlet that may be fixed in the future.

Secure Requests

These APIs support secure requests to "https:" servers. Such requests rely on the JVM's built-in authorization mechanism. Like most web browsers, the JVM recognizes the common Internet certificate authorities. This means that if you're using your own self-created keys, that don't use an approved certificate, you need to specify these keys via a "trust store" for the JVM. For an example, see secure servers in the configuration chapter.

RESTful Files

The same APIs can be used to easily access resources via the "file:" pseudo-protocol. Let's read a JSON file:
var data = Prudence.Resources.request({
	file: '/tmp/weather.json',
	mediaType: 'application/json'
})
The above is simply a shortcut to this:
var data = Prudence.Resources.request({
	uri: 'file:///tmp/weather.json',
	mediaType: 'application/json'
})
You can even "PUT" new file data, and "DELETE" files using this API.

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