The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

Writing Plugins

Everything in AxKit2 is controlled via plugins. This helps keep the core of AxKit2 very simple and lightweight, yet makes it very powerful and extensible.

Although AxKit comes with some included plugins to deliver flat files, and some demo plugins to show how to transform XML, the real power of AxKit2 will only be realised when you write your own plugin. Don't be afraid - this is very simple to do.

To make life easy, lets step through a working example.

A Simple XSLT Engine

We'll start with a simple plugin that transforms XML files to HTML using XSLT. This example will cover some of the basics of how the XML transformation system of AxKit2 works.

Setting up the Directories

Start in the AxKit2 root directory (where you can run ./axkit from). First we'll create a directory to store your XML and your stylesheets:

  $ mkdir myfirstplugin
  $ mkdir myfirstplugin/webroot
  $ mkdir myfirstplugin/stylesheets
  $ mkdir myfirstplugin/plugins

Server Configuration

Now we need to tell AxKit where all our files are and what port to listen on. Here's a minimal config which you should store in myfirstplugin/axkit.conf:

  # Setup error logging to the maximum level:
  Plugin    logging/warn
  LogLevel  LOGDEBUG
  
  # we need to load this to map URLs to filenames
  Plugin uri_to_file
  
  # Setup our server
  <Server>
    Port 8080
    DocumentRoot myfirstplugin/webroot
    DirectoryIndex index.xml
    
    # Load our plugin
    PluginDir myfirstplugin/plugins
    Plugin    serve_xml
  </Server>

Now we can try and start AxKit and see what happens:

  $ ./axkit -c myfirstplugin/axkit.conf
  L7 uri_to_file register_hook: uri_translation => hook_uri_translation
  could not open myfirstplugin/plugins/serve_xml: No such file or directory
  at lib/AxKit2/Plugin.pm line 103, <$fh> line 13.

Well we should have expected this -- we haven't written our plugin yet!

Plugin Code

So load up your editor on myfirstplugin/plugins/serve_xml and enter the following perl code:

  #!/usr/bin/perl -w
  
  sub hook_xmlresponse {
    my ($self, $input) = @_;
    
    $self->log(LOGDEBUG, "Serving up a file");
    
    my $stylesheet = './myfirstplugin/stylesheets/default.xsl';
    
    my $out = $input->transform(XSLT($stylesheet));
    
    return OK, $out;
  }

The key part of this is the $input->transform(...) method call, which transforms the input using the given transformation types. Available types include XSP(), TAL(), XPathScript() and of course XSLT().

Once the transformation is done we return OK, $out to let AxKit know that the transformation was successful and we should deliver the result to the browser.

[As a sidenote here, I include the #! perl shebang line to help syntax highlighters know that this is perl code.]

Now if you run AxKit again we shouldn't see any error, but instead some debug output telling us our plugin was loaded:

  $ ./axkit -c myfirstplugin/axkit.conf
  L7 uri_to_file register_hook: uri_translation => hook_uri_translation
  L7 serve_xml register_hook: xmlresponse => hook_xmlresponse

Now if we point a web browser at this server we will get an error because we haven't created an index.xml and default.xsl yet. So lets do that now.

XML and XSLT Files

myfirstplugin/webroot/index.xml:

  <?xml version="1.0"?>
  <dromedaries>
    <species name="Camel">
      <humps>1 or 2</humps>
      <disposition>Cranky</disposition>
    </species>
    <species name="Llama">
      <humps>1 (sort of)</humps>
      <disposition>Aloof</disposition>
    </species>
    <species name="Alpaca">
      <humps>(see Llama)</humps>
      <disposition>Friendly</disposition>
    </species>
  </dromedaries>

myfirstplugin/stylesheets/default.xsl:

    <?xml version="1.0"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:template match="/">
      <html>
      <head><title>Know Your Dromedaries</title></head>
      <body>
        <table bgcolor="eeeeee" border="1">
        <tr>
        <th>Species</th>
        <th>No of Humps</th>
        <th>Disposition</th>
        </tr>
        <xsl:for-each select="dromedaries">
          <xsl:apply-templates select="./species" />
      </xsl:for-each>
      </table>
      </body>
      </html>
    </xsl:template>
    <xsl:template match="species">
      <tr>
      <td><xsl:value-of select="@name" /></td>
      <td><xsl:value-of select="./humps" /></td>
      <td><xsl:value-of select="./disposition" /></td>
      </tr>
    </xsl:template>
    </xsl:stylesheet>

Now when you request the page you should get a HTML table listing the dromedaries in the index.xml file.

Some browsers might also request the non-existant favicon.ico too, which may cause an error to appear in the terminal. Don't worry about that for now.

Congratulations, your first plugin is complete!

Making Your Plugin Configurable

One area where AxKit2 really shines is that each plugin can define its own configuration parameters. Our plugin is a perfect candidate for that because we hard-coded the XSLT stylesheet. Lets make that configurable now.

To add configuration parameters we need to specify them as soon as the plugin is loaded, in the plugin's init() method.

    A Sidetrack - What is a 'Plugin'?

    Skip this section if you don't want to know the gory details - you don't need to know about for writing simple plugins.

    Knowledgable perl hackers will have noticed that our hook_xmlresponse method above received two parameters, the first being $self, and yet we didn't create a perl class with all the annoying bless verbiage. Well yes, a plugin is a perl class, and $self is an instance of that class. We simply went out of our way to hide all the horrible perl guts from you.

    If you prefer all the perl guts to be exposed, you can create a full blown package as your plugin instead, install it in the usual perl install locations, and load it with:

      Plugin My::Plugin

    As long as it has '::' in the name, AxKit will recognise it as a perl module and load it that way. Just be sure to 'use base q(AxKit2::Plugin)' so you get all the base class methods.

To add configuration directives we just add sub definitions starting with "conf_":

  sub conf_XSLTStyle;

There we added a configuration directive called XSLTStyle which we'll use to define our stylesheet. Using this default behaviour, AxKit will store any data in the configuration file as $self->config('XSLTStyle'), returning an array if multiple values were given.

Simple huh?

Now we need to tie all that together. First add this to your config after "Plugin serve_xml":

  XSLT_Style myfirstplugin/stylesheets/default.xsl

And now modify your hook_xmlresponse() method to get this new value using $self->config(...):

  sub hook_xmlresponse {
    my ($self, $input) = @_;
    $self->log(LOGDEBUG, "Serving up a file");
    
    my $stylesheet = $self->config('XSLTStyle');
    my $out = $input->transform(XSLT($stylesheet));
    
    return OK, $out;
  }

And we're done! Run (or restart) axkit (./axkit -c myfirstplugin/axkit.conf) and ensure that it all works.

Chained Transformations

One of the powerful features of AxKit was always being able to easily chain transformations together, this makes complex XSLT transformations much simpler, among other benefits.

Lets modify our plugin to support multiple XSLT transformations.

The simplest way to do this is to modify our XSLT_Style directive to support multiple values, each value being a new XSLT stylesheet. First lets modify our configuration to detail what we want:

  XSLT_Style myfirstplugin/stylesheets/sort.xsl \
             myfirstplugin/stylesheets/default.xsl

The idea here being that one XSLT will sort our dromedaries, and the second will turn them into HTML.

Lets create our sort.xsl file first:

  <?xml version="1.0" encoding="ISO-8859-1" ?>
  <xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="1.0">
  
  <xsl:template match="dromedaries">
  <dromedaries>
    <xsl:apply-templates select="species">
        <xsl:sort select="@name"/>
    </xsl:apply-templates>
  </dromedaries>
  </xsl:template>
  
  <!-- Identity Template -->
  <xsl:template match="*|@*">
    <xsl:copy>
      <xsl:copy-of select="@*"/>
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>
  </xsl:stylesheet>

Note the use of the identity template. If you haven't used XSLT before take note of that - it's a very useful template for debugging.

So now we need to have a list of XSLT stylesheets in our config, and make use of a list in the ->transform() method.

The nice thing about AxKit's configuration mechanism is that it automatically copes with multi-valued configuration. We don't have to change anything to cope with multiple values.

When we retrieve the values, we just call $self->config(...) in array context and we get all values instead of just one.

  sub hook_xmlresponse {
    my ($self, $input) = @_;
    $self->log(LOGDEBUG, "Serving up a file");
    
    my @stylesheets = $self->config('XSLTStyle');
    my $out = $input->transform(map XSLT($_), @stylesheets);
    
    return OK, $out;
  }

So instead of running XSLT() once, we map it over @stylesheets.

And that's it. Restart AxKit and make sure this returns a table of sorted results.

What Are the APIs?

We've glossed over a lot of what's actually going on under the hood here, so if you want to know more, here are some pointers to extended documentation.

Plugins

To find our more about the Plugin class, which is the $self object in the examples above, see AxKit2::Plugin. This document also covers all the available hooks and what effect the return codes for each of those hooks may have.

Transformations

In AxKit2 the $input->transform() method is provided by the Processor module. See AxKit2::Processor for the API details in there.

Config

So far only seen in our get_style() method. The config object gives us access to the axkit.conf file and a storage place for plugin-specific configuration parameters. See AxKit2::Config for details on the API available.

The Connection

So far we haven't dealt with the client at all - AxKit did all that work for us. However many plugins will want to read headers, set outbound headers, and maybe even write data directly to the client. This is done through the Connection object which is accessed via $self->client in a plugin. See AxKit2::Connection for the available API.

There are three client APIs which I'll detail here as a shortcut as they are the most used (though more documented in the above link).

$client->headers_in returns the inbound headers.

$client->headers_out returns the outbound headers.

$client->send_http_headers writes the outbound headers to the client.

For documentation on the header object that headers_in() and headers_out() return, see AxKit2::HTTPHeaders.

Constants

Finally, for a list of constants useful as return values from plugins, see AxKit2::Constants.

Copyright

This document is Copyright 2006 Apache Software Foundation.