Billets Manual

Author: Adrian Perez <aperez@igalia.com>
Copyright: 2008-2009 Igalia S.L.
License:GPL3

Abstract

Documents how to run billets over the HTTP protocol and administrate the billets container, as well as a quick introduction to billet development suitable for new developers.

Contents

Background

Architecture

img/architecture.png

Billets architecture

Container

The billets container is responsible for providing a suitable environment for a number of billets to run. It must have a fairly complete HTTP 1.0 [1], although some HTTP 1.1 [2] features are desirable to have.

Mandatory features:

  • HTTP methods: GET, POST.

Optional (desirable) features:

  • HTTP keep-alive support.
  • Optimizations for static content:
    • Honor If-Modified-Since header.
    • Implement the HEAD method.
    • Generate ETag headers.
[1]As specified in RFC 1945.
[2]As specified in RFC 2616.

Billets

Billets are composed of a mandatory launcher script, which can contain:

  • The full application. This is useful for simple ones.
  • A trampoline which loads the Bill modules which implement the Billet, and then calls its main entry point.

The launcher script must be named after the context name using the .b extension, e.g. ${context}.b The name of the context will be the first component of the URLs used to access the application. Each context name can have associated resources (place the name of your context instead of the ${context} string of the examples):

Library modules
If you do not want to install the modules needed for running a Billet site-wide, they can be deployed inside a directory named ${context}/libs inside the container's base path. This directory will be added by the container to the Bill module search path when loading the Billet.
Resources
Resources needed for running an application can be provided by storing them in a directory named ${context}/rsrc. Those can be any kind of files which need to be handed over to clients using plain HTTP. The container will automatically handle GET and HEAD HTTP request of URLs pointing to application resources.
Application data
Persistent application data must be stored into ${context}/data; transient application data should be stored into ${context}/temp, although it is not enforced. The container will protect those two directories from being accessed via HTTP. It is recommended to keep temporary stuff in a separate directory to ease administration tasks.

Managing the container

The reference Billets container daemon, billetd is a pure-Bash implementation included with every Bill installation. It will serve a number of applications from a directory structure in the filesystem.

Starting and stopping

For the sake of simplicity, the reference container does not need a configuration file. All configuration is done using command line switches. Note that reasonable defaults are provided for most options: for example, the path from where Billets are served from the current working directory unless otherwise specified.

The usual options to billetd are the following:

-p, --port Set the TCP port in which requests are to be served.
-a, --address Set the IP address to which the TCP socket will be bound.
-b, --path Specifies from which directory Billets and their associated content is to be served.
-d, --daemon Daemonize the Billets container. You may want to set the path to the PID-file and the log file.
-k, --kill Kill a running daemon. Make sure you specify the PID-file of the instance you want to stop.

Let us suppose that the Billets to be served reside in /var/lib/billets, we can just launch the daemon by using the following command:

billetd --path /var/lib/billets

This will run the process in foreground, logging to the standard error output. The server can also be ran as a daemon. If we want to use standard Unix paths to store the PID-file and the log. We would start the container with:

billetd --path     /var/lib/billets     \
        --log-file /var/log/billetd.log \
        --pid-file /var/run/billetd.pid \
        --daemon

In order to stop the container we would need to use the following:

billetd --kill --pid-file /var/run/billetd.pid

Adding a billet

In order to add a Billet it should suffice placing it (and its associated resources) inside the base directory of the container. The launcher script must be marked as executable (e.g. with chmod +x), because the container will refuse requests for billets not marked as executable.

It should not be neccessary to restart the container in order for the change to take effect. The shipped billetd will load the billet the first time it is accessed.

Disabling a billet

Because launchers scripts must be marked executable to work, you can disable a particular billet by just removing the execution bit of it, e.g. using chmod -x.

Disabling a billet should not require a restart of the container. This is true for the shipped billetd container.

Removing a billet

In order to remove a billet it should suffice to remove the launcher script and the associated context directory from the file system. The container might need to be restarted in order to free used memory, but requests for the application will stop being served just after the files are deleted.

Billet development

Environment

Along with the variables of the CGI environment which are defined by the www/http module, the container defines a set of variables in the environment of the running billets:

Variable Content
BILLET_BASE Base container directory where launcher scripts are stored.
BILLET_CONTEXT Name of the context of the active billet.
BILLET_PATH Additional URL path components given after the name of the context.
BILLET_TRAIL Array containing all the URL segments, including the context name.
BILLET_DATA Path to the current billet data directory.
BILLET_RSRC Path to the current billet resources directory.
BILLET_LIBS Path to the current billet local library modules directory.
BILLET_TEMP Path to the current billet temporary data storage directory.

Hooks

The container will look for the existence of certain functions in the loaded billets every time a request is served to a billet. If they exist, they will be called by the container on certain events. Hooks must all be functions named with the hook. prefix. The following are currently defined:

billet.setup
Will be called the first time the billet is accessed. It is meant for one-time setup of an application. If the hook returns a non-zero status, execution of the request will be stopped and the connection to the client will be forcibly closed, unless you send an HTTP response by yourself in the hook.
billet.before_request
Called before a request is handed out to URL handlers, it is meant for making operations needed for attending every request. If the hook returns a non-zero status, connection to the client will be forcibly closed, unless you send an HTTP response by yourself.
billet.after_request
Called after the request was completed by the URL handlers. You can use this hook for clean-up tasks. The exit status of the hook is ignored, so make sure your code can live with that behaviour.

URL handlers

The container does its best for easing handling of requests, and one of the most common source of code rewriting is the login used to determine which action must be carried out depending on the requested URL. Handlers are functions which implement the behaviour of applications.

The main handler is the empty handler, which is given that name because it handles the «empty» URL:

function billet:
{
  # Do something when "/" is accessed:
  http_reponse
  http_header Content-Type text/plain
  http_body

  echo "Hello, world!"
}

The above code would serve for a basic “Hello, world” example. Any URL pointing to this minimal billet would be handled by it. This is because URLs are matched against the most specific handler available. If we had a billet:handle:foo it would catch all URLs starting with the /handle/foo/ string.