aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2019-01-05 16:21:53 +0100
committerLoïc Hoguin <[email protected]>2019-01-05 16:21:53 +0100
commitbd91a3207f4376bd63a72f6c5b1ebabb11747634 (patch)
tree188a70e158c54941f752b3cee8606ba6ea31d939
parent4bd87ba7d1699fc1b2901cd61b703429f1deff63 (diff)
downloadgun-bd91a3207f4376bd63a72f6c5b1ebabb11747634.tar.gz
gun-bd91a3207f4376bd63a72f6c5b1ebabb11747634.tar.bz2
gun-bd91a3207f4376bd63a72f6c5b1ebabb11747634.zip
Don't send empty data chunks
This was a bug in the case of HTTP/1.1 and an inconvenience in the case of HTTP/2.
-rw-r--r--src/gun.erl7
-rw-r--r--test/gun_SUITE.erl58
-rw-r--r--test/gun_test.erl11
3 files changed, 65 insertions, 11 deletions
diff --git a/src/gun.erl b/src/gun.erl
index a8b76f8..55e70dd 100644
--- a/src/gun.erl
+++ b/src/gun.erl
@@ -436,7 +436,12 @@ request(ServerPid, Method, Path, Headers, Body, ReqOpts) ->
-spec data(pid(), reference(), fin | nofin, iodata()) -> ok.
data(ServerPid, StreamRef, IsFin, Data) ->
- gen_statem:cast(ServerPid, {data, self(), StreamRef, IsFin, Data}).
+ case iolist_size(Data) of
+ 0 when IsFin =:= nofin ->
+ ok;
+ _ ->
+ gen_statem:cast(ServerPid, {data, self(), StreamRef, IsFin, Data})
+ end.
%% Tunneling.
diff --git a/test/gun_SUITE.erl b/test/gun_SUITE.erl
index a7bb0f1..ba5b523 100644
--- a/test/gun_SUITE.erl
+++ b/test/gun_SUITE.erl
@@ -18,7 +18,9 @@
-import(ct_helper, [doc/1]).
-import(ct_helper, [name/0]).
+-import(gun_test, [init_origin/2]).
-import(gun_test, [init_origin/3]).
+-import(gun_test, [receive_all_from/2]).
all() ->
ct_helper:all(?MODULE).
@@ -107,16 +109,41 @@ detect_owner_gone_ws(_) ->
end,
cowboy:stop_listener(Name).
-shutdown_reason(_) ->
- doc("The last connection failure must be propagated."),
- {ok, Pid} = gun:open("localhost", 12345, #{retry => 0}),
- Ref = monitor(process, Pid),
- receive
- {'DOWN', Ref, process, Pid, {shutdown, econnrefused}} ->
- ok
- after 200 ->
- error(timeout)
- end.
+ignore_empty_data_http(_) ->
+ doc("When gun:data/4 is called with nofin and empty data, it must be ignored."),
+ {ok, OriginPid, OriginPort} = init_origin(tcp, http),
+ {ok, Pid} = gun:open("localhost", OriginPort),
+ {ok, http} = gun:await_up(Pid),
+ Ref = gun:put(Pid, "/", []),
+ gun:data(Pid, Ref, nofin, "hello "),
+ gun:data(Pid, Ref, nofin, ["", <<>>]),
+ gun:data(Pid, Ref, fin, "world!"),
+ Data = receive_all_from(OriginPid, 500),
+ Lines = binary:split(Data, <<"\r\n">>, [global]),
+ Zero = [Z || <<"0">> = Z <- Lines],
+ 1 = length(Zero),
+ gun:close(Pid).
+
+ignore_empty_data_http2(_) ->
+ doc("When gun:data/4 is called with nofin and empty data, it must be ignored."),
+ {ok, OriginPid, OriginPort} = init_origin(tcp, http2),
+ {ok, Pid} = gun:open("localhost", OriginPort, #{protocols => [http2]}),
+ {ok, http2} = gun:await_up(Pid),
+ timer:sleep(100), %% Give enough time for the handshake to fully complete.
+ Ref = gun:put(Pid, "/", []),
+ gun:data(Pid, Ref, nofin, "hello "),
+ gun:data(Pid, Ref, nofin, ["", <<>>]),
+ gun:data(Pid, Ref, fin, "world!"),
+ Data = receive_all_from(OriginPid, 500),
+ <<
+ %% HEADERS frame.
+ Len1:24, 1, _:40, _:Len1/unit:8,
+ %% First DATA frame.
+ 6:24, 0, _:7, 0:1, _:32, "hello ",
+ %% Second and final DATA frame.
+ 6:24, 0, _:7, 1:1, _:32, "world!"
+ >> = Data,
+ gun:close(Pid).
info(_) ->
doc("Get info from the Gun connection."),
@@ -261,6 +288,17 @@ retry_timeout(_) ->
error(shutdown_too_late)
end.
+shutdown_reason(_) ->
+ doc("The last connection failure must be propagated."),
+ {ok, Pid} = gun:open("localhost", 12345, #{retry => 0}),
+ Ref = monitor(process, Pid),
+ receive
+ {'DOWN', Ref, process, Pid, {shutdown, econnrefused}} ->
+ ok
+ after 200 ->
+ error(timeout)
+ end.
+
transform_header_name(_) ->
doc("The transform_header_name option allows changing the case of header names."),
{ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}]),
diff --git a/test/gun_test.erl b/test/gun_test.erl
index 14c70c3..e74fcd0 100644
--- a/test/gun_test.erl
+++ b/test/gun_test.erl
@@ -106,3 +106,14 @@ receive_from(Pid, Timeout) ->
after Timeout ->
error(timeout)
end.
+
+receive_all_from(Pid, Timeout) ->
+ receive_all_from(Pid, Timeout, <<>>).
+
+receive_all_from(Pid, Timeout, Acc) ->
+ try
+ More = receive_from(Pid, Timeout),
+ receive_all_from(Pid, Timeout, <<Acc/binary, More/binary>>)
+ catch error:timeout ->
+ Acc
+ end.