Tutorial
The source code for the examples in the tutorial is available for download.
Up and Running
All you need to run Prudence is a JVM. Version 7 or above is recommended, though version 6 can also be supported. You do not need the "Enterprise Edition" nor even a "JDK": Prudence needs only the basic JVM runtime.
There are two ways to get Prudence: the easiest way is to download the distribution, which includes most everything and is ready to rumble. Let's do that now.
But Prudence is modular: later on, you may prefer to assemble your own distribution using the Sincerity packaging and bootstrapping tool.
The Command Line
Actually, the distribution you downloaded comes bundled with Sincerity, which we will use to start Prudence.
Before we do that, let's quickly familiarize ourselves with Sincerity. Open a command terminal and change to the Prudence directory. We'll call it our "container." If you're using a *nix operating system, you can run the command as "./sincerity". In Windows it's "sincerity.bat".
To see a list of all Sincerity commands:
sincerity help
For example, let's print out all installed modules:
sincerity dependencies
If your operating system supports GUIs, you can start Sincerity without any command to start its GUI (or use the "gui" Sincerity command):
sincerity
The GUI is useful for visualization, but generally the CLI is better for getting things done. We'll be using the CLI in this tutorial.
Ready… Set…
Go!
sincerity start prudence
The command will print out some version information, tell you which applications are installed, and then hold. You should now be able to point your browser to http://localhost:8080 in order to see the Prudence administration application. Or, just go right to http://localhost:8080/stickstick/ and http://localhost:8080/prudence-example/ to see the included examples. Stickstick is an example of an "AJAX" application, implementing sticky notes shared on the web and saved to a relational (SQL) database. It comes with the H2 database, so it does not require a separate database server. The Prudence Example is more general, walking you through Prudence's basic features in all supported programming languages.
While playing around with the examples, you may also want to look at the logs: they are in the container's "/logs/" directory. "web.log" is an NCSA-style log of all web requests. Logs prefixed with "prudence-" are created per application, and "common.log" is used for everything else. The logs will automatically roll, so don't worry about them overflowing your storage.
To quit Prudence, press CTRL+C.
Further Exploration
- Prudence is built on top of Sincerity's Restlet skeleton. The documentation there applies to Prudence, too.
- Sincerity has its own tutorial.
- Of course, you should not run Prudence in a terminal for deployed systems. Instead, you can run Prudence as a robustly monitored system service/daemon.
- The logging system is especially robust. You can use it to centralize logging for your cluster, and even log directly to a database, such as MongoDB.
First Steps
You've played with the example applications, and now it's time to create your own. Let's make a basic CMS (Content Management System), a web site where users can change the content of pages and create new ones via the web!
Create a new application using the "prudence" Sincerity command:
sincerity prudence create cms
Restart Prudence, and you should be able to see your new application at http://localhost:8080/cms/. There's not much to see at this point.
Our Application's Subdirectory
Let's take a look at our application. Its files are all under "/component/applications/cms/".
At the root are three JavaScript files used to configure the application. Take a look at "routing.js" and "setting.js". The former defines your application's URI-space, while the latter controls its general behavior. Note that Prudence's (and Sincerity's) configuration philosophy is to use JavaScript wherever possible, which allows for dynamic, flexible configurations that adapt to the environment in which they are run.
Resources
The "/resources/" subdirectory is mapped directly to your URI-space, just like most standard web servers. For example, a file named "/mydir/myfile.html" will be mapped to http://localhost:8080/cms/mydir/myfile.html. Also like standard web servers, "index.html" will be mapped to the directory itself.
Actually, you'll notice that our root index is named "index.t.html". We call that "t" a pre-extension: here, the "t" stands for "template," and tells Prudence that this is a text file that may include one or more sections of code called "scriptlets," which are delimited by "<%" and "%>". This, again, may be familiar to you: it's similar to how PHP, JSP and ASP work.
Under "/resources/style/" you'll see "site.less". LESS is a powerful extended CSS language: Prudence will automatically recognize the ".less" extension and create the CSS for us.
Libraries
Our reusable code is all under the "/libraries/" subdirectory. Specifically, "/libraries/includes/" contains fragments that can be included into our template pages. You'll see that our "index.t.html" uses them. For example:
<%& '/header/' %>
…includes the file "/libraries/includes/header.html". Note that included files may have scriptlets in them (and can be cached independently).
All the above might seem quite familiar to you, and this is by intention. But the files under "/libraries/dispatched/" may seem strange: they are "low-level" direct implementations of encapsulated RESTful resources. Take a look at "example.js".
Moreover, these kinds of RESTful resources are not directly mapped to URIs like those in the "/resources/" subdirectory. Instead, they are "dispatched" using unique IDs. In Prudence, this paradigm is called "URI/resource separation," and it allows you full flexibility in structuring your code vs. structuring your URI-space.
We'll get into all that as we go along this tutorial.
Scriptlets
Let's look more closely at our "index.t.html" page. The scriptlets are executed, while what's outside the scriptlets is simply output to the HTTP response. You can freely mix regular output and scriptlets:
<% for (var x = 0; x < 10; x++) { %> <p>These are 10 paragraphs.</p> <% } %>
There are also a few shortcut scriptlets. For example, to output an expression:
<%= x*2 %>
The above is equivalent to:
<% print(x*2) %>
Also useful is the inclusion scriptlet, which we've already discussed:
<%& '/header/' %>
The above is equivalent to:
<% document.include('/header/'); %>
Behind the scenes, Prudence parses "index.t.html" and turns it into JavaScript source code. If you'd like to see the generated code, look at the Scripturian cache, specifically under the container's "/cache/scripturian/container/component/applications/cms/resources/" directory. These generated files are not actually used by Prudence, and only available for debugging purposes. Disable their creation by configuring "debug" mode to false.
APIs
That "document" namespace mentioned above is part of the rich, well-documented API provided by Prudence. Other useful namespaces are "conversation," for accessing the request/response, and "application" for shared application-wide services. The complete API reference is available online, though it can be daunting to start there. Instead, continue reading through this tutorial, where we'll explain APIs as we go along, and eventually hit the manual: especially important is the web data chapter, which goes over much of the interaction with clients.
And remember that you're running on the JVM: the entire JVM standard library is available for you, as well as any other JVM library you install in your container (using Sincerity or manually).
Hello, World
Let's create a new page for our CMS, under our application's "/resources/" subdirectory. We'll call it "page.t.html". We'll start simple:
<html> <body> <p>Hello, world. This page will become editable very soon!</p> </body> </html>
Browse to http://localhost:8080/wiki/page/ to see it. Note that you do not need to restart Prudence if you're only adding or changing pages: Prudence will pick up these changes on-demand, on-the-fly, and make sure to compile and recompile as necessary.
You'll notice that even though the file has a ".t.html" extension, Prudence does not use the extension for the URL. This is because that extension is entirely an implementation concern on your end: there's no reason for the user to have to see it, nor to have it clutter the URL.
Also notice the trailing slash: it's "page/", not "page". Prudence enforces trailing slashes. This allows your relative URLs to be clear and unambiguous. For example, to insert an image in our page, we could do this:
<img src="../images/logo.png" />
Without a trailing slash, you would not need the "../", but with the trailing slash, it's necessary. If you don't pick one of the two options, handling relative URLs quickly becomes messy. So why have we decided to standardize on trailing slashes? Because it gives you more flexibility: for example, instead of saving our page "page.t.html", we could have saved it as "page/index.t.html", and the URL would be identical. As you will see, the trailing slash principle is used throughout Prudence to allow many such conveniences, for both external URLs and for internal library URIs. You'll see us constantly ending our URIs in slashes.
Further Exploration
- Learn how to create your own application templates for the "prudence create" command.
- Learn how to configure your application via its settings.js file.
- Learn about the difference between the "resource mapping" and "URI/resource separation" paradigms.
Let's Make a CMS
It's time to flesh out out "page.t.html":
<html> <body> <% document.require('/prudence/resources/') if (conversation.request.method.name == 'POST') { var form = Prudence.Resources.getForm(conversation, {content: 'string'}) var content = form.content application.globals.put('page', content) } else { var content = application.globals.get('page') || 'This page is empty.' } %> <div style="border: 1px solid black; padding: 5px 5px 5px 5px"> <%= content %> </div> <form method="post"> <p>Edit this page:</p> <p><textarea name="content" cols="80" rows="20"><%= content %></textarea></p> <p><button type="submit">Submit</button></p> </form> </body> </html>
Not the most exciting CMS, but it works for editing a single page. Let's break it down:
- document.require is how we import libraries. (At least for JavaScript: other languages have other import facilities, which you can use instead.) The API will attempt to import libraries from your own application's "/libraries/" subdirectory first, and if not found there, will use the container's "/libraries/scripturian/" directory next. You'll notice that we use a trailing slash in the library URI. The library URIs also match the JavaScript namespaces: "/prudence/resources/" matches Prudence.Resources. This particular namespace is very useful for working with web data. In this case we're using Prudence.Resources.getForm.
- We're using the conversation.request API to find out if we're in an HTTP "POST". If so, we will extract the "content" form field (as simple text). We will then save it as an "application.global". These globals belong to the entire running application, and can be accessed from any page, for any request.
- We then go on to display the content as well as the HTML form for editing the content.
Infinite Editable Pages
The above is just a single page: let's multiply it by infinity.
To do this, we'll configure our URI-space by editing the application's routing.js file. To the "app.routes" dict, let's add the following:
app.routes = { ... '/page/*': '/page/' }
What this does is "capture" all incoming URLs that begin with "/page/" to exactly our "/page/". The "*" at the end of a URI template signifies a wildcard: anything may follow (except nothing). Restart Prudence for our routing.js changes to take effect.
You'll see that now, indeed, any URL beginning with "/page/" will take us to our single, lonely CMS page. For example, this: http://localhost:8080/cms/page/test/. If you edit one page, the content of all pages would change.
Let's edit our template in "page.t.html" to make it differ per incoming URL:
<% document.require('/prudence/resources/') var id = 'page.' + conversation.wildcard if (conversation.request.method.name == 'POST') { var form = Prudence.Resources.getForm(conversation, {content: 'string'}) var content = form.content application.globals.put(id, content) } else { var content = application.globals.get(id) || 'This page is empty.' } %>
And… that's it. Now any URL beginning with "/page/" becomes an editable CMS page. If that page does not exist it, it will be created. Note that we're using "page." as a prefix for our application globals in order to make sure they don't overlap with globals used for other purposes.
The only real "trick" here is the conversation.wildcard API, which gives us the captured wildcard we configured in routing.js.
Further Exploration
- Read more about HTML forms.
- We've barely scratched the surface of what's possible with routing.js. See the URI-space chapter for full details.
- There's also, of course, a lot more you can do with templates, such as reusing fragments and template inheritance.
- Are you a fan of MVC (Model-View-Controller)? It's possible to treat template pages as "views," and moreover you can integrate other templating technologies, such as Jinja2. There's a whole, very detailed chapter about it.
A Persistent CMS
So far in this tutorial, we've been storing the CMS data in RAM. To store the data on disk, we have a vast array of options: you can use any JVM database driver directly from Prudence.
For this tutorial, we'll be using MongoDB: a document-oriented database with many useful features, such as atomic operations, aggregation and map/reduce, and support for flexible horizontal scalability configurations. It uses JavaScript internally, and thus is a natural fit for web programming in Prudence: you can use JavaScript on the server, JavaScript on the client, and JavaScript in the database. This combo is so popular that we've gone so far as to add especially nice integration with Sincerity/Prudence.
You'll need to install a MongoDB server yourself for this section of the tutorial. If you can't do that right now, don't worry: the rest of the tutorial will work just fine using the in-memory storage we've been using so far, and you can just read through this part.
Let's install the MongoDB driver into our container using a Sincerity command:
sincerity add mongodb.javascript : install
We can configure the driver by editing our application's settings.js, and adding a section similar to this:
app.globals = { mongoDb: { defaultUris: 'localhost:27017', defaultDb: 'cms' } }
You can provide full MongoDB connection string URIs, either a single one or an array if you are connecting to a replicaset. The driver will automatically create a thread-safe connection pool upon first use using these settings.
Our new MongoDB-enabled scriptlet:
<% document.require( '/prudence/resources/', '/mongo-db/') var id = 'page.' + conversation.wildcard var collection = new MongoDB.Collection('pages') if (conversation.request.method.name == 'POST') { var form = Prudence.Resources.getForm(conversation, {content: 'string'}) var content = form.content collection.upsert({_id: id}, {$set: {content: content}}) } else { var doc = collection.findOne({_id: id}) var content = doc ? doc.content : 'This page is empty.' } %>
Restart Prudence, and check out your new persistent CMS.
Our usage of the MongoDB API is quite trivial here, but in case you're new to it, read up about the $set operation: it guarantees an atomic update of particular fields in our document.
For the rest of this tutorial, we'll continue to use in-memory storage in our examples, but feel free to adapt them to use MongoDB if you already have it set up!
Further Exploration
- Relational (SQL) databases are quite easy to access using JDBC drivers. For a complete Prudence example, see Stickstick. And there are countless frameworks that build abstractions on top of JDBC.
- You can easily administer MongoDB by installing MongoVision into your Prudence container.
- If MongoDB is your cup of tea, check out Diligence, a comprehensive web framework based on Prudence and MongoDB. Especially useful for CMS, Diligence features a documents service, which features versioning and site-wide snapshots.
A Scalable CMS
Caching
As long as we're storing our CMS content in MongoDB or in memory, page rendering will be very fast and light. However, if we were to do more heavy lifting per page—for example, multiple database lookups—then performance would drop accordingly. By smartly caching our pages we can ensure that pages would be rendered only when necessary, allowing for the best possible performance.
Actually, talking about performance is a shorthand for the real issue: even pulling data from a database is usually very fast. The real issue is scalability: increased load on the database per user request can limit your ability to support many users. Obviously, it's most efficient to do work only when you need it: and that simple principle is the most important tool you have for increasing scalability.
Prudence does caching very well. So, though it might be considered an "advanced" topic, it's a crucial one, and worth going over in this tutorial. It's also easy:
<% document.require('/prudence/resources/') var id = 'page.' + conversation.wildcard caching.duration = 60000 caching.tags.add(id) caching.onlyGet = true if (conversation.request.method.name == 'POST') { var form = Prudence.Resources.getForm(conversation, {content: 'string'}) var content = form.content application.globals.put(id, content) document.cache.invalidate(id) } else { var content = application.globals.get(id) || 'This page is empty.' } %>
What we've done:
- caching.duration is 0 milliseconds by default. We set it to 60 seconds.
- caching.tags allows us to "tag" the cache entry. These tags can then be used to invalidate whole swaths of the cache at once. In this case, we only have one cache entry (the current page) that uses each particular tag.
- caching.onlyGet is set to "true" because we don't want our "POST" requests to use the cache (otherwise they would only be processed for cache misses).
- document.cache.invalidate is used to invalidate a cache tag. In this case, it's our own.
It's easy to see the caching in action via your browser's developer tools. In Firefox, turn on the network monitor by pressing CTRL+SHIFT+Q. In Chromium/Chrome, use CTRL+SHIFT+I and select the "network" tab.
Refresh the page and you will see the "GET" request you just sent. Click on it to see its details, and you will see the response headers returned from the server. The "X-Cache" family of headers will tell you the status of the server-side cache, such as whether it's a "hit" or "miss." You can disable these debug headers by configuring "debug" mode to false.
However, Prudence also utilizes the client-side cache: if you continue refreshing the page from the same browser within the 60-second cache window, the web browser's conditional HTTP requests will return a 304 "not modified" status, which tells the browser that there's no new information on the server, and thus it will efficiently avoid downloading the complete response, using its locally cached value instead.
You've just vastly improved the scalability of your CMS, allowed for a faster user experience, all without ever compromising on the freshness of user data. Prudence caching is pure win.
Note that editing your "page.t.html" file will automatically invalidate the cache, allowing you to instantly see your changes. Actually, this feature extends to the use of includes: if you edit any file, it will also invalidate all files that include it.
Compression
Prudence will automatically compress responses (using gzip or DEFLATE) if their size in bytes exceeds a certain threshold. Our CMS pages were likely too small, which is why you likely haven't seen compression. You'll usually want that threshold: there are little or no bandwidth savings to be gained by compressing tiny responses, so the extra CPU load required for compression will be wasted. For now, let's lower the threshold, just for demonstration purposes.
Open the application's setting.js and edit the app.settings.compression setting:
app.settings = { ... compression: { sizeThreshold: 0, exclude: [] } }
Restart Prudence for the setting to take effect. With a threshold of zero bytes, all responses should now be compressed—assuming the client accepts compression, as indeed web browsers do.
Actually, Prudence integrates compression with caching: both compressed and uncompressed versions of the response are cached, so that compression will not have to be redone. This can result in significant savings for CPU usage in high loads. (And if a client needs a different kind of compression than the one that was stored in the cache—say, gzip was stored, but the client needs DEFLATE—then Prudence will use the uncompressed cache entry, compress it, and then cache. Smart!)
Let's see it in action. Using the web browser's network monitor, you'll notice that the response headers now include "Content-Encoding", which means that our responses are compressed. gzip or DEFLATE will be chosen according to the web browser's preferences. Other than that (and the different response byte size) it should look identical.
To test using cURL, we'll have to do two things: explicitly tell the server that we support compression by sending the "Accept-Encoding" header, and also be ready to uncompress the result in order to display it, using cURL's "compressed" flag:
curl --verbose --header 'Accept-Encoding: gzip' --compressed http://localhost:8080/cms/api/test/
Further Exploration
- Caching is a big deal. Read all about it.
- And caching is just the tip of the scalability iceberg. We treat the topic in depth here.
A CMS API
Our CMS is currently limited to web pages, but we can make it much better by opening it up as a RESTful web API. This would allow all kinds of clients to access our CMS, such as dedicated mobile apps, desktop applications, and "rich" web applications (using "AJAX"). It would also allow 3rd-party web sites and services to use our CMS as a service, rather than as an application (in business buzzword land this is called "SaaS").
Hello, API World
As we've seen, templates are Prudence's way of making scalable web pages. The other side of Prudence is manual resources.
We'll want our API in the "/api/" URL, so let's create a "api.m.js" file:
function handleInit(conversation) { conversation.addMediaTypeByName('application/json') } function handleGet(conversation) { return '{"message": "Hello, API world."}' }
Remember those pre-extensions? "m" stands for "manual." The ".js" extension means that our resource is implemented in JavaScript, though we can end it in ".py", ".rb", etc., if we want to use other supported programming languages.
You could test the resource in a web browser by browsing to http://localhost:8080/wiki/api/, however that's not the best way. Also, the web browser only knows how to do "GET" by default. So instead, we recommend testing APIs using cURL.
Let's test our API using this cURL command line:
curl http://localhost:8080/cms/api/
Hi!
Fleshed Out
We can now fully flesh out our API. It will look quite similar to the template we wrote above:
document.require( '/prudence/resources/', '/sincerity/json/') function handleInit(conversation) { conversation.addMediaTypeByName('application/json') } function handleGet(conversation) { var id = 'page.' + conversation.wildcard var content = application.globals.get(id) || 'This page is empty.' return Sincerity.JSON.to({content: content}) } function handlePut(conversation) { var id = 'page.' + conversation.wildcard var payload = Prudence.Resources.getEntity(conversation, 'json') application.globals.put(id, payload.content) document.cache.invalidate(id) return Sincerity.JSON.to({content: payload.content}) } function handleDelete(conversation) { var id = 'page.' + conversation.wildcard application.globals.remove(id) document.cache.invalidate(id) return null }
What we did:
- The "handle-" functions are "entry points" into our program, with one per HTTP verb. From Prudence's perspective they are all encapsulated as a single resource class, with each resource instance having its own "conversation" instance.
- handleInit is a bit different in that it dynamically sets up our resource. The MIME type we've added will be used for content negotiation with the client: if several are available, Prudence will automatically select the best type according to the client's preferences. Note that order matters here: the first MIME types you add are preferred over the later ones. For our tutorial, we'll only support JSON responses, though it's possible to support XML and others.
- We've used the Prudence.Resources.getEntity API to get the request payload. We're expecting the client to send us JSON, but again it's possible to support other formats.
- Note that we're making sure to invalidate the cache when we change the data: this is to make sure that the CMS pages will be updated accordingly.
We'll also need to change our routing.js to capture the wildcard, again similar to what we did with the template page:
app.routes = { ... '/page/*': '/page/', '/api/*': '/api/' }
Restart Prudence for the routing.js change to take effect, and then test it again:
curl http://localhost:8080/cms/api/test/
Now, let's test an HTTP "PUT" with new page content:
curl --request PUT --data '{"content": "New page content!"}' http://localhost:8080/cms/api/test/
And we can also "DELETE":
curl --request DELETE http://localhost:8080/cms/api/test/ curl http://localhost:8080/cms/api/test/
While testing with cURL, you can simultaneously test the CMS pages using the browser, as we did earlier. Both the API and the pages see the exact same data, as it's stored in the application.globals.
Cached
Let's add support for caching our API results. Again, it's very similar to what we did with our template, except that caching should be set up in handleInit:
function handleInit(conversation) { conversation.addMediaTypeByName('application/json') var id = 'page.' + conversation.wildcard caching.duration = 60000 caching.tags.add(id) }
Note that the cache tag is exactly the same as we're using for template pages: this guarantees that the template pages will be updated even though the CMS is changed from here, and vice versa, even though what is being cached is quite different. Because the cache key depends on the URI, and the URIs for the pages and APIs are different, there will be no conflict.
To see the caching headers when using cURL, add the "verbose" flag:
curl --verbose http://localhost:8080/cms/api/test/ curl --verbose --request PUT --data '{"content": "New page content!"}' http://localhost:8080/cms/api/test/
Further Exploration
- Read all about manual resources.
Finishing Touches
Here, we'll beef up our CMS a bit, and along the way introduce you to a few more Prudence features.
CSS with LESS
Prudence comes with LESS built in: it's an extended CSS language. LESS greatly increases the power of CSS by allowing for code re-usability, variables and expressions, as well as nesting CSS.
Let's "less up" our CMS pages! First, let's include the CSS in our "page.t.html" by adding an HTML "head" tag:
<html> <head> <link rel="stylesheet" type="text/css" href="<%.%>/style/site.css" /> </head>
You'll notice our use of "<%.%>": this is a shortcut to printing out the conversation.base API, which returns a relative URI path from our currently requested URI to the base URI of the application. For example, if our URI is "http://localhost:8080/cms/page/one/two/three/" then conversation.base would be "../../../..". This frees us from having to hardcode complete URI paths, and guarantees portability even if we move the application to a different URI, attach it to multiple virtual hosts, or run it behind a reverse-proxy.
Now, let's edit the "/resources/style/site.less" file, which was created for us from the application template. Note that there's also a ".css" file in that directory: Prudence will update it for us when we edit the ".less" file. How about this:
@sans-serif: Lucida Sans Unicode, Lucida Grande, Verdana, Arial, sans-serif; @color1: #EEEEEE; @color2: #003300; body { font-family: @sans-serif; font-size: 12px; background-color: @color1; color: @color2; textarea { background-color: @color2; color: @color1; } }
As a final treat, Prudence can also minify the CSS file for us, in order to save some bandwidth or obfuscate it (Prudence will also compress it for us using gzip or DEFLATE, as was mentioned above). To turn on minification, simply change the resource URI to include ".min":
<head> <link rel="stylesheet" type="text/css" href="<%.%>/style/site.min.css" /> </head>
Markdown
Another stylistic flourish would be to use an HTML markup language instead of raw HTML. It's useful for this tutorial, because it will show us how easy it is to use JVM libraries from within Prudence.
First let's install our library using Sincerity. We'll use Pegdown, a JVM implementation of Markdown:
sincerity add org.pegdown pegdown : install
Now let's edit our "page.t.html" to render using the Pegdown API:
<div style="border: 1px solid black; padding: 5px 5px 5px 5px"> <%= new org.pegdown.PegDownProcessor().markdownToHtml(content) %> </div>
After restarting Prudence we would be able to use Markdown in our CMS. Try to edit a CMS page using this content:
This is a title =============== And a subtitle -------------- * And these * Are bullet points * In a list
Internal API
You may have noticed that there's some duplication in our code: both the CMS pages and the CMS API use application.globals to store the content. In this case it's trivial code, but if we moved to database storage, for example, the code would surely grow and be worth encapsulating as an API. One way we could do this is create a reusable library, for example "/libraries/data.js", which we could use both in the CMS pages and the CMS API using "document.require".
But an interesting shortcut is suggested in the fact that we already have an encapsulated API: our CMS web API. We could simply use that directly. The apparent disadvantage is that it is a web API, and we would have to go through HTTP to call it. However, in Prudence you can call any resource internally, bypassing HTTP entirely, making such requests as fast as any function call.
Let's modify our "page.t.html" for this:
<% document.require('/prudence/resources/') var id = 'page.' + conversation.wildcard caching.duration = 60000 caching.tags.add(id) caching.onlyGet = true if (conversation.request.method.name == 'POST') { var form = Prudence.Resources.getForm(conversation, {content: 'string'}) var content = form.content Prudence.Resources.request({ uri: '/api/' + conversation.wildcard, method: 'put', payload: {type: 'json', value: {content: content}} }) } else { var data = Prudence.Resources.request({ uri: '/api/' + conversation.wildcard, mediaType: 'application/json' }) var content = data ? data.content : 'This page is empty.' } %>
We've used the Prudence.Resources.request API to make the request: it will automatically make an internal request if the URI begins with "/" (rather than a protocol, such as "http:").
There's actually yet another optimization we can make: we are currently bypassing HTTP, but it's also possible to bypass JSON serialization. That optimization is more advanced, and is fully explained elsewhere.
Further Exploration
- You don't have to use LESS: Prudence can also unify and minify regular CSS, as well as client-side JavaScript. Also, you may want to read the FAQ as to why Prudence supports LESS but not SASS.
- You can use markup languages in scriptlets.
- Check out the Sincerity markup plugin. It doesn't use Prudence, but you can install it into your container as a utility to allow easy use of markup languages.
- The Prudence.Resources.request API is very powerful, allowing you to easily consume RESTful web APIs.
Not Only JavaScript
The application skeleton uses JavaScript (server-side JavaScript; it has nothing to do with the code running inside web browsers), but Prudence also supports Python, Ruby, PHP, Lua, Groovy and Clojure.
PyCMS
For this tutorial, we'll show you how to implement our entire CMS in Python.
First, let's install Python using Sincerity:
sincerity add python : install
We'll also need a Python JSON library, because our Python engine, Jython, doesn't come with one. simplejson works well with Jython, so let's install it:
sincerity easy_install simplejson
Now let's rewrite our "page.t.html" using Python instead of JavaScript for our scriptlets:
<%py from org.pegdown import PegDownProcessor id = 'page.' + conversation.wildcard caching.duration = 60000 caching.tags.add(id) caching.onlyGet = True if conversation.request.method.name == 'POST': content = conversation.form['content'] application.globals[id] = content document.cache.invalidate(id) else: content = application.globals[id] or 'This page is empty.' %> <div style="border: 1px solid black; padding: 5px 5px 5px 5px"> <%= PegDownProcessor().markdownToHtml(content) %> </div>
Note the "<%py" delimiter, telling us that from now on scriptlets will be in Python. We can switch back to JavaScript using "<%js", or indeed to any other supported language. You can also change the default from JavaScript to Python if you prefer.
For our "api.m.js" file, we will have to rename it to "api.m.py". Here's the code in Python:
import simplejson def handle_init(conversation): conversation.addMediaTypeByName('application/json') id = 'page.' + conversation.wildcard caching.duration = 60000 caching.tags.add(id) def handle_get(conversation): id = 'page.' + conversation.wildcard content = application.globals[id] or 'This page is empty.' return simplejson.dumps({'content': content}) def handle_put(conversation): id = 'page.' + conversation.wildcard payload = simplejson.loads(conversation.entity.text) application.globals[id] = payload['content'] document.cache.invalidate(id) return simplejson.dumps({'content': payload['content']}) def handle_delete(conversation): id = 'page.' + conversation.wildcard del application.globals[id] document.cache.invalidate(id) return None
Some things to note:
- Prudence will attempt to use the programming language's naming conventions. For example, JavaScript generally prefers camel-case, while Python uses lowercase-with-underscores: "handleInit" vs. "handle_init". Clojure, for another example, uses lowercase-with-hyphens: "handle-init". However, when you call from the language into the JVM, the convention depends on the language engine. For our example, Jython requires camel-case. See entry points.
- When we were using JavaScript we had access to specialized APIs, such as Prudence.Resources.getForm. These APIs are, unfortunately, only for JavaScript: when using other programming languages, you will have to use the "low-level" APIs, such as the conversation.form we've used here. On the other hand, JavaScript suffers from almost no standard library of its own: in Python, for example, you have access to a rich standard library, as well as its wider ecosystem, to make your programming easier.
- We could easily have use both Python and JavaScript. Why would you want to do that? For example, you might prefer to use JavaScript in scriptlets, because it's more familiar to HTML coders, while having the web API written in Python by a different team. All supported programming languages can live together in a single container.
CljCMS?
Well, to keep this tutorial short, we won't go on rewriting our CMS in every supported programming language… but here's some proof that they do work.
Let's install Clojure:
sincerity add clojure : install
Now let's create a Clojure manual resource, at "/resources/hi.m.clj":
(defn handle-init [conversation] (.. conversation (addMediaTypeByName "text/plain"))) (defn handle-get [conversation] "Hello from Lisp!")
Browse to http://localhost:8080/wiki/hi/ to see it.
Further Exploration
- You can even mix scriptlets in several languages on the same template, like magic.
What's Next?
We purposely didn't cover every single Prudence feature in this tutorial: just enough to get you started. Continue reading through the Basic Manual as necessary, refer to the online API documentation, and take a peek at the Advanced Manual to see just how far you can go.
Here are some highlights of topics we haven't covered:
- Prudence has a sophisticated system for handling background tasks. You can spawn tasks on-demand using APIs, or schedule recurring maintenance tasks using a crontab. In both cases, tasks use a separate thread pool than the one used for handling user requests.
- Prudence supports Swagger, making it especially easy for clients to consume your RESTful API.
- Runtime debugging of web applications is often difficult. Prudence has a straightforward mechanism for live execution, allowing you run arbitrary code remotely.
- Clusters! When you need to scale horizontally, Prudence provides you with a remarkably seamless solution for sharing state. Even the background task APIs support it: you can farm out your workload anywhere within your Prudence cluster.
- We've already mentioned that Prudence has a very powerful system for defining the URI-space. You can also configure virtual hosting and secure "HTTPS" servers.
- There's generally a whole lot you can configure: logging, cache backends, etc.
- We cover deployment strategies in depth.
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.