aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_http2.erl
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2016-03-12 18:25:35 +0100
committerLoïc Hoguin <[email protected]>2016-03-12 18:25:35 +0100
commit4e6a4ee53f8453c900c5439100a249ebb278deda (patch)
tree12d9facbb6c4e93cf44415cb51165000967bbece /src/cowboy_http2.erl
parent92edad53d2546f64fc6dc58b697487e2f7be8ba9 (diff)
downloadcowboy-4e6a4ee53f8453c900c5439100a249ebb278deda.tar.gz
cowboy-4e6a4ee53f8453c900c5439100a249ebb278deda.tar.bz2
cowboy-4e6a4ee53f8453c900c5439100a249ebb278deda.zip
Add initial HTTP/1.1 Upgrade to HTTP/2
The same edge cases that fail with other handshake methods also fail here (mostly bad preface/timeouts stuff). In addition, the HTTP2-Settings header contents are currently not checked and so the related edge case tests also fail.
Diffstat (limited to 'src/cowboy_http2.erl')
-rw-r--r--src/cowboy_http2.erl68
1 files changed, 46 insertions, 22 deletions
diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl
index 67efa61..d73c879 100644
--- a/src/cowboy_http2.erl
+++ b/src/cowboy_http2.erl
@@ -15,7 +15,8 @@
-module(cowboy_http2).
-export([init/6]).
--export([init/8]).
+-export([init/7]).
+-export([init/9]).
-export([system_continue/3]).
-export([system_terminate/4]).
@@ -80,11 +81,10 @@
-spec init(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts(), module()) -> ok.
init(Parent, Ref, Socket, Transport, Opts, Handler) ->
- init(Parent, Ref, Socket, Transport, Opts, Handler, <<>>, undefined).
+ init(Parent, Ref, Socket, Transport, Opts, Handler, <<>>).
--spec init(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts(), module(),
- binary(), binary() | undefined) -> ok.
-init(Parent, Ref, Socket, Transport, Opts, Handler, Buffer, SettingsPayload) ->
+-spec init(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts(), module(), binary()) -> ok.
+init(Parent, Ref, Socket, Transport, Opts, Handler, Buffer) ->
State = #state{parent=Parent, ref=Ref, socket=Socket,
transport=Transport, opts=Opts, handler=Handler},
preface(State),
@@ -93,6 +93,22 @@ init(Parent, Ref, Socket, Transport, Opts, Handler, Buffer, SettingsPayload) ->
_ -> parse(State, Buffer)
end.
+%% @todo Add an argument for the request body.
+-spec init(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts(), module(),
+ binary(), binary() | undefined, cowboy_req:req()) -> ok.
+init(Parent, Ref, Socket, Transport, Opts, Handler, Buffer, SettingsPayload, Req) ->
+ State0 = #state{parent=Parent, ref=Ref, socket=Socket,
+ transport=Transport, opts=Opts, handler=Handler},
+ preface(State0),
+ %% @todo SettingsPayload.
+ %% 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),
+ case Buffer of
+ <<>> -> before_loop(State, Buffer);
+ _ -> parse(State, Buffer)
+ end.
+
preface(#state{socket=Socket, transport=Transport, next_settings=Settings}) ->
%% We send next_settings and use defaults until we get a ack.
ok = Transport:send(Socket, cow_http2:settings(Settings)).
@@ -317,10 +333,13 @@ commands(State, _, []) ->
%% @todo Keep IsFin in the state.
%% @todo Same two things above apply to DATA, possibly promise too.
commands(State=#state{socket=Socket, transport=Transport, encode_state=EncodeState0}, StreamID,
- [{response, IsFin, StatusCode, Headers0}|Tail]) ->
+ [{response, StatusCode, Headers0, Body}|Tail]) ->
Headers = Headers0#{<<":status">> => integer_to_binary(StatusCode)},
{HeaderBlock, EncodeState} = headers_encode(Headers, EncodeState0),
- Transport:send(Socket, cow_http2:headers(StreamID, IsFin, HeaderBlock)),
+ Transport:send(Socket, [
+ cow_http2:headers(StreamID, nofin, HeaderBlock),
+ cow_http2:data(StreamID, fin, Body)
+ ]),
commands(State#state{encode_state=EncodeState}, StreamID, Tail);
%% Send a response body chunk.
%%
@@ -361,7 +380,10 @@ commands(State, StreamID, [{upgrade, _Mod, _ModState}]) ->
commands(State, StreamID, []);
commands(State, StreamID, [{upgrade, _Mod, _ModState}|Tail]) ->
%% @todo This is an error. Not sure what to do here yet.
- commands(State, StreamID, Tail).
+ commands(State, StreamID, Tail);
+commands(State, StreamID, [stop|Tail]) ->
+ %% @todo Do we want to run the commands after a stop?
+ stream_terminate(State, StreamID, stop).
terminate(#state{socket=Socket, transport=Transport, handler=Handler,
streams=Streams, children=Children}, Reason) ->
@@ -379,8 +401,8 @@ terminate_all_streams([#stream{id=StreamID, state=StreamState}|Tail], Reason, Ha
%% Stream functions.
-stream_init(State0=#state{ref=Ref, socket=Socket, transport=Transport, handler=Handler, opts=Opts,
- streams=Streams0, decode_state=DecodeState0}, StreamID, IsFin, HeaderBlock) ->
+stream_init(State0=#state{ref=Ref, socket=Socket, transport=Transport, decode_state=DecodeState0},
+ StreamID, IsFin, HeaderBlock) ->
%% @todo Add clause for CONNECT requests (no scheme/path).
try headers_decode(HeaderBlock, DecodeState0) of
{Headers0=#{
@@ -425,18 +447,7 @@ stream_init(State0=#state{ref=Ref, socket=Socket, transport=Transport, handler=H
%% meta values (cowboy_websocket, cowboy_rest)
},
-
- try Handler:init(StreamID, Req, Opts) of
- {Commands, StreamState} ->
- Streams = [#stream{id=StreamID, state=StreamState}|Streams0],
- commands(State#state{streams=Streams}, StreamID, Commands)
- catch Class:Reason ->
- error_logger:error_msg("Exception occurred in ~s:init(~p, ~p, ~p, ~p, ~p, ~p, ~p) "
- "with reason ~p:~p.",
- [Handler, StreamID, IsFin, Method, Scheme, Authority, Path, Headers, Class, Reason]),
- stream_reset(State, StreamID, {internal_error, {Class, Reason},
- 'Exception occurred in StreamHandler:init/7 call.'}) %% @todo Check final arity.
- end;
+ stream_handler_init(State, StreamID, IsFin, Req);
{_, DecodeState} ->
Transport:send(Socket, cow_http2:rst_stream(StreamID, protocol_error)),
State0#state{decode_state=DecodeState}
@@ -445,6 +456,19 @@ stream_init(State0=#state{ref=Ref, socket=Socket, transport=Transport, handler=H
'Error while trying to decode HPACK-encoded header block. (RFC7540 4.3)'})
end.
+stream_handler_init(State=#state{handler=Handler, opts=Opts, streams=Streams0}, StreamID, IsFin, Req) ->
+ try Handler:init(StreamID, Req, Opts) of
+ {Commands, StreamState} ->
+ Streams = [#stream{id=StreamID, state=StreamState, remote=IsFin}|Streams0],
+ commands(State#state{streams=Streams}, StreamID, Commands)
+ catch Class:Reason ->
+ error_logger:error_msg("Exception occurred in ~s:init(~p, ~p, ~p) "
+ "with reason ~p:~p.",
+ [Handler, StreamID, IsFin, Req, Class, Reason]),
+ stream_reset(State, StreamID, {internal_error, {Class, Reason},
+ 'Exception occurred in StreamHandler:init/7 call.'}) %% @todo Check final arity.
+ end.
+
%% @todo We might need to keep track of which stream has been reset so we don't send lots of them.
stream_reset(State=#state{socket=Socket, transport=Transport}, StreamID,
StreamError={internal_error, _, _}) ->