Skip to content

Commit 7235281

Browse files
committed
transparent (content-encoding) compression
1 parent 2f55c0d commit 7235281

File tree

4 files changed

+65
-17
lines changed

4 files changed

+65
-17
lines changed

include/hackney_lib.hrl

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
partial_headers = [] :: list(),
2727
clen = undefined :: integer() | undefined | bad_int,
2828
te = <<>> :: binary(),
29+
ce = <<>> :: binary(),
2930
connection = <<>> :: binary(),
3031
ctype = <<>> :: binary(),
3132
location = <<>> :: binary(),
32-
body_state = waiting :: atom() | tuple()
33+
body_state = waiting :: atom() | tuple(),
34+
encoding :: {atom(), any()} | undefined
3335
}).

src/hackney.erl

+6
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ cancel_request(Ref) ->
9292

9393
%% @doc set client options.
9494
%% Options are:
95+
%% - `compress': request compression and transparently decompress
9596
%% - `async': to fetch the response asynchronously
9697
%% - `{async, once}': to receive the response asynchronously once time.
9798
%% To receive the next message use the function `hackney:stream_next/1'.
@@ -201,6 +202,8 @@ request(Method, URL, Headers, Body) ->
201202
%% directly. The response is `{ok, Status, Headers, Body}'</li>
202203
%% <li>`max_body': sets maximum allowed size of the body if
203204
%% with_body is true</li>
205+
%% <li>`compress': request that the server sends the body compressed
206+
%% and instructs hackney to transparently decompress it</li>
204207
%% <li>`async': receive the response asynchronously
205208
%% The function return {ok, StreamRef}.
206209
%% When {async, once} is used the response will be received only once. To
@@ -971,6 +974,9 @@ maybe_update_req(State) ->
971974

972975
parse_options([], State) ->
973976
State;
977+
parse_options([compress | Rest], State = #client{headers=Headers}) ->
978+
Headers2 = hackney_headers:store(<<"accept-encoding">>, <<"gzip, deflate">>, Headers),
979+
parse_options(Rest, State#client{headers=Headers2});
974980
parse_options([async | Rest], State) ->
975981
parse_options(Rest, State#client{async=true});
976982
parse_options([{async, Async} | Rest], State) ->

src/hackney_http.erl

+42-7
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ parse_header(Line, St) ->
300300
<<"transfer-encoding">> ->
301301
TE = hackney_bstr:to_lower(hackney_bstr:trim(Value)),
302302
St#hparser{te=TE};
303+
<<"content-encoding">> ->
304+
CE = hackney_bstr:to_lower(hackney_bstr:trim(Value)),
305+
St#hparser{ce=CE};
303306
<<"connection">> ->
304307
Connection = hackney_bstr:to_lower(hackney_bstr:trim(Value)),
305308
St#hparser{connection=Connection};
@@ -325,9 +328,40 @@ parse_trailers(St, Acc) ->
325328
_ -> error
326329
end.
327330

328-
parse_body(#hparser{body_state=waiting, method= <<"HEAD">>, buffer=Buffer}) ->
329-
{done, Buffer};
330-
parse_body(St=#hparser{body_state=waiting, te=TE, clen=Length, buffer=Buffer}) ->
331+
-define(MAX_WBITS, 15). % zconf.h
332+
parse_body(#hparser{body_state=waiting, method=Method, buffer=Buffer, clen=Length}) when Method == <<"HEAD">>; Length =:= 0 ->
333+
{done, Buffer};
334+
parse_body(St=#hparser{body_state=waiting, ce=CE}) ->
335+
St2 = case CE of
336+
<<"gzip">> ->
337+
Z = zlib:open(),
338+
ok = zlib:inflateInit(Z, ?MAX_WBITS + 16), % http://www.zlib.net/manual.html#Advanced
339+
St#hparser{encoding={zlib,Z}};
340+
<<"deflate">> ->
341+
Z = zlib:open(),
342+
ok = zlib:inflateInit(Z, ?MAX_WBITS),
343+
St#hparser{encoding={zlib,Z}};
344+
_ ->
345+
St
346+
end,
347+
parse_body2(St2);
348+
parse_body(St=#hparser{encoding={zlib,Z}}) ->
349+
case parse_body2(St) of
350+
{ok, Chunk, St2} ->
351+
Chunk2 = iolist_to_binary(zlib:inflate(Z, Chunk)),
352+
{ok, Chunk2, St2};
353+
{done, Rest} ->
354+
Rest2 = iolist_to_binary(zlib:inflate(Z, Rest)),
355+
ok = zlib:inflateEnd(Z),
356+
ok = zlib:close(Z),
357+
{done, Rest2};
358+
Else ->
359+
Else
360+
end;
361+
parse_body(St) ->
362+
parse_body2(St).
363+
364+
parse_body2(St=#hparser{body_state=waiting, te=TE, clen=Length, buffer=Buffer}) ->
331365
case {TE, Length} of
332366
{<<"chunked">>, _} ->
333367
parse_body(St#hparser{body_state=
@@ -341,14 +375,13 @@ parse_body(St=#hparser{body_state=waiting, te=TE, clen=Length, buffer=Buffer}) -
341375
St#hparser{body_state={stream, fun te_identity/2, {0, Length}, fun ce_identity/1}}
342376
)
343377
end;
344-
parse_body(#hparser{body_state=done, buffer=Buffer}) ->
378+
parse_body2(#hparser{body_state=done, buffer=Buffer}) ->
345379
{done, Buffer};
346-
parse_body(St=#hparser{buffer=Buffer, body_state={stream, _, _, _}}) when byte_size(Buffer) > 0 ->
380+
parse_body2(St=#hparser{buffer=Buffer, body_state={stream, _, _, _}}) when byte_size(Buffer) > 0 ->
347381
transfer_decode(Buffer, St#hparser{buffer= <<>>});
348-
parse_body(St) ->
382+
parse_body2(St) ->
349383
{more, St, <<>>}.
350384

351-
352385
-spec transfer_decode(binary(), #hparser{})
353386
-> {ok, binary(), #hparser{}} | {error, atom()}.
354387
transfer_decode(Data, St=#hparser{
@@ -513,6 +546,8 @@ get_property(method, #hparser{method=Method}) ->
513546
Method;
514547
get_property(transfer_encoding, #hparser{te=TE}) ->
515548
TE;
549+
get_property(content_encoding, #hparser{ce=CE}) ->
550+
CE;
516551
get_property(content_length, #hparser{clen=CLen}) ->
517552
CLen;
518553
get_property(connection, #hparser{connection=Connection}) ->

src/hackney_request.erl

+14-9
Original file line numberDiff line numberDiff line change
@@ -57,28 +57,33 @@ perform(Client0, {Method0, Path, Headers0, Body0}) ->
5757
%% add host eventually
5858
Headers2 = maybe_add_host(Headers1, Client0#client.netloc),
5959

60+
Compress = proplists:get_value(compress, Options, false),
61+
Headers3 = if Compress ->
62+
hackney_headers_new:store(<<"accept-encoding">>, <<"gzip, deflate">>, Headers2);
63+
true -> Headers2 end,
64+
6065
%% get expect headers
61-
Expect = expectation(Headers2),
66+
Expect = expectation(Headers3),
6267

6368
%% build headers with the body.
6469
{FinalHeaders, ReqType, Body, Client1} = case Body0 of
6570
stream ->
66-
{Headers2, ReqType0, stream, Client0};
71+
{Headers3, ReqType0, stream, Client0};
6772
stream_multipart ->
68-
handle_multipart_body(Headers2, ReqType0, Client0);
73+
handle_multipart_body(Headers3, ReqType0, Client0);
6974
{stream_multipart, Size} ->
70-
handle_multipart_body(Headers2, ReqType0, Size, Client0);
75+
handle_multipart_body(Headers3, ReqType0, Size, Client0);
7176
{stream_multipart, Size, Boundary} ->
72-
handle_multipart_body(Headers2, ReqType0,
77+
handle_multipart_body(Headers3, ReqType0,
7378
Size, Boundary, Client0);
7479
<<>> when Method =:= <<"POST">> orelse Method =:= <<"PUT">> ->
75-
handle_body(Headers2, ReqType0, Body0, Client0);
80+
handle_body(Headers3, ReqType0, Body0, Client0);
7681
<<>> ->
77-
{Headers2, ReqType0, Body0, Client0};
82+
{Headers3, ReqType0, Body0, Client0};
7883
[] ->
79-
{Headers2, ReqType0, Body0, Client0};
84+
{Headers3, ReqType0, Body0, Client0};
8085
_ ->
81-
handle_body(Headers2, ReqType0, Body0, Client0)
86+
handle_body(Headers3, ReqType0, Body0, Client0)
8287
end,
8388

8489
%% build final client record

0 commit comments

Comments
 (0)