Skip to content

Commit 4dbfb49

Browse files
committed
transparent (content-encoding) compression support
1 parent f3e9292 commit 4dbfb49

File tree

4 files changed

+95
-15
lines changed

4 files changed

+95
-15
lines changed

include/hackney.hrl

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
partial_headers = [],
4747
version,
4848
clen = nil,
49+
ce = nil,
4950
te = nil,
5051
connection = nil,
5152
method = nil,

src/hackney.erl

+3
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

src/hackney_request.erl

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

64+
%% overwrite always as we control Accept-Encoding when 'compress' is set
65+
Headers3 = case proplists:get_value(compress, Options, false) of
66+
true -> hackney_headers_new:store(<<"Accept-Encoding">>, <<"gzip, deflate">>, Headers2);
67+
false -> Headers2
68+
end,
69+
6470
%% get expect headers
65-
Expect = expectation(Headers2),
71+
Expect = expectation(Headers3),
6672

6773
%% build headers with the body.
6874
{FinalHeaders, ReqType, Body, Client1} = case Body0 of
6975
stream ->
70-
{Headers2, ReqType0, stream, Client0};
76+
{Headers3, ReqType0, stream, Client0};
7177
stream_multipart ->
72-
handle_multipart_body(Headers2, ReqType0, Client0);
78+
handle_multipart_body(Headers3, ReqType0, Client0);
7379
{stream_multipart, Size} ->
74-
handle_multipart_body(Headers2, ReqType0, Size, Client0);
80+
handle_multipart_body(Headers3, ReqType0, Size, Client0);
7581
{stream_multipart, Size, Boundary} ->
76-
handle_multipart_body(Headers2, ReqType0,
82+
handle_multipart_body(Headers3, ReqType0,
7783
Size, Boundary, Client0);
7884
<<>> when Method =:= <<"POST">> orelse Method =:= <<"PUT">> ->
79-
handle_body(Headers2, ReqType0, Body0, Client0);
85+
handle_body(Headers3, ReqType0, Body0, Client0);
8086
[] when Method =:= <<"POST">> orelse Method =:= <<"PUT">> ->
81-
handle_body(Headers2, ReqType0, Body0, Client0);
87+
handle_body(Headers3, ReqType0, Body0, Client0);
8288
<<>> ->
83-
{Headers2, ReqType0, Body0, Client0};
89+
{Headers3, ReqType0, Body0, Client0};
8490
[] ->
85-
{Headers2, ReqType0, Body0, Client0};
91+
{Headers3, ReqType0, Body0, Client0};
8692
_ ->
87-
handle_body(Headers2, ReqType0, Body0, Client0)
93+
handle_body(Headers3, ReqType0, Body0, Client0)
8894
end,
8995

9096
%% build final client record

src/hackney_response.erl

+75-5
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,25 @@ wait_headers({headers_complete, Parser}, Client, Status, Headers) ->
111111
[hackney, Client#client.host, response_time],
112112
ResponseTime),
113113
HeadersList = hackney_headers_new:to_list(Headers),
114+
CE = case proplists:get_value(compress, Client#client.options, false) of
115+
true ->
116+
case hackney_headers_new:get_value(<<"content-encoding">>, Headers, nil) of
117+
nil -> nil;
118+
C ->
119+
Z = zlib:open(),
120+
%% inflateInit2 (https://www.zlib.net/manual.html#Advanced)
121+
WindowBits = 15 + if C == <<"gzip">> -> 16; true -> 0 end,
122+
ok = zlib:inflateInit(Z, WindowBits),
123+
ok = case erlang:function_exported(zlib, safeInflate, 2) of
124+
%% OTP-20.0.5 and later
125+
true -> ok;
126+
%% OTP-18.0 and later
127+
false -> zlib:setBufSize(Z, 512 * 1024)
128+
end,
129+
{zlib,Z}
130+
end;
131+
false -> nil
132+
end,
114133
TE = hackney_headers_new:get_value(<<"transfer-encoding">>, Headers, nil),
115134
CLen = case hackney_headers_new:lookup("content-length", Headers) of
116135
[] -> undefined;
@@ -122,6 +141,7 @@ wait_headers({headers_complete, Parser}, Client, Status, Headers) ->
122141
end,
123142
Client2 = Client#client{parser=Parser,
124143
headers=Headers,
144+
ce=CE,
125145
te=TE,
126146
clen=CLen},
127147
{ok, Status, HeadersList, Client2}.
@@ -147,17 +167,64 @@ stream_body(Client=#client{parser=Parser, clen=CLen, te=TE}) ->
147167
stream_body(Data, #client{parser=Parser}=Client) ->
148168
stream_body1(hackney_http:execute(Parser, Data), Client).
149169

150-
stream_body1({more, Parser, Buffer}, Client) ->
170+
stream_body1({ok, Data, Parser}, Client = #client{ce={zlib,Z}}) ->
171+
stream_body2(case stream_body_zlib(Z, Data) of
172+
<<>> -> {more, Parser, <<>>};
173+
D when is_binary(D) -> {ok, D, Parser};
174+
E -> {error,E}
175+
end, Client);
176+
stream_body1({done, _Rest}, Client = #client{ce={zlib,_Z}}) ->
177+
stream_body1(done, Client);
178+
stream_body1(done, Client = #client{ce={zlib,Z}, parser=Parser}) ->
179+
stream_body2(case stream_body_zlib(Z, <<>>) of
180+
done -> done;
181+
D when is_binary(D), size(D) > 0 -> {ok, D, Parser};
182+
E -> {error,E}
183+
end, Client);
184+
stream_body1(Result, Client) ->
185+
stream_body2(Result, Client).
186+
187+
stream_body_zlib(Z, Data) ->
188+
case erlang:function_exported(zlib, safeInflate, 2) of
189+
true ->
190+
%% OTP-20.0.5 and later
191+
case zlib:safeInflate(Z, Data) of
192+
{continue, []} when Data == <<>> ->
193+
data_error;
194+
{finished, []} when Data == <<>> ->
195+
case (catch zlib:inflateEnd(Z)) of
196+
ok -> done;
197+
_ -> data_error
198+
end;
199+
{_, Output} ->
200+
iolist_to_binary(Output)
201+
end;
202+
false ->
203+
%% OTP-18.0 and later
204+
case zlib:inflateChunk(Z, Data) of
205+
[] when Data == <<>> ->
206+
case (catch zlib:inflateEnd(Z)) of
207+
ok -> done;
208+
_ -> data_error
209+
end;
210+
{more, Decompressed} ->
211+
iolist_to_binary(Decompressed);
212+
Decompressed ->
213+
iolist_to_binary(Decompressed)
214+
end
215+
end.
216+
217+
stream_body2({more, Parser, Buffer}, Client) ->
151218
stream_body_recv(Buffer, Client#client{parser=Parser});
152-
stream_body1({ok, Data, Parser}, Client) ->
219+
stream_body2({ok, Data, Parser}, Client) ->
153220
{ok, Data, Client#client{parser=Parser}};
154-
stream_body1({done, Rest}, Client) ->
221+
stream_body2({done, Rest}, Client) ->
155222
Client2 = end_stream_body(Rest, Client),
156223
{done, Client2};
157-
stream_body1(done, Client) ->
224+
stream_body2(done, Client) ->
158225
Client2 = end_stream_body(<<>>, Client),
159226
{done, Client2};
160-
stream_body1(Error, _Client) ->
227+
stream_body2(Error, _Client) ->
161228
Error.
162229

163230

@@ -277,6 +344,9 @@ skip_body(Client) ->
277344
{error, Reason} -> {error, Reason}
278345
end.
279346

347+
end_stream_body(Rest, Client = #client{ce={zlib,Z}}) ->
348+
catch zlib:close(Z),
349+
end_stream_body(Rest, Client#client{ce=nil});
280350
end_stream_body(Rest, Client0) ->
281351
Client = Client0#client{response_state=done,
282352
body_state=done,

0 commit comments

Comments
 (0)