Persevere stores structured data in the same way data is represented in JavaScript and JSON. Various different data types can be persisted including numbers, strings, booleans, objects, arrays, dates, functions, and even binary data. Most of these data types are familiar to JavaScript developers; however, binary data is usually not seen in JavaScript. Support for binary data opens up a powerful realm of application capabilities by allowing data such as images, audio, and video to be stored directly in the Persevere database and referenced by JavaScript/JSON style data structures.
Persevere includes a special class/table called File
designed to hold various binary and text files. Each File
instance can be referenced like any other object in Persevere. There are several options for creating new File
instances and sending binary data to Persevere for storage. Persevere supports several very RESTful mechanisms that follow the protocol of the HTTP specification (RFC 2616). This includes directly POSTing a binary file to the File table, using PUT to add a file as a property of an existing JSON object, and using PUT to add an alternate representation of a JSON object in Persevere. Persevere then properly handles HTTP-specified content negotiation to deliver the correct representation of an object, whether it be application/json or a binary MIME type.
While these approaches correctly follow existing standards, it is not possible to get the contents of a binary file using browser-based JavaScript in a web application to do a PUT or POST request. These approaches work great for other code that is not running in the browser (such as another server), but within the browser we must use another technique. Fortunately, Persevere provides a fourth mechanism that is designed for uploading binary files through the browser’s file input element. This functionality makes it possible to build applications where users can upload files as part of the application flow.
To understand how to use this capability, we will look at an example application. We will create a button that allows us to upload a new image for a product object.
<form action="/Product/1" method="POST" enctype="multipart/form-data">
<input type="file" name="picture" size="40" />
<input type="submit" value="Upload Image" />
</form>
When a user selects a file and submits it with this form, the file will be uploaded to Persevere, and the file will be set as the value of the picture
property of the Product with an id of 1
. Consequently, once the file is uploaded we can access the file with the URL of /Product/1.picture
. If the user uploaded an image, we can now show that image in a page with the following HTML:
<img src="/Product/1.picture" />
Of course if you are using Persevere, you probably won’t be using static object id references in your HTML. Naturally, you can set the target URL (action
property) of the form with JavaScript:
myForm.action = productId;
With the HTML for the file upload form defined above, when the user submits the form, Persevere will simply return a blank page. This is probably not desirable. This can easily be handled by creating an invisible iframe as the target of the page. We can then add an onload
for the iframe so we can be notified when the file is uploaded:
<form target="uploadTarget" action="/Product/1"
method="POST" enctype="multipart/form-data">
<input type="file" name="picture" size="40" />
<input type="submit" value="Upload Image" />
</form>
<iframe style="display:none" name="uploadTarget" onload='onFileUploaded()'></iframe>
You can then define a function onFileUploaded
that will be called when the file is finished uploading. Now, when a user chooses a file and submits it, the onFileUploaded
will be called so the application can carry on the next step after uploading.
CRSF Safety
Persevere includes robust secure protection against cross-site request forgery (CSRF). Because form submissions can be triggered from any site to one of your server’s URLs, Persevere will only process the request with the currently logged in user’s authorization level if it is certain that the user authorized the request. Persevere can verify the request if the Referer
header has a URL that corresponds to the Host
header. However, not all users’ requests will have a Referer
as some proxies strip this header. In order to ensure that the form submission works for all users, the request must be verifiable by including a client_id query parameter that matches a predefined client id for the connection to Persevere with the Client-Id
header. This client id can be established by generated a random client id number and sending it to the server with an XHR request:
clientId= Math.random();
var xhr = new XMLHttpRequest();
xhr.open("GET","/Product/",true);
xhr.setRequestHeader("Client-Id", clientId);
xhr.send(null);
If you are using Dojo on the front-end, it should automatically be sending the Client-Id
header on all requests. Therefore the client id can be accessed from the property dojox.rpc.Client.clientId
:
clientId = dojox.rpc.Client.clientId;
Now, to use this client id to verify the form submission, we can add it to the target URL:
myForm.action = productId + "?client_id=" + clientId;
This will ensure that the request is properly authorized for all users, while allowing Persevere to properly protect against any CSRF attacks on your site.
An alternate demonstration of this mechanism can be seen in the friends example in Persevere’s examples
directory. In this example application, you can see a simple front-end for a database of friends. The front-end includes a form for adding an image for each of your friends, and this image is displayed in the listing of friends.
Getting More Information about a Binary Resource
In addition to using a binary file’s URL to show images, you can also interact with the file. The file object includes contentType
and content
properties. The contentType
property holds the MIME type of the object. The content
is a binary object that holds the byte data for the object. If an image has been uploaded for the Product instance with an id of 1
, from the server you could run:
var file = load("Product/1.picture");
file.contentType -> "image/jpeg"
Storing Binary Data in Persevere
Persevere includes several mechanisms for storing and retrieving data. These mechanisms include PUT and POSTs based on the HTTP specification for creating and updating resources, and allows for defining alternate MIME types of existing resources. In addition, Persevere also includes special support for browser-based form submission of binary data to facilitate Ajax-style applications that wish to provide a means for uploading data from the browser to the server.