Simple Webmachine – Proper HTTP Resources

Update: This post sparked my Webmachine nerve in such a way that I wrote four more posts about extending the resource described below. Read them if you’d like to see how this resource can evolve.

This morning I read a post about CouchDB’s HTTP Handlers. Jón Grétar Borgþórsson demonstrates how one might implement a handler that serves a JSON structure describing the OS environment variables.

I thought that two additional pieces of code might be interesting. I’ll lead with an example showing the most likely way this resource would have been coded for Webmachine:

%% dispatch:
%% {["_env"],      env_resource, []}.
%% {["_env", env], env_resource, []}.

-export([init/1, content_types_provided/2, resource_exists/2, to_json/2]).

init(_) -> {ok, undefined}.

content_types_provided(RD, Ctx) ->
    {[{"application/json", to_json}], RD, Ctx}.

resource_exists(RD, Ctx) ->
    case wrq:path_info(env, RD) of
        undefined ->
            Result = [ list_to_tuple(string:tokens(E, "="))
                       || E <- os:getenv() ],
            {true, RD, {struct, Result}};
        Env ->
            case os:getenv(Env) of
                false  -> {false, RD, Ctx};
                Result -> {true, RD, Result}

to_json(RD, Result) ->
    {mochijson:encode(Result), RD, Result}.

The biggest difference to note between the Webmachine version and the CouchDB version is that the proper HTTP status codes are returned from the Webmachine resource. The CouchDB handler returns 500 or 405 when the requested resource is not found. The proper status for this case is 404. Webmachine knows this, and handles the choice automatically.

A little extra emphasis, I think, is appropriate here: Webmachine chooses the proper response code for you. You define methods that describe the state of your resource (like whether or not it exists, what methods it allows, etc.), and Webmachine negotiates the muck of HTTP.

As a bonus, the Webmachine resource is the same length, while at the same time being less dense and more readable.

Let’s not get hasty, though. If there is a really good reason for returning an alternate status code, Webmachine won’t get in your way. To prove it, here’s a Webmachine resource that (as near as I can tell, I’m not a CouchDB guru) returns exactly the same statuses as the CouchDB handler:

%% dispatch:
%% {["_env2", '*'], env_resource, []}.

-export([init/1, content_types_provided/2, resource_exists/2, to_json/2]).

init(_) -> {ok, undefined}.

content_types_provided(RD, Ctx) ->
    {[{"application/json", to_json}], RD, Ctx}.

resource_exists(RD, Ctx) ->
    case string:tokens(wrq:disp_path(RD), "/") of
        [] ->
            Result = [ list_to_tuple(string:tokens(E, "="))
                       || E <- os:getenv() ],
            {true, RD, {struct, Result}};
        [Env] ->
            case os:getenv(Env) of
                false ->
                    {{halt, 500},
                       "Content-type", "application/json",
                           {struct, [{error, "not_found"},
                                     {reason, "Variable Not Found"}]}),
                Result ->
                    {true, RD, Result}
        _ ->
            {{halt, 405},
             wrq:set_resp_header("Allow", "GET,HEAD", RD),

to_json(RD, Result) ->
    {mochijson:encode(Result), RD, Result}.

6 comments so far

  1. Jon Gretar Borgthorsson on

    Very nice. I could simplify my CouchDB demos by using some code from you as well. Get rid of my fun for example.

    Where CouchDB has a bit of an edge is that you may already have a CouchDB infrastructure. May make sense to add some resorces to it rather than having a seperate Webmachine server. Especially as it’s as simple as it is.

    Webmachine may have some edge when we are dealing with open web services or something. I really don’t know webmachine that well to make any statements about it.

  2. Bryan on

    @Jon: Completely agreed with the “hack what you have” sentiment. If you’re just writing a one-off that doesn’t need to strictly comply with any standard, and won’t need thorough testing, then it doesn’t make sense to invest time in a new tool (other than for mind-broadening, of course).

    However, I think you’ve short-changed CouchDB. *Any* tool you have an infrastructure built around has the same edge over any new tool you haven’t picked up yet.

    Where CouchDB has an edge is that it’s an awesome storage system with an HTTP interface. If what you’re doing is document retrieval and storage, there’s little that’s nicer than CouchDB.

    Where Webmachine’s edge lies is in its ability to simplify the dirty complexity of HTTP. RFC 2616 is a long, involved document, and it can be a fair bit of work to ensure that your code runs to the proper response code with the correct set of headers. Webmachine abstracts the nitty gritty of setting headers, adding content, and choosing response codes so you can focus on defining the properties of your resource that you want exposed properly over HTTP (such as existence, encoding, and modification time, but also many others). Webmachine does the translation from what you mean (e.g., “This page exists, and hasn’t been modified since you last requested it.”) to what HTTP has to say (“304 Not Modified, Date: XXX, Etag: YYY, …”).

    Lucky for us, the CouchDB and Webmachine can coexist easily. Run them side-by-side, and put squid/pound/etc. in front to direct requests to the proper place, or just write a simple proxy resource in one to point to the other.

  3. […] was thinking about how the os-environment resource from my last post could be extended. This post begins a four-part series in which new capabilities are added to […]

  4. benoitc on

    Do you have any example of proxy resource ? Could be usefull indeed to use webmachine & couchdb together. Was thinking about tight now to just using js to uplad data to couchdb via webmachine…

  5. Bryan on

    @benoitc: I don’t have a proxy resource written and tested for communicating with CouchDB specifically. I’ve written a few for other services, though.

    Depending on what you want the behavior to be, it’s usually not much more than implementing one of your resource’s callbacks (usually an earlier one like service_available or resource_exists) to make an HTTP request elsewhere (ibrowse or inets makes that easy), then stuffing the response back into your ReqData and returning the response code.

    If I get a chance this weekend, I’ll sanitize one from my pile and post it here.

    Update: The example has been posted – read about it here.

  6. benoitc on

    Thanks a lot for the example. I like working with webmachine currently. So refreshing

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: