Progress with April
Monday, June 9th, 2008Progress April has been coming along nicely. I’ve written most of the HTTP layer (Client, Request and Response). There are a few “extras” left to code, but essentially I’ve now got a functional “80/20″ HTTP client.
Usage looks like this:-
$request = new April_Request("http://www.google.com/search");
$request->GET['q'] = “REST frameworks”;
$request->GET['hl'] = ‘en’;
$request->setHeader(’user-agent’, ‘Mozilla/5.0′);
$client = new April_Client();
$response = $client->request($request);
if($response->isOk()){
echo $response->getBody();
}
If you’re familiar with Zend_Http_Client then the API will be familiar (although it’s quite different under-the-hood).
I’ve also started implementing the “REST Framework” part of the project. This part has gone through quite a lot of refactoring recently as I’m not 100% sure how best to implement it (as a design challenge). The primary goals are:-
- Make it easy for someone to implement a REST API from the client perspective*
- Respect a “Resource Orientated Architecture” (ROA)
* I’m deferring the “server” aspect for now (I think it’s an area that gets blurry with “standard frameworks” and I don’t want to go there!).
Respecting an ROA basically means restraining from creating RPC-like services (à la Zend Framework). So the service doesn’t expose methods like $s3->ListAllMyBuckets(), but allows you to perform an HTTP-verb action (HEAD/GET/PUT/POST/DELETE) upon a resource: $s3->getBuckets()
I’ve come up with a nifty solution that uses PHP’s magic __call() method. It breaks apart the verb and resource (”get” and “Buckets”) and can then take the appropriate action in order to “get Buckets”. So if the REST service has a resource called Object the service would let you call:-
- getObject(…)
- putObject(…)
- headObject(…)
- postObject(…)
- deleteObject(…)
Internally the service object has an HTTP client, so when one of these methods is called it must:-
- Build an HTTP request object, based upon the verb, resourceType, resource, url and any other parameters that it needs.
- Execute the HTTP request and return the Response
How an HTTP Request is built depends mostly on the service (an Amazon S3 request will be different to a Flickr request) and on the Resource (an S3 object-resource is represented differently than a Flickr image).
I’ve reflected this in the class-design: you extend the base April_Service and override the buildRequest() method to apply any service-specfic logic when building a Request. The resource-specific logic is handled slightly differently: we use the Strategy Pattern and load in a ResourceHelper object. The reason for this twofold:-
- A service might have many resources and it’s easier to maintain the code if their nuances are self-contained (imagine a single My_Service class trying to handle a dozen types of resources).
- Resource-specific logic is also required when decomposing the Response to our HTTP Request (ie we have an HTTP Response with some XML… how do we convert that to a PHP representation of the resource?).
So the ResourceHelper essentially converts a “resource” to/from PHP and “service” representations.
At the moment I’m really pleased with how this design has played out. In order to implement a particular REST service you just need to create a Service class and then a ResourceHelper for each type of resource.
Here’s some code to demonstrate all this:-
April_Service: April_Service.phps
The base service, extend this to implement a REST API.
April_Paste2: April_Paste2.phps
An service implementation for a Paste2.org API
April_Paste2_Paste_Helper: April_Paste2_Paste_Helper.phps
A ResourceHelper for a “Paste” resource within the Paste2 service
paste2test: paste2test.phps
A simple use-case showing how the Paste2 service could be used