An HTTP API in 5 minutes
Often when developing an interactive application, you find the need to provide a programmatic way for other applications or tools to work with your application. This usually takes the form of an XML API of some sort. There are a number of XML-based API systems available, such as XML-RPC, SOAP, etc. Often, though, those are overkill for what you need and add unneeded complexity. I've developed a quick and easy way to handle API-type requests that works very well, and is extremely easy to add to your application. I use this method when I'm looking for a quick way to access application functionality from AJAX or from another application (such as a client's PHP app) because of how simple it is to add and use.
I will start by presenting the entire API controller.
package MyApp::Controller::Api; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller' } use JSON::XS; use XML::Simple; sub api : Chained('/') PathPart('api') CaptureArgs(0) { my ( $self, $c ) = @_; if (!exists($c->stash->{'api_params'})) { $c->stash->{'api_params'} = $c->req->params; if (!exists($c->stash->{'api_params'}{'output'})) { $c->stash->{'api_params'}{'output'} = 'json'; } } elsif (!exists($c->stash->{'api_params'}{'output'})) { $c->stash->{'api_params'}{'output'} = 'json'; } ## This sets the default response as a failure. $c->stash->{apiresponse} = { 'processed' => 0, 'status' => 'failed' }; ## this part is optional - if you don't need any kind of authentication, you ## can disable this. if (!$c->stash->{'api_params'}{'authkey'} || $c->stash->{'api_params'}{'authkey'} ne $c->config->{'notifications'}{'authkey'}) { # we fail authentication, so we dump them out to the auth-failed action $c->stash->{'apiresponse'} = { 'processed' => 0, 'error' => { 'general' => 'service not available' }}; $c->detach(); } } sub gettime : Chained('api') PathPart('gettime') Args(0){ my ($self, $c) = @_; # do something interesting. my $server_time = scalar localtime; $c->stash->{apiresponse} = { 'processed' => 1, 'status' => 'OK', 'server_time' => $server_time, }; } sub end : Private { my ( $self, $c ) = @_; if ($c->stash->{'api_params'}{'output'} eq 'xml') { my $xml = XML::Simple->new( NoAttr => 1, RootName => 'apiresponse', XMLDecl => 1); $c->response->body($xml->XMLout($c->stash->{'apiresponse'})); } else { my $jsonobject = JSON::XS->new->utf8->pretty(1); my $responsetext = $jsonobject->encode($c->stash->{'apiresponse'}); $c->response->body($responsetext); } }
How it works
The way this method works, you simply create a new controller and add it to
your application. The API controller does most of the work via two actions.
First, the 'api' action provides for some basic setup. All your API calls
chain off of this. The api
action does some basic set up and if appropriate
does authentication checking.
The authentication check here simply compares a config value,
authkey
, to the authkey provided as a query parameter. You can make
this check as complex or as simple as you want - perhaps involving
normal Catalyst authentication - or remove it entirely, depending on
your usage.
The second action is end
. The end
action simply examines the 'output'
parameter (if passed) and decides what format to respond in. The default is to
respond with a JSON object, but if output is set to 'xml' it will respond with
a simple XML structure.
How to use it
In the example above, I added a gettime
action chained off of the api
action. This is a simple routine to tell the calling application the time on
the server. For the most part, it's self explanatory. The key here is that the
sub should create a hash in $c->stash->{'apiresponse'}
which will be
returned to the calling application.
This information in the $c->stash->{'apiresponse'>
is returned directly
to the calling application in whatever format (JSON or XML) was selected.
I generally provide two keys inside the apiresponse
. The first is
processed
, which is simply a true/false which tells whether the requested
method completed processing normally. The second key is status
, which I use
as a success/failed indicator. There is no requirement that you use either of
these, but I find that they help both for debugging during development and for
tracking down problems in production.
One other addition I make is that within the API I work with
$c->stash->{'api_params'}
instead of $c->request->params
. In the
root of the chain, sub api
, I copy $c->request->params
to
$c->stash->{'api_params'}
if it isn't already set. I do this because it
makes it exceedingly easy to work with the API calls from within your own
application. Normally, when you use $c->forward
the query parameters
from your original request are present. Using $c->stash->{'api_params'}
means you can call your api internally with whatever arguments you want,
regardless of what was passed to the original query.
Don't take my word for it
This is about the simplest way to handle API actions in a Catalyst application. Following the simple conventions I discussed and using the actions shown at the beginning of this article will give you a solid and simple-to-use API system in your application.
You don't have to take my word for it. Take the code above and add it to your application, try it out, and see what it does.
AUTHOR
Jay Kuri <jayk@cpan.org>