Configuring Applications
Prudence applications live in their own subdirectory under "/component/applications/". The subdirectory name itself can be considered a setting, as it is used as a default identifier for the application in various use cases.
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-configuring of applications. You must restart Prudence in order for changed settings to take hold. The one exception is crontab: changes there are picked up on-the-fly once per minute.
Overview
The subdirectory contains five main configuration files:
- settings.js: This required file, detailed in this chapter, includes settings used by Prudence as well as your own custom settings.
- routing.js: This required file defines the application's URI-space.
- crontab: This optional file defines regularly scheduled background tasks.
- default.js: This required file is used to load the other configuration files above. You should not normally need to edit this file, but feel free to examine it to understand the application bootstrapping process.
settings.js
If you use the default template with the "sincerity prudence create" command, you should get a setting.js file that looks something like this:
app.settings = { description: { name: 'myapp', description: 'Skeleton for myapp application', author: 'The Author', owner: 'The Project' }, errors: { debug: true, homeUrl: 'http://threecrickets.com/prudence/', contactEmail: 'info@threecrickets.com' }, code: { libraries: ['libraries'], defrost: true, minimumTimeBetweenValidityChecks: '1s', defaultDocumentName: 'default', defaultExtension: 'js', defaultLanguageTag: 'javascript', sourceViewable: true }, templates: { debug: true }, caching: { debug: true }, uploads: { root: 'uploads', sizeThreshold: '0kb' }, mediaTypes: { php: 'text/html' }, logger: 'myapp' }
Numeric Shortcuts
Time durations are in milliseconds and data sizes in bytes. But these can be specified as either numbers or strings:
- Durations: numerically as milliseconds, or using 'ms', 's', 'm', 'h' or 'd' suffixes for milliseconds, seconds, minutes, hours or days. Fractions can be used, and are rounded to the nearest millisecond, for example: "1.5d".
- Data sizes: numerically as bytes, or using 'b', 'kb', 'mb', 'gb' or 'tb' suffixes. Magnitude uses the binary rather than decimal system: 1kb = 1024b. Fractions can be used, and are rounded to the nearest byte, for example: "1.5mb".
You can accomplish the same trick for your own code using Sincerity.Localization.toMilliseconds and Sincerity.Localization.toBytes.
app.settings.description
Information here is meant for humans.
- name: if not specified, will default to the application's subdirectory name
- description: a sentence or a sentence fragment describing the application
- author: the name of company or individual who made the application
- owner: the project to which the application belongs
This information appears in the Prudence Administration application, and you may access it yourself using the application.application.name, application.application.description, etc., APIs.
app.settings.errors
Configure Prudence's error handling behavior here.
- debug: Boolean value; when true enables various useful debugging features detailed below; should be set to false for production deployments
- debugHeader: sets this HTTP header to "error" when the debug page is displayed; defaults to "X-Debug"; set to null if you don't want the header to be used; only relevant when "debug" is true
- homeUrl: a web URL, displayed in the default error page template
- contactEmail: an email address, displayed in the default error page template
Note that you can route your own error pages in routing.js to replace the default template. If you wish to use the values you set here in your template, use the application.application.statusService.homeRef and application.application.statusService.contactEmail APIs.
Uncaught Exception Debugging
Uncaught exceptions in your code will automatically set the HTTP response status code to 500 ("internal server error"), but here you can configure the content of that response.
When "debug" is true, Prudence will return a very detailed HTML debug page. Because this might reveal your application's internal data, make sure to set "debug" to false for production deployments.
When "debug" is false, the default error template page will be displayed.
app.settings.code
Here you can control how Prudence deals with your code:
- libraries: An array of paths where importable libraries will be found. If relative, they will be based on the application's root subdirectory.
- defrost: When true will attempt to "defrost" manual and template resources under the "/resources/" subdirectory. Defrosting means pre-parsing and sometimes pre-compiling the code: this allows for faster startup times on first hits to these resources. Note that defrosting is not pre-heating: the former only pre-compiles, the latter actually does a "GET" on your resources, which would ensure that services used by your resources are also warmed up. See app.preheat. The default value for this setting is true.
- minimumTimeBetweenValidityChecks: Scripturian makes sure to reload (and thus re-compile) code if the source files are changed, for which it compares the file's modification dates to the cached values. For high-volume deployments, this might involve constantly checking the filesystem, potentially resulting in performance problems on some operating systems. This value allows you to enforce a delay between these checks. It's a good idea to set it to anything greater than zero. The default value for this setting is 1 second.
- defaultDocumentName: When a document name specifies to a directory, Scripturian will internally change the specification to a document with this name in the subdirectory (excluding the extension). For example, if the value is "default" and you are calling "document.require('/mylibrary/')" and "/libraries/mylibrary/" is a directory, the it would specify "/libraries/mylibrary/default.*". The default value for this setting is "default".
- defaultExtension: If more than one file in a directory has the same name but different extensions, then this extension will be preferred. For example, if the value is "js" and a directory has both "mylibrary.js" and "mylibrary.py", then the former file will be preferred. The default value for this setting is "js".
- defaultLanguageTag: If a scriptlet tag does not specify a language, then this value will be the default. The default value for this setting is "javascript".
app.settings.templates
Configure templates resources here.
- debug: Boolean value; when true, under your container's "/cache/scripturian/" directory you will see the generated source code for each scriptlet resource. The filenames will include the timestamp for their creation, so you can see all versions of code that were created.
- parser: The parser name; allows you to change the default scriptlets parser to your own custom Scripturian parser; defaults to "scriptlets"
- plugins: Configure scriptlet plugins here
app.settings.caching
Configure caching here.
-
debug: Boolean value; when true, special HTTP response headers will be added for cached resources:
- X-Cache: the caching event; either "hit", "hit;encode" or "miss"
- X-Cache-Expiration: a timestamp in standard HTTP format
- X-Cache-Key: the cache key (see also the caching.key API)
- X-Cache-Tags: comma-separated list of cache tags
- defaultKeyTemplate: Allows you to set the default caching.keyTemplate for all resources. If not specified, will be "{ri}|{dn}".
- keyTemplatePlugins: A dict mapping cache key template variable names to plugin library names. Allows you to install key template plugins for all resources in the application. See the caching.keyTemplatePlugins API for more information.
app.settings.compression
Global settings for resource compression. Note that compression can be turned on or off per individual resource type.
- sizeThreshold: Responses smaller than this will not be compressed. Defaults to 1024.
- exclude: An array of MIME types that will be additionally excluded from compression. Note that several common types are automatically excluded, specifically compressed archive and media formats.
app.settings.uploads
Configure file upload behavior here.
- root: This is where uploaded files are stored. If relative, it will be based on the application's root subdirectory.
- sizeThreshold: The file upload mechanism can optimize by caching small files in memory instead of saving them to disk. Only if files are greater in size will be they be stored. Set to zero to save all files to disk.
app.settings.mediaTypes
This dict maps filename extensions to MIME types.
Prudence recognizes many common file types by default: for example, "png" is mapped to "image/png". However, using this setting you can define additional mappings or change the default ones. Note that each filename extension can be mapped to one and only one MIME type.
This setting is used mostly for textual and static resources. For example, a template resource named "person.t.html" will have the default "text/html" MIME type (which you can change in scriptlet code via the conversation.mediaType APIs), and a static resource named "logo.png" will have the "image/png" MIME type.
For manual resources, you define their supported MIME types manually in handleInit. There, you can refer to MIME types directly via the conversation.addMediaTypeByName API, or you can look them up from this setting using the conversation.addMediaTypeByExtension API.
An example:
app.settings = { ... mediaTypes: { webm: 'video/webm', msh: 'model/mesh' } }
Note for PHP: You may notice that the "default" template's settings.js sets the "text/html" MIME type for the "php" extension. The reason for this is that ".php" files you put in resources are usually expected to output HTML. You may change if you require a different behavior.
app.settings.distributed
- applicationInstance: The name of the Hazelcast "application" instance. Defaults to "com.threecrickets.prudence.application". Can be accessed at runtime via the application.hazelcastApplicationInstance API.
- globals: The name of the Hazelcast map used for the application.distributedGlobals API. Defaults to "com.threecrickets.prudence.distributedGlobals.[name]", where "name" is the application's subdirectory name.
- sharedGlobals: The name of the Hazelcast map used for the application.distributedSharedGlobals API. Defaults to "com.threecrickets.prudence.distributedSharedGlobals".
- taskInstanceSharedGlobal: The name of the application.sharedGlobal in which the Hazelcast "task" instance is stored. If there is nothing in that shared global, then the "task" instance will be identical to the "application" instance. Defaults to "com.threecrickets.prudence.hazelcast.taskInstance". Can be accessed at runtime via the application.hazelcastTaskInstance API.
- executorService: The name of the Hazelcast executor service used for the distributed task APIs. Defaults to "default". Can be accessed at runtime via the application.hazelcastExecutorService API.
To learn how to configure general Hazelcast settings, see the configuration guide.
app.settings.routing
Though routing is configured in routing.js, here we can configure how routing is handled.
- useForwardedHeaders: Whether to apply the "X-Forwarded-Proto", "X-Forwarded-Host", and "X-Forwarded-Port" HTTP headers to requests. Defaults to false. You should only set this to true if you are behind a proxy, otherwise clients could use this to manipulate the information. See "forwarded headers". When true, installs the ForwardedFilter before your application on all non-internal hosts.
app.settings.logger
Set this string to override the default logger name. If not set, it will be the name of the application's subdirectory. The actual logger name will be this value prefixed with "prudence.".
See the logging chapter for a complete discussion.
app.globals
Use this for custom settings for your application: values here will become application.globals when your application is running. Note that Prudence also supports localized settings via inversion of control.
This dict is "flattened" using dot separators. For example, the following:
app.globals = { database: { driver: 'mysql', table: { db: 'myapp', name: 'users' } } }
…would be interpreted as if it were:
app.globals = { 'database.driver': 'mysql', 'database.table.db': 'myapp', 'database.table.name': 'users' }
In your code, you would access these values using the application.globals API:
var driver = application.globals.get('database.driver')
To set application.sharedGlobals you can use a custom service.
Lazy Initialization
Lazy initialization allows you to defer the initialization of an application.global to its actual use within your application. This feature can be used to solve three different problems:
- You wish to set up an application.global with resources that would only be available during runtime, not during the bootstrap process.
- You wish to set up a heavy resource, but instead of slowing down the bootstrap process by initializing it up front, you'd rather initialize it on-demand when actually used for the first time by the running application.
- You wish to simplify the configuration of complex objects: the mechanism allows you to use a simple DSL (Domain-Specific Language), which behind the scenes calls an arbitrary constructor.
The mechanism relies on evaluation of JavaScript code, and thus is only available for other JavaScript code in your applications. However, similar principles can be used for other languages that support eval. Study the library's code to see how it's done!
Lazy globals can be defined using a double-dot key. Here's an example for settings.js:
app.globals = { services: { email: { '..': { dependencies: '/services/email/', name: 'EmailService', config: { smtp: 'localhost', origin: 'service@myapp.org' } } } } }
The lazy configuration has three settings, which are used when the object is initialized:
- dependencies: These are called with document.require
- name: This is the constructor to call
- config: This is an optional argument sent to the constructor; note that it must be JSON-serializable
For this example, here's how our "/libraries/services/email.js" file could look:
function EmailService(config) { this.smtp = config.smtp this.origin = config.origin this.send = function(destination, message) { application.logger.info('Sending an email from ' + config.origin + ' to ' + destination) ... } }
To actually access the object in the application, while possibly triggering lazy initialization (if it was not initialized already), we need the Prudence.Lazy library:
document.require('/prudence/lazy/') var emailService = Prudence.Lazy.getGlobalEntry( 'services.email', null, function(c) { return eval(c)() }) emailService.send('test@myapp.org', 'Hello, this is a test message!')
That third argument requires some explanation: it is a closure (anonymous function) that accepts the constructor source code and evaluates it locally. It will be called only if the global has not yet been initialized. Thus, in all calls except the first, this would be a very fast application.global lookup.
The lazy initialization mechanism can optimize for dicts and arrays of lazy entries using the triple-dot key:
app.globals = { emailServices: { '...': { moderator: { dependencies: '/services/email/', name: 'EmailService', config: {smtp: 'localhost', origin: 'moderator@myapp.org'} }, admin: { dependencies: '/services/email/', name: 'EmailService', config: {smtp: 'localhost', origin: 'admin@myapp.org'} } } }, nodes: { '...': [{ dependencies: '/services/nodes', name: 'Node', config: {address: 'node1.myapp.org'} }, { dependencies: '/services/nodes', name: 'Node', config: {address: 'node2.myapp.org'} }] } }
To use them:
document.require('/prudence/lazy/') var emailServices = Prudence.Lazy.getGlobalMap( 'emailServices', null, function(c) { return eval(c)() }) emailServices.moderator.send('test@myapp.org', 'Hello, this is a test message!') var nodes = Prudence.Lazy.getGlobalList( 'nodes', null, function(c) { return eval(c)() })
Note that for this optimization the entire dict or array will be created at once and stored in a single application.global.
If you edit the source file for the lazy-initialized object—say, our "email.js"—you would not see your changes. The reason is that the object has already been initialized and stored in memory: the recompiled functions would not be hooked to the already-existing object (really just a dict in JavaScript).
You can, however, force re-initialization using the API:
Prudence.Lazy.getGlobalEntry('services.email').reset()
This works on dicts and arrays, too:
Prudence.Lazy.getGlobalMap('emailServices').reset()
To do this live, you can use the live execution mechanism.
The lazy mechanism is thread-safe: if you call getGlobalEntry concurrently, it will make sure to store only the first initialized object. You can thus be sure that you will always get the same instance of the lazy-initialized object in your application.
However, take note: it is possible that under high-concurrency situations the object would be created more than once, even if subsequent objects after the first are discarded. This might be a problem if your object creation is very heavy. Unfortunately, there is no easy way around this: allowing for multiple object creation is key to making the lazy mechanism perform well (it does no locking). If this is a problem for you, you will have to find your own way to avoid simultaneous initialization: possibly locking internally within your object constructor, and checking to make sure that the constructor is not being simultaneously called by another thread.
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.