
Configuration
Your Prudence container's "/component/" directory has various subdirectories in which you can configure it.
Prudence uses "configuration-by-script" almost everywhere: configuration files are true JavaScript source code, meaning that you can do pretty much anything you need during the bootstrap process, allowing for dynamic configurations that adjust to their deployed environments.
Prudence, as of version 2.0, does not support live re-configuration. You must restart Prudence in order for changed settings to take hold. The one exception is the system crontab: changes there are picked up on-the-fly once per minute.
/configuration/logging/
Used by the Sincerity logging plugin. Configure system-wide logging here, using Apache log4j.
/configuration/sincerity/
Used by Sincerity to manage installation of packages in your Prudence container. You usually won't be editing this files directly, instead using "sincerity" commands to manipulate it. However, take special note of artifacts.conf if you are committing your container to a VCS.
/configuration/hazelcast/
Configure Hazelcast here, if you are running in a cluster. Note that the configuration is actually loaded by the distributed service.
You have two options for configuration:
- By script, at "/configuration/hazelcast/". This is the default method, and is recommended.
- By standard XML file, at "/configuration/hazelcast.conf". This method is provided for compatibility, and it's generally preferable to use the by-script configuration. An example file is provided for you at "/configuration/hazelcast.alt.conf". If you rename this file to "hazelcast.conf", then it will be used instead of the by-script configuration.
For your convenience, the entire com.hazelcast.config package is already imported for configuration-by-script. See the Hazelcast configuration guide for a general overview.
The default configuration creates a single Hazelcast instance belonging to an "application" cluster, and the default configuration of applications points them to use this instance, though each application gets its own Hazelcast map to handles it application.distributedGlobals API. However, you can configure applications to use any Hazelcast instance, as well as change which shared objects to use, in their settings.js. This allows each application to belong to a different Hazelcast cluster.
Likewise, if you are using Hazelcast as a cache backend, it will default to use the Hazelcast "application" instance. However, you can set the cache backend to use its own Hazelcast instance in its constructor.
There are good reasons why you might want a more complex configuration: for example, if you're doing task farming, you might want the "task" nodes to be separate from the "application" nodes. A commented-out suggested configuration for this is included—see the clusters chapter for a detailed explanation.
/component/
The Prudence component is bootstrapped here. If you wish to understand exactly how it works, take a look at "/component/default.js".
sharedGlobals
In all the "/component/" configuration files, you have access to a global "sharedGlobals" JavaScript dict. Values you set here will become application.sharedGlobals once the component is started. This dict works similarly to app.globals in settings.js; it is likewise "flattened."
For an example, here we set a database connection pool as a shared global, using a custom service defined in "/component/services/database/default.js":
sharedGlobals.database = sharedGlobals.database || {} sharedGlobals.database.pool = createPool()
Initializers
In all the "/component/" configuration files, you have access to a global "initializers" JavaScript array. Any function you add to this array will be executed after the component is started, in the order in which they were added. Here's a trivial usage example:
initializers.push(function() { println('This is my trivial initializer!') })
/component/hosts/
Restlet has excellent virtual host support. There is a many-to-many relationship routing between servers, hosts and applications, allowing you considerable flexibility in binding your URI-spaces. For example, you can easily have a single Prudence container (running in a single JVM instance) managing several sites at once, with several applications, on several domains, on several servers.
Define your virtual hosts as ".js" files under "/component/hosts/". A minimal host definition would like this:
var host = new org.restlet.routing.VirtualHost(component.context) host.name = 'privatehost' component.hosts.add(host)
The "host.name" param exactly matches the string used in app.hosts per each application.
A virtual host can route according to domain name, and incoming server IP address and port assignment:
host.resourceScheme = [string] host.resourceDomain = [string] host.resourcePort = [string] host.serverAddress = [string] host.serverPort = [string] host.hostScheme = [string] host.hostDomain = [string] host.hostPort = [string]
An example of a virtual host for a specific domain name:
var host = new org.restlet.routing.VirtualHost(component.context) host.name = 'other' host.resourceDomain = 'otherdomain.org' component.hosts.add(host)
If you do this, you will likely want to set "resourceDomain" for the default host to a specific domain, too.
Some notes:
- "*" wildcards are supported for all of these properties. If you do not explicitly set a property value, it will default to "*".
- "resourceScheme", "resourceDomain" and "resourcePort" refer to the actual incoming URI. Thus "resourcePort" would be meaningful only if URIs explicitly include a port number, a rather rare situation. To match a host to a specific server you would likely want to use "serverPort".
- "hostScheme", "hostDomain" and "hostPort" are for matching the "Host" HTTP header used by load balancers and other proxies.
The Hungry Host
The "default" host that comes with the Prudence skeleton doesn't configure any routing limitations, meaning that all incoming requests are routed (equivalent to setting all properties at "*"). We call such a host "hungry," because it will "eat" any request coming its way, denying any other virtual hosts the opportunity to route them. Thus, if you want to use more than one host, you have two options:
- Don't use a hungry host: delete the "default.js" file or comment out the "component.hosts.add" call in it.
- Make sure that the hungry host is the last one, so that it acts as a fallback when other hosts don't match incoming requests. Because host files are executed in alphabetical order, a good way to ensure that the hungry host is last is to number your host filenames. For example:
- "1-public.js"
- "2-private.js"
- "3-default.js" (this is the hungry host)
Deploying Multiple Sites
Using the virtual hosts and application model, Prudence can let you manage several sites using a single Prudence installation (a "container"). But is this always a good idea?
- Possibly simpler deployment: you are using a single base directory for the entire project, which might be easier for you. Because all configuration is done by JavaScript inside the container, it is very flexible.
- Less memory use than running multiple JVMs.
- Shared memory: you can use application.sharedGlobals to share state between applications.
- Possibly simpler deployment: several base directories can mean separate code/distribution repositories, which might be easier for you. You'll configure routing between them at your load balancer.
- Robustness: crashes/deadlocks/memory leaks in one VM won't affect others. With this in mind, it may even be worth having each single application running in its own JVM/container.
- Run-time flexibility: you can restart the JVM for one container without affecting others that are running.
There is no performance advantage in either scenario. Everything in Prudence is designed around high-concurrency and threading, and generally threads are managed by the OS globally.
Well, there are caveats to that statement: Linux can group threads per running process for purposes of prioritization, but this is mostly used for desktop applications. The feature could possibly be useful when running several Prudence containers, if you want to guarantee high thread priority to one of the containers over the others. This kind of tweaking would only effect very high concurrency and highly CPU-bound deployments.
/component/servers/
Define your servers as ".js" files under "/component/servers/". At the minimum, you must specify a protocol and a port. Here's an example definition for an HTTP server, "http.js":
var server = new Server(Protocol.HTTP, 8080) server.name = 'myserver' component.servers.add(server)
The server name is optional, and used for debugging.
An important configuration is to bind a server to a specific IP address, in case your machine has more than one IP address:
server.address = [string]
There are many configuration parameters for Jetty, the HTTP engine, which you can set as parameters in the server's context. Here we'll increase the size of the thread pool and lower the idle timeout:
server.context.parameters.set('threadPool.minThreads', '50') server.context.parameters.set('threadPool.maxThreads', '300') server.context.parameters.set('connector.idleTimeout', '10000')
For a complete list of available configuration parameters, see JettyHttpServerHelper. Make sure to check the documentation for all the parent classes, because they are inherited. Note that parameters are always set as strings, even if they are interpreted as other types.
Are your servers running behind a load balancer? See the explanation here as to why you would want to add this:
server.context.parameters.set('useForwardedForHeader', 'true')
Secure Servers (HTTPS)
If you are using a load balancer, it may make sense to handle secure connections there. But Prudence can also handle secure (HTTPS) connections itself. Here's an example configuration for "/component/servers/https.js":
var server = new Server(Protocol.HTTPS, 8082) server.name = 'secure' component.servers.add(server) // Configure it to use our security keys server.context.parameters.set('keystorePath', '/path/prudence.jks') server.context.parameters.set('keystorePassword', 'mykeystorepassword') //server.context.parameters.set('keyPassword', 'mykeypassword')
See DefaultSslContextFactory for all security configuration parameters.
The above configuration assumes the you have a Java KeyStore (JKS) file at "/path/prudence.jks" containing your security key. You can create a key using the "keytool" utility that is bundled with most JDKs. For example:
keytool -keystore /path/prudence.jks -alias mykey -genkey -keyalg RSA
When creating the keystore, you will be asked provide a password for it, and you may optionally provide a password for your key, too, in which case you need to comment out the relevant line in the example above. (The key alias and key password would be transferred together with the key if you move it to a different keystore.)
Note that if you want to make client requests to a server that uses such a self-created key, you will need your client to recognize that key. If this is done from the JVM, this means setting the "javax.net.ssl.trustStore" JVM property. For example, if you're using Prudence's request API, you will need to start Prudence like so:
JVM_SWITCHES=-Djavax.net.ssl.trustStore=/path/prudence.jks sincerity start prudence
Such self-created keys are useful for controlled intranet environments, in which you can provide clients with the public key, but for Internet applications you will likely want a key created by one of the "certificate authorities" trusted by most web browsers. Some of these certificate authorities may conveniently let you download a key in JKS format. Otherwise, if they support PKCS12 format, you can use keytool (only JVM version 6 and later) to convert PKCS12 to JKS. For example:
keytool -importkeystore -srcstoretype PKCS12 -srckeystore /path/prudence.pkcs12 -destkeystore /path/prudence.jks
If your certificate authority won't even let you download PKCS12 file, you can create one from your ".key" and ".crt" (or ".pem") files using OpenSSL:
openssl pkcs12 -inkey /path/mykey.key -in /path/mykey.crt -export -out /path/prudence.pkcs12
(Note that in this case you must give your new PKCS12 a non-empty password, or else keytool will fail with an unhelpful error message.)
Jetty adds full HTTP/2 support to your secure servers, bringing an improved user experience and a lighter load on the backend. See the Sincerity documentation for details on how to enable it.
It's sometimes necessary to support HTTPS specially in your implementation. One useful strategy is to create separate applications for HTTP and HTTPS, and then attach them to different virtual hosts, one for each protocol (the "resourceScheme" parameter). However, if the application behaves mostly the same for HTTP and HTTPS, but differs only in a few specific resources, it may be useful to check for HTTPS programmatically, via the conversation.reference.schemeProtocol API. For example:
if (conversation.reference.scheme == 'https') { ... }
Other Server Engines
Prudence, by default, uses Jetty 9.3 as its server engine. Jetty is mature, performant and eminently scalable, and we highly recommend it for production environments.
However, it's possible to replace Jetty with a different engine should you require. To do this, you must remove the Jetty Restlet 9 connector from your container, and install a different connector instead, as well as its dependencies.
To make sure Jetty 9 is excluded from the next installation, you can use the following Sincerity command:
sincerity exclude org.restlet.jse org.restlet.ext.restlet.jetty9 : exclude org.eclipse.jetty jetty-security : exclude org.eclipse.jetty jetty-client
As of Restlet 2.3, three alternatives are available:
This is the recommended alternative to Jetty 9.3 if you cannot use JVM 8 or above, and are limited to JVM 7. To install it:
The configuration parameters are documented here.
sincerity add org.restlet.jse org.restlet.ext.jetty : install
Simple Framework, which also works on JVM 6, is a lighter alternative to Jetty. Its documentation makes some controversial claims about its improved scalability in comparison to Jetty, but we encourage you to verify them for yourself. To install it:
The configuration parameters are documented here.
sincerity add org.restlet.jse org.restlet.ext.simple : install
Will be used if no other connector is installed. While the internal engine may be adequate for testing, we found that it suffers from stability issues, and do not recommend it for production environments.
/component/clients/
Client connectors have two main use cases:
- External requests. Most often you will use "http:" and "https:" connectors, but you might also need "file:", "ftp:", WebDAV extensions and/or others.
- Static resources internally require a "file:" client connector.
To add a client, add a ".js" file to "/component/clients/". For example, here's a minimal configuration for an HTTP client, "http.js":
importClass(org.restlet.data.Protocol) var client = component.clients.add(Protocol.HTTP)
Clients are configured by setting parameters in their context:
client.context.parameters.set('socketTimeout', '10000')
For a complete list of available configuration parameters, see HttpClientHelper. Make sure to check the documentation for all the parent classes, because they are inherited. Note that parameters are always set as strings, even if they are interpreted as other types.
(That link was for Jetty 9; use this link if you are using Apache HttpClient on JVM 6.)
Other Client Engines
Prudence, by default, uses Jetty 9 as its client engine. Jetty is mature, performant and eminently scalable, and we highly recommend it for production environments.
However, it's possible to replace Jetty with a different engine should you require. To do this, you must remove the Jetty Restlet 9 connector from your container, and install a different connector instead, as well as its dependencies.
To make sure Jetty 9 is excluded from the next installation, you can use the following Sincerity command:
sincerity exclude org.restlet.jse restlet-jetty9 : exclude org.eclipse.jetty jetty-security : exclude org.eclipse.jetty jetty-client
As of Restlet 2.2, three alternatives are available:
Apache HttpClient is the recommended alternative to Jetty 9 if you cannot use JVM 7 or above, and are limited to JVM 6. To install it:
The configuration parameters are documented here.
sincerity add org.restlet.jse restlet-httpclient : install
This engine uses the java.net.URLConnection class included in the JVM. It does not scale well, however it does the job, and even supports FTP connections. To install it:
The configuration parameters are documented here for HTTP, and here for FTP.
sincerity add org.restlet.jse restlet-net : install
Will be used if no other connector is installed. While the internal engine may be adequate for testing, we found that it suffers from stability issues, and do not recommend it for production environments.
/component/services/
Services are run after the component is configured but before it is started. Several services are required to support various Prudence features, but you may freely add your own subdirectories here, to support your own features and subsystems.
Remember that your custom services have access to "sharedGlobals" and "initializers".
/component/services/log
Prudence supports NCSA-style logging of all incoming client requests, to all servers. By default, we just configure the logger name here (the default is "web"):
component.logService.loggerName = 'web'
You may also further configure the log format using the string interpolation variables:
component.logService.responseLogFormat = '{d}\t{cia}\t{ri}\t{S}'
See the Restlet documentation for more information.
To learn how configure this logger and connect it to an appender, refer to the documentation for Sincerity's logging plugin. By default, Prudence will use a rolling file appender to "/logs/web.log".
/component/services/prudence/status
Here you may configure system-wide custom error pages. Though applications may define their own custom error pages using app.errors, you can set them up here, too. Note that app.errors takes precedence over definitions here: you may thus treat this feature as a fallback option for when applications do not handle errors themselves.
An example definition:
statusService.capture(404, 'myapp', '/404/', component.context)
The capture API uses an application's internal name, allowing you to implement the error page in any installed application. Note that this API does not let you capture-and-hide the target URI, e.g. "/404/!". If you wish to hide it, you must do so in the application's app.routes.
/component/services/prudence/caching
Configure the caching backends here.
In "/services/prudence/caching/default.js", a ChainCache instance is set up as the main cache implementation. This allows you to create a tiered cache. Each tier should be added in order: the first tier added to the chain is the first one from which Prudence will fetch, so it usually should be the fastest backend. When storing entries in the cache, all tiers will be invoked.
Configure your tiered backends under "/services/prudence/caching/backends/", making sure that their filenames are in alphabetical order. By default, Prudence calls these "backend.1.js", "backend.2.js", etc., though you may your use your own alphabetic scheme.
By default, Prudence sets up an InProcessMemoryCache as the first tier. Its default max size is 1MB, however it's recommended to increase this size according to your machine's available RAM. Note that if you're limiting the JVM's RAM usage in some way (for example, if you're using the Sincerity service plugin), then you want to make sure that the JVM has enough room for your cache as well as its normal operational requirements.
Though the in-process memory cache offers the best possible performance, it's of course limited in size. For very large web sites, it might be too small to be effective: if cache entries keep being discarded to make room for others, it will not be helping you much as a 1st tier backend. Make sure to monitor your usage carefully to see how often cache entries are discarded. Otherwise, good alternatives for the 1st tier would be Hazelcast or memcached.
The following cache backends are all supported in Prudence:
- Hazelcast: If you're already running in a cluster, enabling a Hazelcast-based cache backend is a great idea, because you've already deployed it. This cache backend is in essence similar to the in-process memory cache, except that you will be pooling together the RAM from all running JVMs in the cluster. Note that it's possible to add lazy persistence plugins to Hazelcast, to make sure your data is stored on disk. Note that it doesn't make much sense to use the in-process memory cache together with Hazelcast: choose one or the other. See the API documentation.
- memcached: For very large web sites, even your pooled RAM in the cluster may not be enough. In that case, you may consider creating a separate memcached-based cluster, entirely devoted to caching. An advantage of memached is that it's very standard and widely supported, so you may also be able to use your cache cluster for other systems. Note that though memcached is not persistent by design, there exist compatible alternatives, such as Tarantool, that allow lazy persistence of your cached data to disk. See the API documentation.
- MongoDB: If you're using MongoDB to store your data, it makes perfect sense to also use it as a cache, as it performs very well and provides you with instant persistence, as well as support for truly enormous caches. You can also use a capped collection for even better performance, at the expense of limiting your cache size. Also, Prudence can store its cache entries as structured MongoDB documents, making it very easy to debug or otherwise collect statistics directly from the cache collection. See the API documentation.
- SQL: This is a great choice if you're using a relational (SQL) database to store your data. Advocates of "NoSQL" like to claim that relational databases are "slow," but that's nonsense: Prudence doesn't use transactions for its implementation, and performance should be excellent, no worse than "NoSQL," especially with some careful tuning. It's definitely fine as a 2nd-tier cache. Practically any database can be supported via JDBC, though we also have a special implementation for the H2 database, making it easy to test locally, because H2 can run inside Prudence's JVM. See the API documentation (also for H2).
We've made it easy to install the dependencies for all the supported cache backends via Sincerity packages and shortcuts. The packages will also install an example configuration, as a 2nd-level tier (after the default in-process memory cache), in the file "/services/prudence/caching/backends/backend.2.js". Here's a table of the shortcuts, as well as the full package identifiers:
Backend | Shortcut | Identifier |
Hazelcast | prudence.cache.hazelcast | com.threecrickets.prudence prudence-cache-hazelcast |
memached | prudence.cache.memached | com.threecrickets.prudence prudence-cache-memcached |
MongoDB | prudence.cache.mongodb | com.threecrickets.prudence prudence-cache-mongodb |
H2 | prudence.cache.h2 | com.threecrickets.prudence prudence-cache-h2 |
For example, to install the H2 cache backend in the second tier:
sincerity add prudence.cache.h2 : install
The default configuration will put the H2 files under "/cache/prudence/cache/".
/component/services/prudence/startup
This service provides you with access to a global "startupTasks" JavaScript array. Any object implementing Callable or Runnable that you add to this array will be executed after the component configured but before is started in a multi-threaded task pool. You can configure the thread pool here.
This feature is used by the defrost/preheat feature.
/component/services/prudence/executor
This service configures the thread pool used for background task execution. You may change the size of the thread pool here, or otherwise install specialized implementations. By default Prudence uses (number of CPU cores * 2 + 1) for the pool size, a formula which offers good performance under high loads for common network-bound scenarios, though you may want to decrease this size for heavy CPU-bound workloads.
See the Executors API for a few other built-in options. Note, however, that your implementation must support the ScheduledExecutorService interface if you wish to support task scheduling.
The executor can be accessed via the application.executor API.
/component/services/prudence/scheduler
The scheduler is used to handle the crontab feature. By default, Prudence here sets up a scheduler for the component, accessible via the application.scheduler API, and also installs the system-wide crontab. You may edit this file to add other specialized crontabs, or otherwise set up scheduled tasks.
See the cron4j Scheduler documentation for more options.
/component/services/prudence/distributed
This service's job is to load the Hazelcast configuration.
/component/services/prudence/singleton
Prudence assumes a single Restlet Component instance. If for some reason you have a more complex setup, you can configure Prudence's initialization here.
/component/services/prudence/version
Provides access to Prudence, Restlet and Jetty versions, and prints out the welcoming message. There's not much to configure here, but feel free to examine the code!
/component/templates/
The "prudence create" Sincerity command copies and adapts the application template under "/component/templates/default/". You can create your own templates in that directory, and use their name as a second argument to the "prudence create" command, e.g.: "sincerity prudence create cms mytemplate". Likely, you'd want to copy the default template and modify it.
The mechanism interpolates the "${APPLICATION}" string in any of these files to be the application name argument you supplied as the first argument to "prudence create".
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.