lunedì 29 ottobre 2007

RESTful Web Services - A Better HTTP interface

Before moving to the remaining Amazon S3 examples from Chapter 3 of the RESTFul Web Services book, let’s take another look at the code. The previous examples were strongly inspired by the corresponding Ruby examples. This, paired with the use of the libCurl bindings, has led to code which is brittle and hard to modify.

As an example, let’s consider the code for S3Object>>open:method:headers:. This method requires the HTTP method in order to compute the correct signature for the request, but then ignores it by sending request download, which is the message used to send a GET request. A possible solution for this is to use a case-like solution in order to send the right message; the code would then become something like this:
(method asLowercase == 'put')
ifTrue: [request upload]
ifFalse: [request download]

Now, this isn’t just ugly, it’s plain bad OO code. This means we have to look for another solution.

The solution that I’ve chosen is based on the pattern used for Seaside’s and HV2’s Canvas systems. It involves having an S3Request object that wraps a Curl instance and provides a simple interface. S3Request has different subclasses for the various HTTP methods: S3GetRequest, S3PutRequest etc.

The code for S3Request and its subclasses may be found in package RWS-gc.20 from the RWS repository. S3Request is defined as:
Object subclass: #S3Request
instanceVariableNames: 'headers url request contents'
classVariableNames: ''
poolDictionaries: ''
category: 'RWS-Examples'

I won’t provide a detailed explaination for this class, save for the #send message:
S3Request>>send
request := Curl new.
request url: url.
headers keysAndValuesDo:
[:key :value |
request addHeader: key, ': ', value].
request onHttpHeaders.
self perform.
^ request

#send creates a new Curl object, sets the headers and url for the new request, then sends a #perform message to itself before returning the Curl object. #perform is an abstract message implemented by the various subclasses of S3Request. For example, S3PutRequest>>perform looks like
S3PutRequest>>perform
contents ifNotNil: [request contents: contents].
request upload

This is a simple and elegant solution to our problem that, while adding 5 new classes to the system, keeps the overall complexity of the code to a much lower level.

In order to use this new API, I’ve added some more messages to the protocols of S3Object. If you browse the latest versions of this class, you’ll find many methods such as #get:headers:, #put:contents:headers: etc. This methods replace the #open:method:headers: message and its variants.

In the next post, we’ll see how to interact with the Amazon S3 service. Meanwhile, I suggest you go over Chapter 3 of Richardson and Ruby’s book in order to get yourself acquainted with S3’s lingo.

Nessun commento: