aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2019-07-24 13:04:56 +0200
committerLoïc Hoguin <[email protected]>2019-07-24 13:04:56 +0200
commit8c6adf73d9d3fa1ebb49d4b9bd71caab1815dcb2 (patch)
tree0646abc1e66631fa340f6e0cb09cea50bc03d2f6
parent516933f9dd2722329b3886c495d5242308958fe1 (diff)
downloadgun-8c6adf73d9d3fa1ebb49d4b9bd71caab1815dcb2.tar.gz
gun-8c6adf73d9d3fa1ebb49d4b9bd71caab1815dcb2.tar.bz2
gun-8c6adf73d9d3fa1ebb49d4b9bd71caab1815dcb2.zip
Add push_promise_start/push_promise_end events
-rw-r--r--src/gun_default_event_h.erl8
-rw-r--r--src/gun_event.erl24
-rw-r--r--src/gun_http2.erl31
-rw-r--r--src/gun_tcp.erl2
-rw-r--r--test/event_SUITE.erl65
-rw-r--r--test/handlers/push_h.erl12
6 files changed, 129 insertions, 13 deletions
diff --git a/src/gun_default_event_h.erl b/src/gun_default_event_h.erl
index 9b56e71..25076ac 100644
--- a/src/gun_default_event_h.erl
+++ b/src/gun_default_event_h.erl
@@ -25,6 +25,8 @@
-export([request_start/2]).
-export([request_headers/2]).
-export([request_end/2]).
+-export([push_promise_start/2]).
+-export([push_promise_end/2]).
-export([response_start/2]).
-export([response_inform/2]).
-export([response_headers/2]).
@@ -70,6 +72,12 @@ request_headers(_EventData, State) ->
request_end(_EventData, State) ->
State.
+push_promise_start(_EventData, State) ->
+ State.
+
+push_promise_end(_EventData, State) ->
+ State.
+
response_start(_EventData, State) ->
State.
diff --git a/src/gun_event.erl b/src/gun_event.erl
index 4716f47..f27d336 100644
--- a/src/gun_event.erl
+++ b/src/gun_event.erl
@@ -92,6 +92,28 @@
-callback request_end(request_end_event(), State) -> State.
+%% push_promise_start.
+
+-type push_promise_start_event() :: #{
+ stream_ref := reference(),
+ reply_to := pid()
+}.
+
+-callback push_promise_start(push_promise_start_event(), State) -> State.
+
+%% push_promise_end.
+
+-type push_promise_end_event() :: #{
+ stream_ref := reference(),
+ reply_to := pid(),
+ promised_stream_ref := reference(),
+ method := binary(),
+ uri := binary(),
+ headers := [{binary(), iodata()}]
+}.
+
+-callback push_promise_end(push_promise_end_event(), State) -> State.
+
%% response_start.
-type response_start_event() :: #{
@@ -233,7 +255,5 @@
%% @todo origin_changed
%% @todo transport_changed
-%% @todo push_promise_start
-%% @todo push_promise_end
%% @todo cancel_start
%% @todo cancel_end
diff --git a/src/gun_http2.erl b/src/gun_http2.erl
index 55e7c06..d039ecb 100644
--- a/src/gun_http2.erl
+++ b/src/gun_http2.erl
@@ -115,12 +115,16 @@ parse(Data, State0=#http2_state{http2_machine=HTTP2Machine}, EvHandler, EvHandle
frame(State=#http2_state{http2_machine=HTTP2Machine0}, Frame, EvHandler, EvHandlerState0) ->
EvHandlerState = if
- is_tuple(Frame) andalso element(1, Frame) =:= headers ->
+ element(1, Frame) =:= headers; element(1, Frame) =:= push_promise ->
EvStreamID = element(2, Frame),
case cow_http2_machine:get_stream_remote_state(EvStreamID, HTTP2Machine0) of
{ok, idle} ->
#stream{ref=StreamRef, reply_to=ReplyTo} = get_stream_by_id(State, EvStreamID),
- EvHandler:response_start(#{
+ EvCallback = case element(1, Frame) of
+ headers -> response_start;
+ push_promise -> push_promise_start
+ end,
+ EvHandler:EvCallback(#{
stream_ref => StreamRef,
reply_to => ReplyTo
}, EvHandlerState0);
@@ -149,9 +153,9 @@ frame(State=#http2_state{http2_machine=HTTP2Machine0}, Frame, EvHandler, EvHandl
{rst_stream_frame(State#http2_state{http2_machine=HTTP2Machine}, StreamID, Reason),
EvHandlerState};
{ok, {push_promise, StreamID, PromisedStreamID, Headers, PseudoHeaders}, HTTP2Machine} ->
- {push_promise_frame(State#http2_state{http2_machine=HTTP2Machine},
- StreamID, PromisedStreamID, Headers, PseudoHeaders),
- EvHandlerState};
+ push_promise_frame(State#http2_state{http2_machine=HTTP2Machine},
+ StreamID, PromisedStreamID, Headers, PseudoHeaders,
+ EvHandler, EvHandlerState);
{ok, Frame={goaway, _StreamID, _Reason, _Data}, HTTP2Machine} ->
{terminate(State#http2_state{http2_machine=HTTP2Machine},
{stop, Frame, 'Server is going away.'}),
@@ -277,13 +281,22 @@ rst_stream_frame(State=#http2_state{streams=Streams0}, StreamID, Reason) ->
push_promise_frame(State=#http2_state{streams=Streams},
StreamID, PromisedStreamID, Headers, #{
method := Method, scheme := Scheme,
- authority := Authority, path := Path}) ->
+ authority := Authority, path := Path},
+ EvHandler, EvHandlerState0) ->
#stream{ref=StreamRef, reply_to=ReplyTo} = get_stream_by_id(State, StreamID),
PromisedStreamRef = make_ref(),
- ReplyTo ! {gun_push, self(), StreamRef, PromisedStreamRef, Method,
- iolist_to_binary([Scheme, <<"://">>, Authority, Path]), Headers},
+ URI = iolist_to_binary([Scheme, <<"://">>, Authority, Path]),
+ ReplyTo ! {gun_push, self(), StreamRef, PromisedStreamRef, Method, URI, Headers},
+ EvHandlerState = EvHandler:push_promise_end(#{
+ stream_ref => StreamRef,
+ reply_to => ReplyTo,
+ promised_stream_ref => PromisedStreamRef,
+ method => Method,
+ uri => URI,
+ headers => Headers
+ }, EvHandlerState0),
NewStream = #stream{id=PromisedStreamID, ref=PromisedStreamRef, reply_to=ReplyTo},
- State#http2_state{streams=[NewStream|Streams]}.
+ {State#http2_state{streams=[NewStream|Streams]}, EvHandlerState}.
ignored_frame(State=#http2_state{http2_machine=HTTP2Machine0}) ->
case cow_http2_machine:ignored_frame(HTTP2Machine0) of
diff --git a/src/gun_tcp.erl b/src/gun_tcp.erl
index 2d091a0..615e925 100644
--- a/src/gun_tcp.erl
+++ b/src/gun_tcp.erl
@@ -27,7 +27,7 @@
ip_addresses := [inet:ip_address()],
port := inet:port_number(),
tcp_module := module(),
- tcp_opts := [gen_tcp:connect_option()] | [ssl:connect_option()]
+ tcp_opts := [gen_tcp:connect_option()]
}.
-export_type([lookup_info/0]).
diff --git a/test/event_SUITE.erl b/test/event_SUITE.erl
index f7cae4e..ace509a 100644
--- a/test/event_SUITE.erl
+++ b/test/event_SUITE.erl
@@ -30,10 +30,12 @@ all() ->
groups() ->
Tests = ct_helper:all(?MODULE),
+ %% Push is not possible over HTTP/1.1.
+ PushTests = [T || T <- Tests, lists:sublist(atom_to_list(T), 5) =:= "push_"],
%% We currently do not support Websocket over HTTP/2.
WsTests = [T || T <- Tests, lists:sublist(atom_to_list(T), 3) =:= "ws_"],
[
- {http, [parallel], Tests},
+ {http, [parallel], Tests -- PushTests},
{http2, [parallel], Tests -- [protocol_changed|WsTests]}
].
@@ -43,6 +45,7 @@ init_per_suite(Config) ->
{"/", hello_h, []},
{"/empty", empty_h, []},
{"/inform", inform_h, []},
+ {"/push", push_h, []},
{"/stream", stream_h, []},
{"/trailers", trailers_h, []},
{"/ws", ws_echo, []}
@@ -343,6 +346,58 @@ do_request_end_headers_content_length_0(Config, EventName) ->
} = do_receive_event(EventName),
gun:close(Pid).
+push_promise_start(Config) ->
+ doc("Confirm that the push_promise_start event callback is called."),
+ {ok, Pid, _} = do_gun_open(Config),
+ {ok, _} = gun:await_up(Pid),
+ StreamRef = gun:get(Pid, "/push"),
+ ReplyTo = self(),
+ #{
+ stream_ref := StreamRef,
+ reply_to := ReplyTo
+ } = do_receive_event(?FUNCTION_NAME),
+ #{
+ stream_ref := StreamRef,
+ reply_to := ReplyTo
+ } = do_receive_event(?FUNCTION_NAME),
+ gun:close(Pid).
+
+push_promise_end(Config) ->
+ doc("Confirm that the push_promise_end event callback is called."),
+ {ok, Pid, _} = do_gun_open(Config),
+ {ok, _} = gun:await_up(Pid),
+ StreamRef = gun:get(Pid, "/push"),
+ ReplyTo = self(),
+ #{
+ stream_ref := StreamRef,
+ reply_to := ReplyTo,
+ promised_stream_ref := _,
+ method := <<"GET">>,
+ uri := <<"http://",_/bits>>,
+ headers := [_|_]
+ } = do_receive_event(?FUNCTION_NAME),
+ #{
+ stream_ref := StreamRef,
+ reply_to := ReplyTo,
+ promised_stream_ref := _,
+ method := <<"GET">>,
+ uri := <<"http://",_/bits>>,
+ headers := [_|_]
+ } = do_receive_event(?FUNCTION_NAME),
+ gun:close(Pid).
+
+push_promise_followed_by_response(Config) ->
+ doc("Confirm that the push_promise_end event callbacks are followed by response_start."),
+ {ok, Pid, _} = do_gun_open(Config),
+ {ok, _} = gun:await_up(Pid),
+ _ = gun:get(Pid, "/push"),
+ #{promised_stream_ref := PromisedStreamRef} = do_receive_event(push_promise_end),
+ #{stream_ref := StreamRef1} = do_receive_event(response_start),
+ #{stream_ref := StreamRef2} = do_receive_event(response_start),
+ #{stream_ref := StreamRef3} = do_receive_event(response_start),
+ true = lists:member(PromisedStreamRef, [StreamRef1, StreamRef2, StreamRef3]),
+ gun:close(Pid).
+
response_start(Config) ->
doc("Confirm that the request_start event callback is called."),
{ok, Pid, _} = do_gun_open(Config),
@@ -674,6 +729,14 @@ request_end(EventData, Pid) ->
Pid ! {?FUNCTION_NAME, EventData},
Pid.
+push_promise_start(EventData, Pid) ->
+ Pid ! {?FUNCTION_NAME, EventData},
+ Pid.
+
+push_promise_end(EventData, Pid) ->
+ Pid ! {?FUNCTION_NAME, EventData},
+ Pid.
+
response_start(EventData, Pid) ->
Pid ! {?FUNCTION_NAME, EventData},
Pid.
diff --git a/test/handlers/push_h.erl b/test/handlers/push_h.erl
new file mode 100644
index 0000000..4184227
--- /dev/null
+++ b/test/handlers/push_h.erl
@@ -0,0 +1,12 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+-module(push_h).
+
+-export([init/2]).
+
+init(Req, State) ->
+ cowboy_req:push("/", #{<<"accept">> => <<"text/plain">>}, Req),
+ cowboy_req:push("/empty", #{<<"accept">> => <<"text/plain">>}, Req),
+ {ok, cowboy_req:reply(200, #{
+ <<"content-type">> => <<"text/plain">>
+ }, <<"Hello world!">>, Req), State}.