aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2017-01-02 16:28:03 +0100
committerLoïc Hoguin <[email protected]>2017-01-02 16:28:03 +0100
commit6fcb19616b09b59f8fab68b30c08606f9260a5b3 (patch)
tree2bfd00a36ab749782f16319499f28fdc77d6582c
parent8f8f63b1cef76a28b2bf7558f4dbb7feda6e9168 (diff)
downloadgun-6fcb19616b09b59f8fab68b30c08606f9260a5b3.tar.gz
gun-6fcb19616b09b59f8fab68b30c08606f9260a5b3.tar.bz2
gun-6fcb19616b09b59f8fab68b30c08606f9260a5b3.zip
Implement HTTP/2 server push
-rw-r--r--src/gun.erl4
-rw-r--r--src/gun_http2.erl37
-rw-r--r--src/gun_spdy.erl6
3 files changed, 39 insertions, 8 deletions
diff --git a/src/gun.erl b/src/gun.erl
index 82f658d..eeaaa1e 100644
--- a/src/gun.erl
+++ b/src/gun.erl
@@ -309,8 +309,8 @@ await(ServerPid, StreamRef, Timeout, MRef) ->
{response, IsFin, Status, Headers};
{gun_data, ServerPid, StreamRef, IsFin, Data} ->
{data, IsFin, Data};
- {gun_push, ServerPid, StreamRef, NewStreamRef, Method, Host, Path, Headers} ->
- {push, NewStreamRef, Method, Host, Path, Headers};
+ {gun_push, ServerPid, StreamRef, NewStreamRef, Method, URI, Headers} ->
+ {push, NewStreamRef, Method, URI, Headers};
{gun_error, ServerPid, StreamRef, Reason} ->
{error, Reason};
{gun_error, ServerPid, Reason} ->
diff --git a/src/gun_http2.erl b/src/gun_http2.erl
index 1f852e2..e58db74 100644
--- a/src/gun_http2.erl
+++ b/src/gun_http2.erl
@@ -154,9 +154,40 @@ frame(settings_ack, State) -> %% @todo =#http2_state{next_settings=_NextSettings
State;
%% PUSH_PROMISE frame.
%% @todo Continuation.
-%frame({push_promise, StreamID, head_fin, PromisedStreamID, HeaderBlock}, State) ->
-% %% @todo
-% State;
+frame({push_promise, StreamID, head_fin, PromisedStreamID, HeaderBlock},
+ State=#http2_state{owner=Owner, decode_state=DecodeState0}) ->
+ case get_stream_by_id(PromisedStreamID, State) of
+ false ->
+ case get_stream_by_id(StreamID, State) of
+ #stream{ref=StreamRef} ->
+ try cow_hpack:decode(HeaderBlock, DecodeState0) of
+ {Headers0, DecodeState} ->
+ {Method, Scheme, Authority, Path, Headers} = try
+ {value, {_, Method0}, Headers1} = lists:keytake(<<":method">>, 1, Headers0),
+ {value, {_, Scheme0}, Headers2} = lists:keytake(<<":scheme">>, 1, Headers1),
+ {value, {_, Authority0}, Headers3} = lists:keytake(<<":authority">>, 1, Headers2),
+ {value, {_, Path0}, Headers4} = lists:keytake(<<":path">>, 1, Headers3),
+ {Method0, Scheme0, Authority0, Path0, Headers4}
+ catch error:badmatch ->
+ stream_reset(State, StreamID, {stream_error, protocol_error,
+ 'Malformed push promise; missing pseudo-header field. (RFC7540 8.1.2.3)'})
+ end,
+ NewStreamRef = make_ref(),
+ Owner ! {gun_push, self(), StreamRef, NewStreamRef, Method,
+ iolist_to_binary([Scheme, <<"://">>, Authority, Path]), Headers},
+ new_stream(PromisedStreamID, NewStreamRef, nofin, fin,
+ State#http2_state{decode_state=DecodeState})
+ catch _:_ ->
+ terminate(State, {connection_error, compression_error,
+ 'Error while trying to decode HPACK-encoded header block. (RFC7540 4.3)'})
+ end;
+ _ ->
+ stream_reset(State, StreamID, {stream_error, stream_closed,
+ 'DATA frame received for a closed or non-existent stream. (RFC7540 6.1)'})
+ end;
+ _ ->
+ stream_reset(State, StreamID, {stream_error, todo, ''})
+ end;
%% PING frame.
frame({ping, Opaque}, State=#http2_state{socket=Socket, transport=Transport}) ->
Transport:send(Socket, cow_http2:ping_ack(Opaque)),
diff --git a/src/gun_spdy.erl b/src/gun_spdy.erl
index ef5d0cb..dcd7496 100644
--- a/src/gun_spdy.erl
+++ b/src/gun_spdy.erl
@@ -97,15 +97,15 @@ handle_frame(Rest, State=#spdy_state{owner=Owner,
handle_frame(Rest, State=#spdy_state{owner=Owner,
socket=Socket, transport=Transport},
{syn_stream, StreamID, AssocToStreamID, IsFin, IsUnidirectional,
- _, Method, _, Host, Path, Version, Headers})
+ _, Method, Scheme, Host, Path, Version, Headers})
when AssocToStreamID =/= 0, IsUnidirectional ->
case get_stream_by_id(StreamID, State) of
false ->
case get_stream_by_id(AssocToStreamID, State) of
#stream{ref=AssocToStreamRef} ->
StreamRef = make_ref(),
- Owner ! {gun_push, self(), AssocToStreamRef,
- StreamRef, Method, Host, Path, Headers},
+ Owner ! {gun_push, self(), AssocToStreamRef, StreamRef, Method,
+ iolist_to_binary([Scheme, <<"://">>, Host, Path]), Headers},
handle_loop(Rest, new_stream(StreamID, StreamRef,
not IsFin, false, Version, State));
false ->