Skip to content

Commit 9c18419

Browse files
committed
support "Expect: 100-continue" header
When the header is set the body is only sent if the server answer with a 100 continue status. In other case `hackney:stream_body/2` and `hackney_multipart:stream/2` return {stop, Client}. When the body is not streamed Hakney takes of this header and only send it if needed. This changes allows you to received immediately the server errors and allows you don't send the body for nothing. More info about this header: http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3
1 parent 377258a commit 9c18419

File tree

5 files changed

+92
-3
lines changed

5 files changed

+92
-3
lines changed

examples/test_continue.ebin

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env escript
2+
%% -*- erlang -*-
3+
%%! -pa ./ebin -pa ./deps/mimetypes/ebin
4+
5+
-module(test_continue).
6+
7+
8+
9+
main(_) ->
10+
hackney:start(),
11+
12+
{ok, _, _, Client} = hackney:request(<<"https://friendpaste.com">>),
13+
{ok, Body, Client1} = hackney:body(Client),
14+
15+
io:format("body: ~p~n~n", [Body]),
16+
17+
{ok, _, _, Client2} = hackney:send_request(Client1, {get,
18+
<<"/_all_languages">>,
19+
[],
20+
<<>>}),
21+
22+
{ok, Body1, Client3} = hackney:body(Client2),
23+
24+
io:format("body: ~p~n~n", [Body1]),
25+
26+
27+
ReqBody = << "{
28+
\"id\": \"some_paste_id\",
29+
\"rev\": \"some_revision_id\",
30+
\"changeset\": \"changeset in unidiff format\"
31+
}" >>,
32+
33+
ReqHeaders = [{<<"Content-Type">>, <<"application/json">>},
34+
{<<"Expect">>, <<"100-continue">>}],
35+
36+
{ok, _, _, Client4} = hackney:send_request(Client3, {post, <<"/">>,
37+
ReqHeaders,
38+
ReqBody}),
39+
{ok, Body2, Client5} = hackney:body(Client4),
40+
io:format("body: ~p~n~n", [Body2]),
41+
42+
ReqBody1 = {file, "./examples/test.json"},
43+
44+
{ok, _, _, Client6} = hackney:send_request(Client5, {post, <<"/">>,
45+
ReqHeaders,
46+
ReqBody1}),
47+
{ok, Body3, Client7} = hackney:body(Client6),
48+
io:format("body: ~p~n~n", [Body3]),
49+
50+
hackney:close(Client7).

include/hackney.hrl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
response_state = start,
1616
mp_boundary = nil,
1717
req_type = normal,
18+
expect = false,
1819
send_fun=nil,
1920
body_state=waiting,
2021
req_chunk_size=4096,

src/hackney.erl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ start_response(Client) ->
313313

314314

315315
%% @doc Stream the response body.
316+
-spec stream_body(#client{})
317+
-> {ok, #client{}} | {stop, #client{}} | {error, term()}.
316318
stream_body(Client) ->
317319
hackney_response:stream_body(Client).
318320

src/hackney_request.erl

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ perform(Client0, {Method0, Path, Headers0, Body0}) ->
4848
Headers0),
4949

5050
ReqType0 = req_type(HeadersDict),
51+
Expect = expectation(HeadersDict),
5152

5253
%% build headers with the body.
5354
{HeaderDict1, ReqType, Body, Client1} = case Body0 of
@@ -98,11 +99,15 @@ perform(Client0, {Method0, Path, Headers0, Body0}) ->
9899
%% send headers data
99100
case hackney_request:send(Client, HeadersData) of
100101
ok when Body =:= stream ->
101-
{ok, Client#client{response_state=stream, method=Method}};
102+
{ok, Client#client{response_state=stream, method=Method,
103+
expect=Expect}};
102104
ok ->
103-
case stream_body(Body, Client) of
105+
case stream_body(Body, Client#client{expect=Expect}) of
104106
{error, _Reason}=E ->
105107
E;
108+
{stop, Client2} ->
109+
FinalClient = Client2#client{method=Method},
110+
hackney_response:start_response(FinalClient);
106111
{ok, Client2} ->
107112
case end_stream_body(Client2) of
108113
{ok, Client3} ->
@@ -116,6 +121,15 @@ perform(Client0, {Method0, Path, Headers0, Body0}) ->
116121
Error
117122
end.
118123

124+
stream_body(Msg, #client{expect=true}=Client) ->
125+
case hackney_response:expect_response(Client) of
126+
{continue, Client2} ->
127+
stream_body(Msg, Client2);
128+
{stop, Client2} ->
129+
{stop, Client2};
130+
Error ->
131+
Error
132+
end;
119133
stream_body(eof, Client) ->
120134
{ok, Client#client{response_state=waiting}};
121135
stream_body(<<>>, Client) ->
@@ -331,6 +345,13 @@ req_type(Headers) ->
331345
_ -> normal
332346
end.
333347

348+
expectation(Headers) ->
349+
ExpectHdr = hackney_headers:get_value(<<"Expect">>, Headers, <<>>),
350+
case hackney_util:to_lower(ExpectHdr) of
351+
<<"100-continue">> -> true;
352+
_ -> false
353+
end.
354+
334355
end_stream_body(#client{req_type=chunked}=Client) ->
335356
case send_chunk(Client, <<>>) of
336357
ok ->

src/hackney_response.erl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
stream_headers/1, stream_header/1,
1919
stream_body/1,
2020
body/1, body/2, skip_body/1,
21-
close/1]).
21+
close/1,
22+
expect_response/1]).
2223

2324
%% @doc Start the response It parse the request lines and headers.
2425
start_response(#client{response_state=stream, mp_boundary=nil} = Client) ->
@@ -50,6 +51,20 @@ start_response(#client{response_state=waiting} = Client) ->
5051
start_response(_) ->
5152
{error, invalide_state}.
5253

54+
55+
expect_response(Client) ->
56+
case recv(Client#client{recv_timeout=1000}) of
57+
{ok, <<"HTTP/1.1 100 Continue\r\n\r\n" >>} ->
58+
{continue, Client#client{expect=false}};
59+
{ok, Data} ->
60+
{stop, Client#client{buffer=Data, expect=false,
61+
response_state=waiting}};
62+
{error, timeout} ->
63+
continue;
64+
Error ->
65+
Error
66+
end.
67+
5368
%% @doc parse the status line
5469
stream_status(#client{buffer=Buf}=Client) ->
5570
case binary:split(Buf, <<"\r\n">>) of

0 commit comments

Comments
 (0)