Syntax-K

Know-How für Ihr Projekt

Perl Documentation

NAME

AxKit2::Plugin - base class for all plugins

DESCRIPTION

An AxKit2 plugin allows you to hook into various parts of processing requests and modify the behaviour of that request. This class is the base class for all plugins and this document covers both the details of the base class, and the available hooks and the consequences the return codes for those hooks have.

See "AVAILABLE HOOKS" for the hooks, and "API" for the API provided to all plugins.

WRITING A SIMPLE PLUGIN

Most plugin authors should start at AxKit2::Docs::WritingPlugins. However a hook consists of the following things:

Although plugins are classes, they do not need the usual perl extra stuff such as a package declaration, a constructor (such as new()), nor do they require the annoying "1;" at the end of the file. AxKit2 adds those things in for you.

All plugins are simple blessed hashes.

API

Methods marked virtual below can be implemented in your plugin and will be called at the appropriate times by the AxKit2 framework.

$plugin->register (virtual)

Called when the plugin is initialised, and should be used to call $plugin->register_hook(...). However see "AVAILABLE HOOKS" regarding naming hook methods so they are automatically registered.

Example:

sub register {
    my $self = shift;
    $self->register_hook('response' => 'hook_response1');
    $self->register_hook('response' => 'hook_response2');
}

$plugin->register_hook( HOOK_NAME => METHOD_NAME )

Register method METHOD_NAME to be called for hook HOOK_NAME.

$plugin->init() (virtual)

Called when the plugin is compiled/loaded. Use this to do any per-plugin setup.

Example:

sub init {
    my $self = shift;
    $self->{dhb} = DBI->connect(...);
}

$plugin->config

Retrieve the current config object. See AxKit2::Config. If you pass the name of a configuration directive of your own plugin, you can get/set values directly without bothering about name clashes with other plugins.

If you call this method in array context, it will return a list of all values that were set, or the empty list if nothing was configured. In scalar context, only the first value is returned.

If you pass a list following the directive name, these values replace the current values.

Directives of other plugins can be accessed through $plugin->config->notes('<package name>::<directive name>').

$plugin->client

Retrieve the current client object. See AxKit2::Connection.

WARNING: This object goes out of scope outside of the hook. See plugins/aio/serve_file for an example of where this might become relevant.

$plugin->log( LEVEL, MESSAGE )

Write a log message. Gets passed to whatever logging plugins are loaded.

$plugin->plugin_name

Retrieve the name of this plugin

$plugin->hook_name

Retrieve the name of the currently executing hook

$plugin->notes

If you need to store plugin-private data associated with a request, you can store them with $plugin->notes($key,$value) and retrieve them via $plugin->notes($key). It works exactly like $client->notes(...), except that the key is made unique for each plugin.

$plugin->send

This is the interface to user-defined callbacks and message passing. Plugins can define messages they handle like this:

sub message_login { # sent by plugins/authenticate
  my ($self, $user) = @_;
  # let an imaginary database setting do something on login
  $self->config->database->login($user);
}

Other plugins can then send a message using $plugin->send('login',$user). This is intended for typical callback situations, but it also allows plugins to offer services to other plugins, like this:

my $cached_object = $plugin->send('cache',$key);

Since all plugins are searched for a corresponding message handler, plugins are independent of the actual implementation of such services.

By default, the first message handler that is found is run and it's return value returned. If you want that other handlers are run after it, return an empty list ().

If you need even more complex processing, see hook_handler below.

CONFIGURATION DIRECTIVES

Any sub named conf_<name> is taken as a configuration directive. If you simply declare a sub without supplying the function body, AxKit will store any values given in $self->config('<name>'), supporting multiple values at once.

This declaration:

sub conf_FooBar;

will create this entry:

$self->config('FooBar');

There are actually may possible variation on directive names that are all equivalent. Anywhere a directive name is used, you can use any variation like this:

FooBar
foo_bar
foo-bar
FOO_BAR
foobar

and some more. This includes sub conf_FOO_BAR or $self->config("foo-bar").

By default, a single values are accepted and stored, while quoting is supported:

FooBar "demo 3"

For different ways of validating configuration directives, you can add a custom validation routine:

# use predefined "multiple strings" validator
sub FooBar : Validate(STRINGLIST);

# this directive takes one of two values
sub FooBar : Validate(sub { die "invalid value" if $_[1] !~ m/^(red|green)$/i; lc($_[1]); });

If you want to have custom actions when the directive is parsed, supply a function body:

# store a database connection instead
sub FooBar { my ($parser, $value) = @_; return DBI->connect($value); }

# preprocess parameters
sub FooBar { my ($parser, @values) = @_; return join(",",@values); }

Of course, all this can be combined:

# this is a rather nonsensical example, do you spot why?
sub FooBar : Validate(sub { split(/,/,$_[1]); }) {
    my ($parser, @values) = @_;
    return join(",",@values);
}

For full details and more ways of designing configuration directives, see AxKit2::Config.

OTHER FACILITIES

Since AxKit2 is based on Danga::Socket, those facilities are also available to plugins. You should read it's documentation for details, but two use cases are rather common:

Timers

Have a custom callback at (roughly) a certain time from now:

# add a timed callback
my $timer = Danga::Socket->AddTimer($seconds, $subref);
# later, if you decide the timer is no longer needed
$timer->cancel;

Pass a closure if you need values from $plugin, as the callback is called without arguments or $self reference.

Watching additional FDs

If you want to watch other file descriptors for IO, for example a permanent network connection to some other host, or a subprocess that you spawned for some asynchronous processing, add a new package at the end of your plugin, like this:

# ... your regular plugin code ...

package My::Plugin::SubprocessEvent
use base 'Danga::Socket';
# ... and so on, copy the example code from Danga::Socket docs

Since watching a FD happens in parallel to other requests, you will have to use global variables or similar to communicate with your plugin.

AVAILABLE HOOKS

In order to hook into a particular phase of the request you simply write a method called hook_NAME where NAME is the name of the hook you wish to connect to.

Example:

sub hook_logging {

If your plugin needs to hook into the same hook more than once, you will need to write a register() method as shown above. This is usually the case if you need continuations for some reason (such as doing asynchronous I/O).

All hooks are called as a method on an instance of the plugin object. Params below are listed without the $plugin or $self object as the first param.

For some plugins returning CONTINUATION is valid. For details on how continuations work in AxKit2 see "CONTINUATIONS" below.

In all cases below, returning DECLINED means other plugins/methods for the same hook get called. Any other return value means execution stops for that hook.

Following are the available hooks:

logging

Params: LEVEL, ARGS

Called when something calls $obj->log(...) within AxKit. Logger is expected to provide a way to set log level and ignore logging below the current level.

Return Values:

connect

Params: None

Called immediately upon connect.

Return Values:

pre_request

Params: None

Called before headers are received. Useful if keep-alives are present as this is called after a keep-alive request finishes but before the next request.

post_read_request

Params: HEADER

Called after the headers are received and parsed. Passed the AxKit2::HTTPHeaders object for the incoming headers.

Return Values:

body_data

Params: BREF

Called for POST, PUT, etc verbs as each packet of body data is received. The param is a SCALAR reference to the data received.

Return Values:

uri_translation

Params: HEADERS, URI

Called to translate the URI into a filename and path_info. See plugins/uri_to_file for an example of what needs to be done.

Return Values:

mime_map

Params: HEADERS, FILENAME

Called to set the MIME type for the given filename. See plugins/fast_mime_map for an example of what needs to be achieved.

Return Values - see "uri_translation" above.

access_control

Params: HEADERS

Return Values - see "uri_translation" above.

authentication

Params: HEADERS

Return Values - see "uri_translation" above.

authorization

Params: HEADERS

Return Values - see "uri_translation" above.

fixup

Params: HEADERS

Return Values - see "uri_translation" above.

xmlresponse

Params: PROCESSOR, HEADERS

If this URI is to be treated as an XML request, this hook is for you. Passed an AxKit2::Processor object and the headers.

Return Value:

response

Params: HEADERS

Main response phase. Used for sending normal responses to the client.

Return Value:

response_sent

Params: CODE

Called after the response has been sent to the browser. The parameter is the response code used (e.g. 200 for OK, 404 for Not Found, etc).

The return codes for this hook are used to determine if the connection should be kept open in a keep-alive request.

Return Value:

disconnect

TBD

error

Params: ERROR

Called whenever a hook die()s or returns SERVER_ERROR.

Return Value:

message

Params: MESSAGE_NAME, ARGUMENTS

Called whenever a plugin send's a message.

Return Value:

CONTINUATIONS

AxKit2 is entirely single threaded, and so it is important not to do things that take significant runtime away from the main event loop. A simple example of this might be looking up a request on a remote web server - while the AxKit process waits for the response it is important to allow AxKit to continue on processing other requests.

In order to achieve this AxKit2 uses a simplified version of a technique known in computer science terms as a continuation.

In simple english, this is a way to suspend execution of one request and continue it at an arbitrary later time.

AxKit2 has a form of continuations based on the core event loop. Some hooks can suspend execution by returning CONTINUATION, and have execution of the request continue when some event has occured.

Requests continue with the next handler in the chain, so usually you would register two or more handlers for a single hook (as the example below illustrates). As a convenience, you may instead return a list with sub reference as second value. That sub will be called as if it was a regular, registered handler. These are called ad-hoc continuations in AxKit2. As a third alternative, an easy-to-use helper module AxKit2::Continuation makes continuations yet another bit easier.

A typical usage of this is when you need to perform an action that may take some time. An example of this is disk I/O - typical I/O in the common POSIX read/write style APIs occurs in a blocking manner - when you ask for a read() the disk seeks to the position you need it to go to when it can do so and the read is performed as soon as possible before the API call returns. This may take very little CPU time because the OS has to wait until the disk head is in the correct position to perform the actions requested. But it does take "clock" time which can be put to better use responding to other requests.

In asynchronous I/O the action is requested and a callback is provided to be called when the action has occured. This allows the event loop to continue processing other requests while we are waiting for the disk.

This is better explained with a simple example. For this example we'll take the stat() system call in an attempt to find out if the filename we are requesting is a directory or not. In perl we would normally perform this with the following code:

sub hook_response {
    my $self = shift;
    my $filename = $self->client->headers_in->filename;
    if (-d $filename) {
        ....
    }
    $self->do_something_else();
    return OK;
}

However with AIO and continuations we can re-write that as:

sub register {
    my $self = shift;
    $self->register_hook('response' => 'hook_response1');
    $self->register_hook('response' => 'hook_response2');
}

sub hook_response1 {
    my $self = shift;
    my $client = $self->shift;
    my $filename = $self->client->headers_in->filename;
    IO::AIO::aio_stat $filename, sub {
        if (-d _) {
            ...
        }
        $self->do_something_else();
        $client->finish_continuation;
    };
    return CONTINUATION;
}

sub hook_response2 {
    return DECLINED;
}

Using ad-hoc continuations, this example would look like this:

sub hook_response {
    my $self = shift;
    my $client = $self->shift;
    my $filename = $self->client->headers_in->filename;
    IO::AIO::aio_stat $filename, sub {
        if (-d _) {
            ...
        }
        $self->do_something_else();
        $client->finish_continuation;
    };
    return CONTINUATION, sub {
        return DECLINED;
    };
}

A first read will prove one thing - AIO and continuations are a lot harder than regular procedural code. However often the performance benefits are worth it.

In general if you need to use continuations then consult the plugins in the aio/ directory, and send emails to the mailing list, as they are generally a big source of hard to locate bugs.

SEE ALSO

AxKit2::Connection

AxKit2::HTTPHeaders

AxKit2::Constants

AxKit2::Processor