src/whitecap_handler.erl

-module(whitecap_handler).
-include("whitecap.hrl").

-compile(inline).
-compile({inline_size, 512}).

-export([handle/2, response/2, response/3]).

-type status()  :: non_neg_integer() | {non_neg_integer(), iodata()}.
-type header()  :: {iodata(), iodata()}.
-type headers() :: [header()].

-export_type([status/0, header/0, headers/0]).

%% public
-spec handle(whitecap_req(), map()) -> {ok, {status(), headers(), iodata()}}.

handle(Req, #{handler := Handler} = Opts) ->
    try
        HandlerOpts = maps:get(handler_opts, Opts, #{}),
        Handler:handle(Req, HandlerOpts)
    catch
        E:R:ST ->
            logger:error("whitecap handler crashed: ~p:~p~n~p", [E, R, ST]),
            {ok, {500, [], <<>>}}
    end.

-spec response(status(), headers()) -> iodata().

response(Status, Headers) ->
  response(Status, Headers, <<>>).

-spec response(status(), headers(), iodata()) -> iodata().

response(204, Headers, Body) ->
    [<<"HTTP/1.1 204 No Content\r\n">>, format_headers(Headers), <<"\r\n">>, Body];

response({204, _} = Status, Headers, Body) ->
    [format_status(Status), format_headers(Headers), <<"\r\n">>, Body];

response(Status, Headers, Body) ->
    ContentLength = integer_to_binary(iolist_size(Body)),
    Headers2 = [{<<"Content-Length">>, ContentLength} | Headers],
    [format_status(Status), format_headers(Headers2), <<"\r\n">>, Body].

%% private
format_headers(Headers) ->
    [format_header(Header) || Header <- Headers].

format_header({Key, Value}) ->
    [Key, <<": ">>, Value, <<"\r\n">>].

format_status(200) -> <<"HTTP/1.1 200 OK\r\n">>;
format_status(204) -> <<"HTTP/1.1 204 No Content\r\n">>;
format_status(301) -> <<"HTTP/1.1 301 Moved Permanently\r\n">>;
format_status(302) -> <<"HTTP/1.1 302 Found\r\n">>;
format_status(400) -> <<"HTTP/1.1 400 Bad Request\r\n">>;
format_status(403) -> <<"HTTP/1.1 403 Forbidden\r\n">>;
format_status(404) -> <<"HTTP/1.1 404 Not Found\r\n">>;
format_status(408) -> <<"HTTP/1.1 408 Request Timeout\r\n">>;
format_status(500) -> <<"HTTP/1.1 500 Internal Server Error\r\n">>;
format_status(501) -> <<"HTTP/1.1 501 Not Implemented\r\n">>;
format_status(502) -> <<"HTTP/1.1 502 Bad Gateway\r\n">>;
format_status({Code, Reason}) ->
    [<<"HTTP/1.1 ">>, integer_to_binary(Code), <<" ">>, Reason, <<"\r\n">>].