diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cowboy_protocol.erl | 14 | ||||
-rw-r--r-- | src/cowboy_req.erl | 68 |
2 files changed, 65 insertions, 17 deletions
diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl index 48c0b00..0e9982b 100644 --- a/src/cowboy_protocol.erl +++ b/src/cowboy_protocol.erl @@ -17,6 +17,8 @@ %% %% The available options are: %% <dl> +%% <dt>compress</dt><dd>Whether to automatically compress the response +%% body when the conditions are met. Disabled by default.</dd> %% <dt>env</dt><dd>The environment passed and optionally modified %% by middlewares.</dd> %% <dt>max_empty_lines</dt><dd>Max number of empty lines before a request. @@ -64,6 +66,7 @@ socket :: inet:socket(), transport :: module(), middlewares :: [module()], + compress :: boolean(), env :: cowboy_middleware:env(), onrequest :: undefined | onrequest_fun(), onresponse = undefined :: undefined | onresponse_fun(), @@ -99,6 +102,7 @@ get_value(Key, Opts, Default) -> %% @private -spec init(pid(), inet:socket(), module(), any()) -> ok. init(ListenerPid, Socket, Transport, Opts) -> + Compress = get_value(compress, Opts, false), MaxEmptyLines = get_value(max_empty_lines, Opts, 5), MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64), MaxHeaderValueLength = get_value(max_header_value_length, Opts, 4096), @@ -112,7 +116,7 @@ init(ListenerPid, Socket, Transport, Opts) -> Timeout = get_value(timeout, Opts, 5000), ok = ranch:accept_ack(ListenerPid), wait_request(<<>>, #state{socket=Socket, transport=Transport, - middlewares=Middlewares, env=Env, + middlewares=Middlewares, compress=Compress, env=Env, max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive, max_request_line_length=MaxRequestLineLength, max_header_name_length=MaxHeaderNameLength, @@ -457,11 +461,11 @@ parse_host(<< C, Rest/bits >>, Acc) -> request(Buffer, State=#state{socket=Socket, transport=Transport, req_keepalive=ReqKeepalive, max_keepalive=MaxKeepalive, - onresponse=OnResponse}, + compress=Compress, onresponse=OnResponse}, Method, Path, Query, Fragment, Version, Headers, Host, Port) -> Req = cowboy_req:new(Socket, Transport, Method, Path, Query, Fragment, Version, Headers, Host, Port, Buffer, ReqKeepalive < MaxKeepalive, - OnResponse), + Compress, OnResponse), onrequest(Req, State). %% Call the global onrequest callback. The callback can send a reply, @@ -546,13 +550,13 @@ error_terminate(Code, Req, State) -> %% Only send an error reply if there is no resp_sent message. -spec error_terminate(cowboy_http:status(), #state{}) -> ok. error_terminate(Code, State=#state{socket=Socket, transport=Transport, - onresponse=OnResponse}) -> + compress=Compress, onresponse=OnResponse}) -> receive {cowboy_req, resp_sent} -> ok after 0 -> _ = cowboy_req:reply(Code, cowboy_req:new(Socket, Transport, <<"GET">>, <<>>, <<>>, <<>>, {1, 1}, [], <<>>, undefined, - <<>>, false, OnResponse)), + <<>>, false, Compress, OnResponse)), ok end, terminate(State). diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index dab9410..973cc65 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -42,7 +42,7 @@ -module(cowboy_req). %% Request API. --export([new/13]). +-export([new/14]). -export([method/1]). -export([version/1]). -export([peer/1]). @@ -156,6 +156,7 @@ buffer = <<>> :: binary(), %% Response. + resp_compress = false :: boolean(), resp_state = waiting :: locked | waiting | chunks | done, resp_headers = [] :: cowboy_http:headers(), resp_body = <<>> :: iodata() | resp_body_fun() @@ -179,16 +180,16 @@ %% in an optimized way and add the parsed value to p_headers' cache. -spec new(inet:socket(), module(), binary(), binary(), binary(), binary(), cowboy_http:version(), cowboy_http:headers(), binary(), - inet:port_number() | undefined, binary(), boolean(), + inet:port_number() | undefined, binary(), boolean(), boolean(), undefined | cowboy_protocol:onresponse_fun()) -> req(). new(Socket, Transport, Method, Path, Query, Fragment, Version, Headers, Host, Port, Buffer, CanKeepalive, - OnResponse) -> + Compress, OnResponse) -> Req = #http_req{socket=Socket, transport=Transport, pid=self(), method=Method, path=Path, qs=Query, fragment=Fragment, version=Version, headers=Headers, host=Host, port=Port, buffer=Buffer, - onresponse=OnResponse}, + resp_compress=Compress, onresponse=OnResponse}, case CanKeepalive and (Version =:= {1, 1}) of false -> Req#http_req{connection=close}; @@ -892,7 +893,8 @@ reply(Status, Headers, Req=#http_req{resp_body=Body}) -> reply(Status, Headers, Body, Req=#http_req{ socket=Socket, transport=Transport, version=Version, connection=Connection, - method=Method, resp_state=waiting, resp_headers=RespHeaders}) -> + method=Method, resp_compress=Compress, + resp_state=waiting, resp_headers=RespHeaders}) -> RespConn = response_connection(Headers, Connection), HTTP11Headers = case Version of {1, 1} -> [{<<"connection">>, atom_to_connection(Connection)}]; @@ -922,18 +924,60 @@ reply(Status, Headers, Body, Req=#http_req{ BodyFun(Socket, Transport); true -> ok end; + _ when Compress -> + Req2 = reply_may_compress(Status, Headers, Body, Req, + RespHeaders, HTTP11Headers, Method); _ -> - {_, Req2} = response(Status, Headers, RespHeaders, [ - {<<"content-length">>, integer_to_list(iolist_size(Body))}, - {<<"date">>, cowboy_clock:rfc1123()}, - {<<"server">>, <<"Cowboy">>} - |HTTP11Headers], - case Method of <<"HEAD">> -> <<>>; _ -> Body end, - Req) + Req2 = reply_no_compress(Status, Headers, Body, Req, + RespHeaders, HTTP11Headers, Method, iolist_size(Body)) end, {ok, Req2#http_req{connection=RespConn, resp_state=done, resp_headers=[], resp_body= <<>>}}. +reply_may_compress(Status, Headers, Body, Req, + RespHeaders, HTTP11Headers, Method) -> + BodySize = iolist_size(Body), + {ok, Encodings, Req2} + = cowboy_req:parse_header(<<"accept-encoding">>, Req), + CanGzip = (BodySize > 300) + andalso (false =:= lists:keyfind(<<"content-encoding">>, + 1, Headers)) + andalso (false =:= lists:keyfind(<<"content-encoding">>, + 1, RespHeaders)) + andalso (false =:= lists:keyfind(<<"transfer-encoding">>, + 1, Headers)) + andalso (false =:= lists:keyfind(<<"transfer-encoding">>, + 1, RespHeaders)) + andalso (Encodings =/= undefined) + andalso (false =/= lists:keyfind(<<"gzip">>, 1, Encodings)), + case CanGzip of + true -> + GzBody = zlib:gzip(Body), + {_, Req3} = response(Status, Headers, RespHeaders, [ + {<<"content-length">>, integer_to_list(byte_size(GzBody))}, + {<<"content-encoding">>, <<"gzip">>}, + {<<"date">>, cowboy_clock:rfc1123()}, + {<<"server">>, <<"Cowboy">>} + |HTTP11Headers], + case Method of <<"HEAD">> -> <<>>; _ -> GzBody end, + Req2), + Req3; + false -> + reply_no_compress(Status, Headers, Body, Req, + RespHeaders, HTTP11Headers, Method, BodySize) + end. + +reply_no_compress(Status, Headers, Body, Req, + RespHeaders, HTTP11Headers, Method, BodySize) -> + {_, Req2} = response(Status, Headers, RespHeaders, [ + {<<"content-length">>, integer_to_list(BodySize)}, + {<<"date">>, cowboy_clock:rfc1123()}, + {<<"server">>, <<"Cowboy">>} + |HTTP11Headers], + case Method of <<"HEAD">> -> <<>>; _ -> Body end, + Req), + Req2. + %% @equiv chunked_reply(Status, [], Req) -spec chunked_reply(cowboy_http:status(), Req) -> {ok, Req} when Req::req(). chunked_reply(Status, Req) -> |