Thursday, October 20, 2011

Catalyst::Action::Serializer::Plist::Binary

Last weekend, at DrupalCamp WNY, I attended a session entitled Semi-homemade Web Services in Drupal 7 given by Brian Fending. One of the examples was a RESTful API which provided data to an IPad app serialized as binary plist. This struck me as one of the better ideas I've heard lately. Property lists are native to iOS, and the binary format is far more space-efficient than the XML format. If it is at least as efficient as JSON, and some client-end processing can be reduced or avoided, it could be a big win.

Currently, the interfaces I design are being implemented in Catalyst. Since there is no Catalyst plist serializer available on CPAN, I decided to write one (two, actually - I included the XML-based format for completeness). Fortunately, there are plist modules on CPAN - Data::Plist::XMLWriter and Data::Plist::BinaryWriter. Creating the serializer actions requires just a little glue.

Here's the binary serializer action:
package Catalyst::Action::Serialize::Plist::Binary;
use parent 'Catalyst::Action';
use Data::Plist::BinaryWriter;

sub execute {
    my ($self, $controller, $c) = @_;
    $c->res->content_type('application/x-plist');
    $c->res->output(Data::Plist::BinaryWriter->new->write($c->stash->{$controller->config->{'stash_key'} || 'rest' }));
}

1;
And here's the XML serializer action:
package Catalyst::Action::Serialize::Plist;
use parent 'Catalyst::Action';
use Data::Plist::XMLWriter;

sub execute {
    my ($self, $controller, $c) = @_;
    $c->res->content_type('text/plist');
    $c->res->output(Data::Plist::XMLWriter->new->write($c->stash->{$controller->config->{'stash_key'} || 'rest' }));
}

1;

Now to wire it up.

In our Root controller:
sub end : ActionClass('Serialize') {}

In a REST controller:

To add XML plist serialization:
__PACKAGE__->config->{map}{'text/plist'} = 'Plist';
To add binary plist serialization:
__PACKAGE__->config->{map}{'application/x-plist'} = 'Plist::Binary';

In the client application, set the Content-Type header, content-type query parameter or Accept header appropriately, as described in Catalyst::Controller::REST, and voila.

Disclaimer: I whacked these together in a few minutes last night and have not tested them extensively. As time permits, I'll test them systematically and productionalize them.