Skip to content

Commit 9cfef75

Browse files
committed
transparent (content-encoding) compression
1 parent f3e9292 commit 9cfef75

File tree

4 files changed

+57
-18
lines changed

4 files changed

+57
-18
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
@@ -94,6 +94,7 @@ cancel_request(Ref) ->
9494

9595
%% @doc set client options.
9696
%% Options are:
97+
%% - `compress': request compression and transparently decompress
9798
%% - `async': to fetch the response asynchronously
9899
%% - `{async, once}': to receive the response asynchronously one time.
99100
%% To receive the next message use the function `hackney:stream_next/1'.
@@ -211,6 +212,8 @@ request(Method, URL, Headers, Body) ->
211212
%% directly. The response is `{ok, Status, Headers, Body}'</li>
212213
%% <li>`max_body': sets maximum allowed size of the body if
213214
%% with_body is true</li>
215+
%% <li>`compress': request that the server sends the body compressed
216+
%% and instructs hackney to transparently decompress it</li>
214217
%% <li>`async': receive the response asynchronously
215218
%% The function return {ok, StreamRef}.
216219
%% When {async, once} is used the response will be received only once. To
@@ -987,6 +990,9 @@ maybe_update_req(State) ->
987990

988991
parse_options([], State) ->
989992
State;
993+
parse_options([compress | Rest], State = #client{headers=Headers}) ->
994+
Headers2 = hackney_headers:store(<<"accept-encoding">>, <<"gzip, deflate">>, Headers),
995+
parse_options(Rest, State#client{headers=Headers2});
990996
parse_options([async | Rest], State) ->
991997
parse_options(Rest, State#client{async=true});
992998
parse_options([{async, Async} | Rest], State) ->

src/hackney_http.erl

+33-7
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,9 @@ parse_header(Line, St) ->
299299
<<"transfer-encoding">> ->
300300
TE = hackney_bstr:to_lower(hackney_bstr:trim(Value)),
301301
St#hparser{te=TE};
302+
<<"content-encoding">> ->
303+
CE = hackney_bstr:to_lower(hackney_bstr:trim(Value)),
304+
St#hparser{ce=CE};
302305
<<"connection">> ->
303306
Connection = hackney_bstr:to_lower(hackney_bstr:trim(Value)),
304307
St#hparser{connection=Connection};
@@ -331,9 +334,31 @@ parse_trailers(St, Acc) ->
331334
{more, St2} -> {more, St2}
332335
end.
333336

334-
parse_body(#hparser{body_state=waiting, method= <<"HEAD">>, buffer=Buffer}) ->
335-
{done, Buffer};
336-
parse_body(St=#hparser{body_state=waiting, te=TE, clen=Length, buffer=Buffer}) ->
337+
parse_body(#hparser{body_state=waiting, method=Method, buffer=Buffer, clen=Length}) when Method == <<"HEAD">>; Length =:= 0 ->
338+
{done, Buffer};
339+
parse_body(St=#hparser{body_state=waiting, ce=CE}) when CE == <<"gzip">>; CE == <<"deflate">> ->
340+
MaxWBITS = 15, % zconf.h
341+
WB = MaxWBITS + if CE == <<"gzip">> -> 16; true -> 0 end, % http://www.zlib.net/manual.html#Advanced
342+
Z = zlib:open(),
343+
ok = zlib:inflateInit(Z, WB),
344+
parse_body2(St#hparser{encoding={zlib,Z}});
345+
parse_body(St=#hparser{encoding={zlib,Z}}) ->
346+
case parse_body2(St) of
347+
{ok, Chunk, St2} ->
348+
Chunk2 = iolist_to_binary(zlib:inflate(Z, Chunk)),
349+
{ok, Chunk2, St2};
350+
{done, Rest} ->
351+
Rest2 = iolist_to_binary(zlib:inflate(Z, Rest)),
352+
ok = zlib:inflateEnd(Z),
353+
ok = zlib:close(Z),
354+
{done, Rest2};
355+
Else ->
356+
Else
357+
end;
358+
parse_body(St) ->
359+
parse_body2(St).
360+
361+
parse_body2(St=#hparser{body_state=waiting, te=TE, clen=Length, buffer=Buffer}) ->
337362
case {TE, Length} of
338363
{<<"chunked">>, _} ->
339364
parse_body(St#hparser{body_state=
@@ -347,14 +372,13 @@ parse_body(St=#hparser{body_state=waiting, te=TE, clen=Length, buffer=Buffer}) -
347372
St#hparser{body_state={stream, fun te_identity/2, {0, Length}, fun ce_identity/1}}
348373
)
349374
end;
350-
parse_body(#hparser{body_state=done, buffer=Buffer}) ->
375+
parse_body2(#hparser{body_state=done, buffer=Buffer}) ->
351376
{done, Buffer};
352-
parse_body(St=#hparser{buffer=Buffer, body_state={stream, _, _, _}}) when byte_size(Buffer) > 0 ->
377+
parse_body2(St=#hparser{buffer=Buffer, body_state={stream, _, _, _}}) when byte_size(Buffer) > 0 ->
353378
transfer_decode(Buffer, St#hparser{buffer= <<>>});
354-
parse_body(St) ->
379+
parse_body2(St) ->
355380
{more, St, <<>>}.
356381

357-
358382
-spec transfer_decode(binary(), #hparser{})
359383
-> {ok, binary(), #hparser{}} | {done, binary()} | {error, atom()}.
360384
transfer_decode(Data, St=#hparser{
@@ -514,6 +538,8 @@ get_property(method, #hparser{method=Method}) ->
514538
Method;
515539
get_property(transfer_encoding, #hparser{te=TE}) ->
516540
TE;
541+
get_property(content_encoding, #hparser{ce=CE}) ->
542+
CE;
517543
get_property(content_length, #hparser{clen=CLen}) ->
518544
CLen;
519545
get_property(connection, #hparser{connection=Connection}) ->

src/hackney_request.erl

+15-10
Original file line numberDiff line numberDiff line change
@@ -61,30 +61,35 @@ perform(Client0, {Method0, Path0, Headers0, Body0}) ->
6161
%% add host eventually
6262
Headers2 = maybe_add_host(Headers1, Client0#client.netloc),
6363

64+
Compress = proplists:get_value(compress, Options, false),
65+
Headers3 = if Compress ->
66+
hackney_headers_new:store(<<"accept-encoding">>, <<"gzip, deflate">>, Headers2);
67+
true -> Headers2 end,
68+
6469
%% get expect headers
65-
Expect = expectation(Headers2),
70+
Expect = expectation(Headers3),
6671

6772
%% build headers with the body.
6873
{FinalHeaders, ReqType, Body, Client1} = case Body0 of
6974
stream ->
70-
{Headers2, ReqType0, stream, Client0};
75+
{Headers3, ReqType0, stream, Client0};
7176
stream_multipart ->
72-
handle_multipart_body(Headers2, ReqType0, Client0);
77+
handle_multipart_body(Headers3, ReqType0, Client0);
7378
{stream_multipart, Size} ->
74-
handle_multipart_body(Headers2, ReqType0, Size, Client0);
79+
handle_multipart_body(Headers3, ReqType0, Size, Client0);
7580
{stream_multipart, Size, Boundary} ->
76-
handle_multipart_body(Headers2, ReqType0,
81+
handle_multipart_body(Headers3, ReqType0,
7782
Size, Boundary, Client0);
7883
<<>> when Method =:= <<"POST">> orelse Method =:= <<"PUT">> ->
79-
handle_body(Headers2, ReqType0, Body0, Client0);
84+
handle_body(Headers3, ReqType0, Body0, Client0);
8085
[] when Method =:= <<"POST">> orelse Method =:= <<"PUT">> ->
81-
handle_body(Headers2, ReqType0, Body0, Client0);
86+
handle_body(Headers3, ReqType0, Body0, Client0);
8287
<<>> ->
83-
{Headers2, ReqType0, Body0, Client0};
88+
{Headers3, ReqType0, Body0, Client0};
8489
[] ->
85-
{Headers2, ReqType0, Body0, Client0};
90+
{Headers3, ReqType0, Body0, Client0};
8691
_ ->
87-
handle_body(Headers2, ReqType0, Body0, Client0)
92+
handle_body(Headers3, ReqType0, Body0, Client0)
8893
end,
8994

9095
%% build final client record

0 commit comments

Comments
 (0)