From 6fcb19616b09b59f8fab68b30c08606f9260a5b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 2 Jan 2017 16:28:03 +0100 Subject: Implement HTTP/2 server push --- src/gun.erl | 4 ++-- src/gun_http2.erl | 37 ++++++++++++++++++++++++++++++++++--- src/gun_spdy.erl | 6 +++--- 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 -> -- cgit v1.2.3