0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 12:20:24 +00:00

Pass the HTTP/2 switch_protocol event to stream handlers

To accomplish this the code for sending the 101 response was
moved to the cowboy_http2 module.
This commit is contained in:
Loïc Hoguin 2017-04-18 14:06:34 +02:00
parent 3633bceac5
commit 6e8b907ae2
No known key found for this signature in database
GPG key ID: 71366FF21851DF03
3 changed files with 26 additions and 15 deletions

View file

@ -18,6 +18,11 @@ Cowboy calls the stream handler for nearly all events
related to a stream. Exceptions vary depending on the
protocol.
Extra care must be taken when implementing stream handlers
to ensure compatibility. While some modification of the
events and commands is allowed, it is generally not a good
idea to completely omit them.
== Callbacks
Stream handlers must implement the following interface:

View file

@ -635,6 +635,7 @@ request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, in_stream
%% HTTP/2 upgrade.
%% @todo We must not upgrade to h2c over a TLS connection.
is_http2_upgrade(#{<<"connection">> := Conn, <<"upgrade">> := Upgrade,
<<"http2-settings">> := HTTP2Settings}, 'HTTP/1.1') ->
Conns = cow_http_hd:parse_connection(Conn),
@ -675,13 +676,6 @@ http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Tran
%% Always half-closed stream coming from this side.
try cow_http_hd:parse_http2_settings(HTTP2Settings) of
Settings ->
%% @todo We should invoke cowboy_stream:info for this stream,
%% with a switch_protocol tuple.
Transport:send(Socket, cow_http:response(101, 'HTTP/1.1', maps:to_list(#{
<<"connection">> => <<"Upgrade">>,
<<"upgrade">> => <<"h2c">>
}))),
%% @todo Possibly redirect the request if it was https.
_ = cancel_request_timeout(State),
cowboy_http2:init(Parent, Ref, Socket, Transport, Opts, Peer, Buffer, Settings, Req)
catch _:_ ->

View file

@ -27,7 +27,7 @@
%% Stream handlers and their state.
state = undefined :: {module(), any()},
%% Whether we finished sending data.
local = idle :: idle | cowboy_stream:fin(),
local = idle :: idle | upgrade | cowboy_stream:fin(),
%% Whether we finished receiving data.
remote = nofin :: cowboy_stream:fin(),
%% Request body length.
@ -122,11 +122,17 @@ init(Parent, Ref, Socket, Transport, Opts, Peer, Buffer, _Settings, Req) ->
State0 = #state{parent=Parent, ref=Ref, socket=Socket,
transport=Transport, opts=Opts, peer=Peer,
parse_state={preface, sequence, preface_timeout(Opts)}},
preface(State0),
%% @todo Apply settings.
%% StreamID from HTTP/1.1 Upgrade requests is always 1.
%% The stream is always in the half-closed (remote) state.
State = stream_handler_init(State0, 1, fin, Req),
State1 = stream_handler_init(State0, 1, fin, upgrade, Req),
%% We assume that the upgrade will be applied. A stream handler
%% must not prevent the normal operations of the server.
State = info(State1, 1, {switch_protocol, #{
<<"connection">> => <<"Upgrade">>,
<<"upgrade">> => <<"h2c">>
}, ?MODULE, undefined}), %% @todo undefined or #{}?
preface(State),
case Buffer of
<<>> -> before_loop(State, Buffer);
_ -> parse(State, Buffer)
@ -496,7 +502,12 @@ commands(State, Stream=#stream{id=StreamID}, [Error = {internal_error, _, _}|_Ta
%% @todo Do we even allow commands after?
%% @todo Only reset when the stream still exists.
stream_reset(after_commands(State, Stream), StreamID, Error);
%% @todo HTTP/2 has no support for the Upgrade mechanism.
%% Upgrade to HTTP/2. This is triggered by cowboy_http2 itself.
commands(State=#state{socket=Socket, transport=Transport},
Stream=#stream{local=upgrade}, [{switch_protocol, Headers, ?MODULE, _}|Tail]) ->
Transport:send(Socket, cow_http:response(101, 'HTTP/1.1', maps:to_list(Headers))),
commands(State, Stream#stream{local=idle}, Tail);
%% HTTP/2 has no support for the Upgrade mechanism.
commands(State, Stream, [{switch_protocol, _Headers, _Mod, _ModState}|Tail]) ->
%% @todo This is an error. Not sure what to do here yet.
commands(State, Stream, Tail);
@ -610,7 +621,7 @@ stream_init(State0=#state{ref=Ref, socket=Socket, transport=Transport, peer=Peer
has_body => IsFin =:= nofin,
body_length => BodyLength
},
stream_handler_init(State, StreamID, IsFin, Req);
stream_handler_init(State, StreamID, IsFin, idle, Req);
{_, DecodeState} ->
Transport:send(Socket, cow_http2:rst_stream(StreamID, protocol_error)),
State0#state{decode_state=DecodeState}
@ -619,15 +630,16 @@ stream_init(State0=#state{ref=Ref, socket=Socket, transport=Transport, peer=Peer
'Error while trying to decode HPACK-encoded header block. (RFC7540 4.3)'})
end.
stream_handler_init(State=#state{opts=Opts}, StreamID, IsFin, Req) ->
stream_handler_init(State=#state{opts=Opts}, StreamID, RemoteIsFin, LocalIsFin, Req) ->
try cowboy_stream:init(StreamID, Req, Opts) of
{Commands, StreamState} ->
commands(State#state{client_streamid=StreamID},
#stream{id=StreamID, state=StreamState, remote=IsFin}, Commands)
#stream{id=StreamID, state=StreamState,
remote=RemoteIsFin, local=LocalIsFin}, Commands)
catch Class:Reason ->
error_logger:error_msg("Exception occurred in "
"cowboy_stream:init(~p, ~p, ~p) with reason ~p:~p.",
[StreamID, IsFin, Req, Class, Reason]),
[StreamID, Req, Opts, Class, Reason]),
stream_reset(State, StreamID, {internal_error, {Class, Reason},
'Exception occurred in cowboy_stream:init/3.'})
end.