Current section

Files

Jump to
elli src elli_middleware_compress.erl
Raw

src/elli_middleware_compress.erl

%%% @doc Response compression as Elli middleware.
-module(elli_middleware_compress).
-export([postprocess/3]).
-include("elli_util.hrl").
%%
%% Postprocess handler
%%
%%% @doc Postprocess all requests and compress bodies larger than
%%% `compress_byte_size' (`1024' by default).
-spec postprocess(Req, Result, Config) -> Result when
Req :: elli:req(),
Result :: elli_handler:result(),
Config :: [{compress_byte_size, non_neg_integer()} | tuple()].
postprocess(Req, {ResponseCode, Body}, Config)
when is_integer(ResponseCode) orelse ResponseCode =:= ok ->
postprocess(Req, {ResponseCode, [], Body}, Config);
postprocess(Req, {ResponseCode, Headers, Body} = Res, Config)
when is_integer(ResponseCode) orelse ResponseCode =:= ok ->
Threshold = proplists:get_value(compress_byte_size, Config, 1024),
?IF(not should_compress(Body, Threshold), Res,
case compress(Body, Req) of
no_compress ->
Res;
{CompressedBody, Encoding} ->
NewHeaders = [{<<"Content-Encoding">>, Encoding} | Headers],
{ResponseCode, NewHeaders, CompressedBody}
end);
postprocess(_, Res, _) ->
Res.
%%
%% INTERNALS
%%
%% NOTE: Algorithm is either `<<"gzip">>' or `<<"deflate">>'.
-spec compress(Body0 :: elli:body(), Req :: elli:req()) -> Body1 when
Body1 :: {Compressed :: binary(), Algorithm :: binary()} | no_compress.
compress(Body, Req) ->
case accepted_encoding(Req) of
<<"gzip">> = E -> {zlib:gzip(Body), E};
<<"deflate">> = E -> {zlib:compress(Body), E};
_ -> no_compress
end.
accepted_encoding(Req) ->
hd(binary:split(elli_request:get_header(<<"Accept-Encoding">>, Req, <<>>),
[<<",">>, <<";">>], [global])).
-spec should_compress(Body, Threshold) -> boolean() when
Body :: binary(),
Threshold :: non_neg_integer().
should_compress(Body, S) ->
is_binary(Body) andalso byte_size(Body) >= S orelse
is_list(Body) andalso iolist_size(Body) >= S.