Please note that this document is a draft. Until such time as it becomes 'final', the API is subject to change wildly. After such time as it becomes 'final', OpenFlax is obliged to provide a compatibility layer for OpenFlax API 1.0.
To add functionality to an OpenFlax web server, a handler
must be written. A handler is a callback module which conforms to
the openflax.handler
behaviour. Writing a handler is similar to writing a CGI or an ESI.
In order for the functionality of a handler to actually be used by the web server, the handler must be given its own section in a configuration file, and a dispatch rule response must point to the handler.
By convention, the handler's module name follows the pattern
openflax.mod.*, although this is not a hard and
fast rule. The handler module can be placed anywhere in
the Erlang search path.
A conf() represents a configuration dictionary. Such a
dictionary is typically drawn primarily from a configuration file,
although it is important to remember that it may also
contain settings drawn from various sources, including:
serve/1 callback which
then switched to the current handler
As a dictionary, a conf() contains a set of
settings, mapping each settings' key to its value. (These terms
are described in greater depth in the Configuration File Format documentation.)
Handlers communicate with OpenFlax and with each other
system by reading and writing settings in a conf().
Settings within the conf()
can be accessed and manipulated with the functions exported by
the openflax.conf module.
Each function in a handler accepts at least a conf()
among its arguments, and is expected to return a conf().
Often (although not always) it can simply return the same conf()
as was passed to it.
The conf() that is passed to each function is generally
(although not always) the conf() that was returned from the
previous function that was called.
Initially, this conf() is generally constructed by reading
the configuration files specified on the command line. Note however, that
while there may be many configuration files and sections within configuration
files, there is only one conf() that forms the basis for
conf()s passed to and fro within OpenFlax; settings in this
conf() override (or accumulate into) other settings when
multiple conf()s, such as from two different sections, are
merged.
A response() is an abstract representation of a desired
HTTP response from the web server. A response() may be
specified directly in the cfg_openflax_dispatch setting
in the configuration (with the exception of streams, which cannot be
specified in dispatch rules) and/or returned by a handler.
response() = error()
| relocate()
| serve()
| stream()
| handler()
error() = not_found
| not_implemented
| method_not_allowed
relocate() = {moved_temporarily, location()}
| {moved_permanently, location()}
| {permanent_new_domain, domain()}
| {temporary_new_domain, domain()}
serve() = {content, data()}
data() = [byte()] | binary()
byte() = 0 <= integer() <= 255
stream() = {stream, pid(), size()}
size() = integer() | unknown
handler() = module_name()
module_name() = atom()
A standard HTTP error response can be indicated by any of the atoms
not_found, not_implemented, method_not_allowed.
An HTTP redirection response can be indicated by
{moved_permanently, location()} or
{moved_temporarily, location()},
where location()
is a string (URI or URL).
A redirection to an analogous URI at another domain name can be indicated with
{permanent_new_domain, domain()} or
{temporary_new_domain, domain()} or
where domain()
is a string (domain name, eg "foo.bar.baz").
An indication to use a (different) handler can be given by naming the module. Note that when this is passed back from an already-executing handler, the new handler will not have an opportunity to read its own conf stuff from the config file.
An indication to serve some content to the user agent can be given with
{content, data()}. data() is a string or a
binary which is sent to the user agent verbatim. HTTP headers should be
present in the conf() which accompanies this response; if
important headers such as res_content_type are not present,
reasonable defaults will be assumed.
In addition, content can be served to the user by streaming it from
the handler to OpenFlax. This is recommended for content which is
too large to be efficiently passed all at once in a
{content, data()} response.
Streaming content is indicated with the response
{stream, pid(), size()}. This implies that the handler
has already spawned a streaming process to stream the content,
and that the pid() is the pid of that process. If the size of
the content is known, it should be stated (in octets) as size();
if it is not known, the atom unknown should be passed instead.
The response will be altered accordingly, cancelling the possibilities for
a cacheable response or a persistent connection.
The streaming process should follow the following protocol:
The streaming process should wait for the message
{pid(), stream_open} before it begins. The pid()
in this message is the pid of the receiving process internal
to the OpenFlax web serving logic, and which should be the destination of
all subsequent messages from the streaming process.
The streaming process should then send the receiving process a series
of messages of the form {self(), stream_data, data()}. After
each message, it should wait for a message {pid(), stream_acknowledge,
data} before proceeding to send the next message, to keep the stream
in synch. data() is as described above in
{content, data()}, except that it need not be the entire content
of the response, naturally. self() is the pid of the
streaming process and pid() should be the pid of
the receiving process.
When the streaming process is finished, it should send {self(),
stream_close} to the receiving process. It may then wait for a
response of the form {pid(), stream_acknowledge, close}, at
which point both processes know that they are done, and must stop
communicating. The streaming process may then exit.
For an example of streaming with a known size,
see openflax.mod.file. For an example of streaming with
an unknown size, see openflax.mod.watch.
The openflax.handler behaviour requires that the handler
implement the following functions.
start(conf()) -> conf()
This function is used to initialize any resources needed by the module
(create ets tables, spawn processes, etc.) It is passed a
conf() which comes
from the configuration file (and possibly other places) which may be
used to specify startup behaviour.
On success, this function must return a new conf().
The settings in this conf() will be merged into the
global section of the configuration; therefore, it should
return a mostly-empty conf(), and should not simply
return a modified version of the conf() that was passed
to it, lest it pollute the global section with module-specific settings.
On failure, this function should just crash.
serve(conf()) -> {response(), conf()}
Implements the action undertaken when this resource is served.
This function should return a response() as
described above.
This function should not call any of the following for generating a response:
openflax.serve
openflax.http.response
openflax.http.interface
gen_tcp
On failure, this function should just crash. A "500 Server Error" will generated for the benefit of the user agent.
stop(conf()) -> conf()
This function can be used to release any resources required by the module.
On success, this function must return a conf(), most
likely a modified or unmodified version of the one it was passed.
On failure, this function should just crash.