Previous | Up | Next

Folding on cowboy_req

Streamlined use of a streamlined API

The problem

Pre-1.x versions of cowboy have an elegant, minimalist, and uniform API for grabbing data out of http_req records. This API includes functions like: cowboy_req:header/3, cowboy_req:qs_vals/1, etc.

Their elegance and uniformity come from the fact that they all return the tuple {A, NewReq}, such that A is the result of the function call, and NewReq is the potentially-transformed, new request record that you should use thereafter.

Suppose your business requirements dictate that you have to gather a lot of data from a request, and then do_something() with it. What ends up happening is that your code begins to look like the following:

{OriginalIp, Req1} = cowboy_req:header(<<"x-forwarded-for">>, Req, <<>>),
{UserAgent, Req2} = cowboy_req:header(<<"user-agent">>, Req1, <<>>),
{QsVals, Req3} = cowboy_req:qs_vals(Req2),
{Path, Req4} = cowboy_req:path(Req4),
Reply = do_something(OriginalIp, UserAgent, QsVals, Path),
{ok, Reply, Req4}.

This gets tedious fast, and causes problems if you want to add or remove some of the intermediate steps. For example: removing the assignment to Req2 would force you to rename all following variables, or leave a ‘hole’ and rename Req1 to Req2 in the previous line. Inelegant options, both.

The solution

As the wonderful tutorial on the universality and expresiveness of fold will tell you, fold is the best function. We can clean up the above code and remove our useless assignments to numbered variables by using the following pattern.

{[OriginalIp, UserAgent, QsVals, Path], NewReq} =
  fold_req([fun(R) -> cowboy_req:header(<<"x-forwarded-for">>, R, <<>>) end,
            fun(R) -> cowboy_req:header(<<"user-agent">>, R, <<>>) end,
            fun(R) -> cowboy_req:qs_vals(R) end,
            fun(R) -> cowboy_req:path(R) end]
            Req),
Reply = do_something(OriginalIp, UserAgent, QsVals, Path),
{ok, Reply, NewReq}.

Yes, that’s 2 extra lines of overhead, but the gain is that we don’t have to keep track of the various Reqs that abound in the code above.

All of the functions in the chain are completely independent, and we can add or remove them as we see fit, without having to rearrange all the code.

The implemenation

The implementation of fold_req/2 is trivial, so if you’re up for the task, try to write it yourself, now.

Scroll below to see my take on the solution.

----------------------------------------------------------














--------------------- SPOILERS BELOW ---------------------














----------------------------------------------------------

The actual implementation

fold_req(Funs, Req) -> 
    lists:foldr(fun fold_step/2, {[], Req}, Funs).

fold_step(Fun, {Acc, Req}) ->
    {Val, NewReq} = Fun(Req),
    {[Val|Acc], NewReq}.

Cheers!

Previous | Up | Next