Simple Webmachine Extension (4/4): DELETE

This is the final entry in a four-part series that demonstrates how to take the simple os-environment Webmachine resource, and expand it to support modification, authorization, and conditional requests. Today I add support for DELETE.

This is the third post in a four-part series about extending a simple Webmachine resource. The first part discussed adding support for the HTTP method PUT, and the second part added basic authorization, while the third part added conditional requests through ETag.


I can modify variables through PUT; how about removing them altogether with DELETE?

First a little sidetrack. While Erlang exposes os:getenv/1 and os:putenv/2, it does not expose an os:deleteenv/1. So, what I’m going to do instead is adopt the convention that a variable set to “nothing” (i.e. the empty list) has been deleted. This requires a little modification to resource_exists/2 to filter out variables with empty-list values. (See the full source at the end of this post.)

Sidetrack #2: I also noticed that there was a bug preventing values from containing the equals sign, so while hacking resource_exists/2, I fixed that as well.

With the empty-list-is-deleted convention in hand, I can implement DELETE handling like this:


delete_resource(RD, Ctx) ->
    os:putenv(wrq:path_info(env, RD), []),
    {true, RD, Ctx}.

I only want to allow one variable to be deleted at a time, so I’ll modify allowed_methods/2 such that it only adds 'DELETE' to the method list if the request path is of the form /_env/VARIABLE.

I also want DELETE to require authorization, so I’ll modify is_authorized/2 to watch for both PUT and DELETE.

And that’s that. I can now use the following curl command to delete MY_VAR:

$ curl -u webmachine:rules -X DELETE http://localhost:8000/_env/MY_VAR

Wrap Up

Before I get to the complete source, I want to take a moment to highlight something I stressed in an earlier blog post: Did you notice that none of the code I’ve displayed in the last few days has mentioned specific HTTP response codes?

The env_resource now runs all over the HTTP decision flowchart, returning everything from 405 when methods other than GET, HEAD, PUT, and DELETE are issued (or just the first three in the whole-environment case), to 401 when PUT or DELETE is attempted without the proper credentials, and 412 when ETags don’t match on a PUT (or 304 when they do match on a GET!), and yet, I didn’t mention a single code. I simply described the properties I wanted the resource to have, and let Webmachine do the translation.

I stress this because I think it’s important to consider the power of an HTTP translation system. I’ve seen reduced development time as an effect of not having to worry about getting to the right response code in every corner case, while still adding necessary headers. I’ve also seen reduced troubleshooting time as an effect of being able to read through a resource that only concerns itself with describing its properties, rather than including a lot of mechanics for tearing apart HTTP requests and building up HTTP responses.


If this series has raised your interest level in Webmachine, I recommend you attend Justin Sheehy’s talk at the Bay Area Erlang Factory.

The Complete Code

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

-export([init/1, content_types_provided/2, resource_exists/2, to_json/2]).
-export([allowed_methods/2, content_types_accepted/2, from_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 = [ {K, string:join(V, "=")}
                       || [K|V] <- [ string:tokens(E, "=")
                                     || E <- os:getenv() ],
                          V /= [] ],
            {true, RD, {struct, Result}};
        Env ->
            case os:getenv(Env) of
                false  -> {false, RD, Ctx};
                []     -> {false, RD, Ctx};
                Result -> {true, RD, Result}

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

%% PUT support

allowed_methods(RD, Ctx) ->
    {['GET', 'HEAD', 'PUT'
      |case wrq:path_info(env, RD) of
          undefined -> [];
          _         -> ['DELETE']
     RD, Ctx}.

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

from_json(RD, Ctx) ->
    case wrq:path_info(env, RD) of
        undefined ->
            {struct, MJ} = mochijson:decode(wrq:req_body(RD)),
            [ os:putenv(K, V) || {K, V} <- MJ ];
        Env ->
            MJ = mochijson:decode(wrq:req_body(RD)),
            os:putenv(Env, MJ)
    {true, RD, Ctx}.

%% AUTH support

-define(AUTH_HEAD, "Basic realm=MyOSEnv").

is_authorized(RD, Ctx) ->
    case wrq:method(RD) of
        PD when PD == 'PUT'; PD == 'DELETE' -> basic_auth(RD, Ctx);
        _                                   -> {true, RD, Ctx}

basic_auth(RD, Ctx) ->
    case wrq:get_req_header("Authorization", RD) of
        "Basic "++Base64 ->
            case string:tokens(base64:mime_decode_to_string(Base64), ":") of
                ["webmachine", "rules"] -> {true, RD, Ctx};
                _                       -> {?AUTH_HEAD, RD, Ctx}
        _ -> {?AUTH_HEAD, RD, Ctx}

%% ETAG support

generate_etag(RD, Result) ->
    {mochihex:to_hex(erlang:phash2(Result)), RD, Result}.

%% DELETE support

delete_resource(RD, Ctx) ->
    os:putenv(wrq:path_info(env, RD), []),
    {true, RD, Ctx}.

Author: Bryan

I'm the creator of Symbology (, BeerRiot (, lots of homebrew, some furniture, and other things. There's more about me at

3 thoughts on “Simple Webmachine Extension (4/4): DELETE”

  1. Thanks for the great posts.
    What about POST, how would you handle that?


    All the best,

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