ONLamp.com
oreilly.comSafari Books Online.Conferences.

advertisement


Developing RESTful Web Services in Perl
Pages: 1, 2

GET to Read

By visiting a URL like "/=/model/book/id/<ID>" on the server, where ID is a string of numbers and dashes, you will fetch the YAML file describing the book with the given ID. If no book can be found with that ID you will be handed a "404 Not Found" error.



Here's the important code from the sample server. In this snippet, the $id variable is already set to an untainted ID value pulled out of the URL.


# Look up the resource file
my $filename = get_local_path($id);
if (-f $filename) {

    # Open and slurp up the file and output the resource
    open my $bookfh, $filename
    or barf 500, "I Am Broke", "Cannot open $filename: $!";

    print $q->header('text/yaml');
    print do { local $/; <$bookfh> };
}

# No such resource exists
else {
    barf 404, "Where is What?", "Book for $id does not exist.";
}

First, we look up the filename on the local disk. The code checks to see if the file exists and returns a 404 if not. Otherwise, we slurp the file and send back a "text/yaml" response containing the YAML data. Since I'm storing the data on the disk in the format I'm using to communicate, I can just send the file directly. Had this been a database record look or something, I may have needed to serialize the data into YAML format using YAML::Dump().

If you have set up the CGI script, you can attempt to fetch one of these records directly after they have been added. For example, you might visit this URL into your browser:

http://localhost/cgi-bin/library.cgi/=/model/book/id/0-936083-11-5

You will be asked to save oo open the downloaded file.

POST to Create

Now that we've learned to fetch records (which we haven't learned how to create yet), let's learn how to put new books into the system. This is implemented using POST. In this implementation, the server will always assign an ID to a newly created book record, but that ID will be based upon the ISBN recorded in the file (if the "isbn" field is present). If you attempt to create a book resource with an ISBN that has already been submitted, you will receive an error.

Here's the code that will be run if you POST a request to "/=/model/book". I've broken it up a bit to make the explanation a little easier.


# Check to make sure the input book is sane
my $book = check_book( $q->param('POSTDATA') );

# If we have an ISBN (some books don't!), then die if we already have
# it because we don't permit POST cannot for updates!
if ($book->{isbn} and -f get_local_path($book->{isbn})) {
    barf 500, 'Not Gonna Do It',
        'A POST may not be used to update an existing book.';
}

# Our data is sane!

The first thing we do is validate the sanity of the data. The check_book() function performs several tasks to verify the sanity of the request. You can look at the code yourself to see the details, but I will mention that this function will cause a "415 Unsupported Media Type" or "400 Bad Request" or "500 Internal Server Error" depending on the kind of problem encountered. I have tried to pick the code that is most appropriate status code when possible. I highly recommend being familiar with the Status Code Definitions of the HTTP/1.1 protocol to make sure you are sending the best code possible for a given response. And when in doubt, you return a "500 Internal Server Error" to the client, which is the most general error status you can return.


# Figure out an ID, this is either the ISBN or a generated ID
my $id = $book->{isbn} ? $book->{isbn} : next_id;

# Store the ID for reference within the record
$book->{id} = $id;

# Save the resource
eval { YAML::DumpFile(get_local_path($id), $book) };
barf 500, 'I Am Broke', $@ if $@;

Now that we know that the data is sane, we will set up the ID of the book and save the book as a YAML file. If the book has an ISBN, that is used as the ID. If the book does not have an ISBN listed, then a new ID is generated with the next_id() function. The server then uses YAML::DumpFile() to save the book to the disk in YAML format at the appropriate local path.


# Note the success to the end-user
my $resource_url = absolute_url('/=/model/book/id/'.$id);
print $q->header(
    -status   => 201,
    -type     => 'text/html',
    -location => $resource_url,
);
print $q->h1("Created $book->Developing RESTful Web Services in Perl");
print $q->ul(
    $q->li(
        $q->a({ href => $resource_url }, $resource_url)
    )
);

Finally, we end by returning a "201 Created" response to the client. You should try to return either a 201 or 202 response to the client when creating a resource. These are generally superior to 200 or 204 on create. With a 201, you may also set the "Location" header (as I have here) in the response. This must point to the URL of the new item. If you cannot create the item immediately, you may want to consider a "202 Accepted" status. Check the HTTP/1.1 specification for more information.

By the way, don't respond with a 200 status and include a "Location" header under Apache. Apache will translate that 200 into a 302, which is probably helpful for the typical forgetful web programmer, but is not what you want in a RESTful interface. A "Location" header is not appropriate in a 200, so make sure you return a 201 when including a "Location" header for creates.

PUT to Update

In a RESTful interface, PUT is the exact opposite of GET. GET fetches the content from the server. PUT pushes the content to the server. In the sample server snippets below, the variable named $id is already set to the last part of the URL, which looks like "/=/model/book/id/<ID>". Here is how the sample server starts handling this PUT.


# Check to make sure the input book is sane
my $book = check_book( $q->param('PUTDATA') );

# Make sure the book already exists or barf
my $resource_path = get_local_path($id);
unless (-f $resource_path) {
    barf 500, 'Not Gonna Do It',
        'Cannot use PUTs for creating a new resource.';
}

This looks similar to POST. I first start by performing a sanity check to make sure the data is sane. In addition to using check_book() again, I also perform an extra test here to make sure this is an update. This REST server does not permit PUT to create records. Your own REST API could allow creates with PUT, but this should only be permitted when the client is able to and has specified a proper URL for the PUT.


# Make sure the ID is set
$book->{id} = $id;

# Save the resource
eval { YAML::DumpFile($resource_path, $book) };
barf 500, 'I Am Broke', $@ if $@;

This should look similar to the POST, but it's a little similar since the ID is known from the URL (and won't change). This is one place where this interface is a little wonky. You could actually change the ISBN here so that it differs from the ID, which is probably bad. Meh. I'm just a lazy author, so I leave fixing this as an exercise to the diligent reader.


# Note the success to the end-user
print $q->header('text/html');
print $q->h1("Updated $book->Developing RESTful Web Services in Perl");

Finally, I return the response back to the client to let them know it succeeded.

DELETE to Delete

The last verb we consider in the sample server is the DELETE. We are using the same URL that we already used in GET and PUT: "/=/model/book/id/<ID>". In the code snippet here, $id is already initialized to <ID>.


# Make sure the book actually exists
my $resource_path = get_local_path($id);
unless (-f $resource_path) {
    barf 404, 'Where is What?',
        'Nothing here to delete.';
}

# Baleted!
unlink $resource_path;

# Tell me about it.
print $q->header('text/html');
print $q->h1("Deleted $id");

This is pretty simple, but let's walk through it. First, we find where this ID should be found on the local disk. We check first to see if there is even a file there to delete. If not, return a 404 error. If it does exist, delete it. Finally, return a nice 200 response and content letting them know about the update.

That concludes the discussion of the features of the sample REST server. Next, we can discuss some significant extension possibilities.

Stay Tuned for the RESTful Client

We've now explained most of the key components of the sample REST server. Hopefully I have given you some principles and practical tools in Perl to explain the most important concepts when developing your own RESTful web service.

In the next article, I will show how to access all these features with a custom made client tool written in libwww-perl. We'll also consider some extensions that can be made to client and server to improve the abilities of the service and I will leave you with some links that have been helpful in my own work with REST web services.

Until then, cheers.

Resources

Andrew Sterling Hanenkamp is a proud Kansan and spends most of his time hacking Perl, his web site, avoiding yard work, and with his wife and son.


Return to ONLamp.



Sponsored by: