mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
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.
This commit is contained in:
parent
92edad53d2
commit
4e6a4ee53f
3 changed files with 208 additions and 81 deletions
|
@ -327,7 +327,7 @@ parse_request(Buffer, State=#state{opts=Opts, in_streamid=InStreamID}, EmptyLine
|
||||||
%% Accept direct HTTP/2 only at the beginning of the connection.
|
%% Accept direct HTTP/2 only at the beginning of the connection.
|
||||||
<< "PRI * HTTP/2.0\r\n", _/bits >> when InStreamID =:= 1 ->
|
<< "PRI * HTTP/2.0\r\n", _/bits >> when InStreamID =:= 1 ->
|
||||||
%% @todo Might be worth throwing to get a clean stacktrace.
|
%% @todo Might be worth throwing to get a clean stacktrace.
|
||||||
http2_upgrade(State, Buffer, undefined);
|
http2_upgrade(State, Buffer);
|
||||||
_ ->
|
_ ->
|
||||||
parse_method(Buffer, State, <<>>,
|
parse_method(Buffer, State, <<>>,
|
||||||
maps:get(max_method_length, Opts, 32))
|
maps:get(max_method_length, Opts, 32))
|
||||||
|
@ -628,31 +628,76 @@ request(Buffer, State0=#state{ref=Ref, transport=Transport, in_streamid=StreamID
|
||||||
|
|
||||||
%% meta values (cowboy_websocket, cowboy_rest)
|
%% meta values (cowboy_websocket, cowboy_rest)
|
||||||
},
|
},
|
||||||
State = case HasBody of
|
case is_http2_upgrade(Headers, Version) of
|
||||||
true ->
|
|
||||||
cancel_request_timeout(State0#state{in_state=#ps_body{
|
|
||||||
%% @todo Don't need length anymore?
|
|
||||||
transfer_decode_fun = TDecodeFun,
|
|
||||||
transfer_decode_state = TDecodeState
|
|
||||||
}});
|
|
||||||
false ->
|
false ->
|
||||||
set_request_timeout(State0#state{in_streamid=StreamID + 1, in_state=#ps_request_line{}})
|
State = case HasBody of
|
||||||
end,
|
true ->
|
||||||
{request, Req, State, Buffer}.
|
cancel_request_timeout(State0#state{in_state=#ps_body{
|
||||||
|
%% @todo Don't need length anymore?
|
||||||
|
transfer_decode_fun = TDecodeFun,
|
||||||
|
transfer_decode_state = TDecodeState
|
||||||
|
}});
|
||||||
|
false ->
|
||||||
|
set_request_timeout(State0#state{in_streamid=StreamID + 1, in_state=#ps_request_line{}})
|
||||||
|
end,
|
||||||
|
{request, Req, State, Buffer};
|
||||||
|
{true, SettingsPayload} ->
|
||||||
|
http2_upgrade(State0, Buffer, SettingsPayload, Req)
|
||||||
|
end.
|
||||||
|
|
||||||
%% HTTP/2 upgrade.
|
%% HTTP/2 upgrade.
|
||||||
|
|
||||||
|
is_http2_upgrade(#{<<"connection">> := Conn, <<"upgrade">> := Upgrade,
|
||||||
|
<<"http2-settings">> := HTTP2Settings}, 'HTTP/1.1') ->
|
||||||
|
Conns = cow_http_hd:parse_connection(Conn),
|
||||||
|
io:format(user, "CONNS ~p~n", [Conns]),
|
||||||
|
case {lists:member(<<"upgrade">>, Conns), lists:member(<<"http2-settings">>, Conns)} of
|
||||||
|
{true, true} ->
|
||||||
|
Protocols = cow_http_hd:parse_upgrade(Upgrade),
|
||||||
|
io:format(user, "PROTOCOLS ~p~n", [Protocols]),
|
||||||
|
case lists:member(<<"h2c">>, Protocols) of
|
||||||
|
true ->
|
||||||
|
SettingsPayload = cow_http_hd:parse_http2_settings(HTTP2Settings),
|
||||||
|
{true, SettingsPayload};
|
||||||
|
false ->
|
||||||
|
false
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end;
|
||||||
|
is_http2_upgrade(_, _) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
%% Upgrade through an HTTP/1.1 request.
|
||||||
|
|
||||||
|
%% Prior knowledge upgrade, without an HTTP/1.1 request.
|
||||||
http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Transport,
|
http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Transport,
|
||||||
opts=Opts, handler=Handler}, Buffer, Settings) ->
|
opts=Opts, handler=Handler}, Buffer) ->
|
||||||
case Transport:secure() of
|
case Transport:secure() of
|
||||||
false ->
|
false ->
|
||||||
_ = cancel_request_timeout(State),
|
_ = cancel_request_timeout(State),
|
||||||
cowboy_http2:init(Parent, Ref, Socket, Transport, Opts, Handler, Buffer, Settings);
|
cowboy_http2:init(Parent, Ref, Socket, Transport, Opts, Handler, Buffer);
|
||||||
true ->
|
true ->
|
||||||
error_terminate(400, State, {connection_error, protocol_error,
|
error_terminate(400, State, {connection_error, protocol_error,
|
||||||
'Clients that support HTTP/2 over TLS MUST use ALPN. (RFC7540 3.4)'})
|
'Clients that support HTTP/2 over TLS MUST use ALPN. (RFC7540 3.4)'})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Transport,
|
||||||
|
opts=Opts, handler=Handler}, Buffer, SettingsPayload, Req) ->
|
||||||
|
%% @todo
|
||||||
|
%% However if the client sent a body, we need to read the body in full
|
||||||
|
%% and if we can't do that, return a 413 response. Some options are in order.
|
||||||
|
%% Always half-closed stream coming from this side.
|
||||||
|
|
||||||
|
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, Handler, Buffer, SettingsPayload, Req).
|
||||||
|
|
||||||
%% Request body parsing.
|
%% Request body parsing.
|
||||||
|
|
||||||
parse_body(Buffer, State=#state{in_streamid=StreamID, in_state=
|
parse_body(Buffer, State=#state{in_streamid=StreamID, in_state=
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
-module(cowboy_http2).
|
-module(cowboy_http2).
|
||||||
|
|
||||||
-export([init/6]).
|
-export([init/6]).
|
||||||
-export([init/8]).
|
-export([init/7]).
|
||||||
|
-export([init/9]).
|
||||||
|
|
||||||
-export([system_continue/3]).
|
-export([system_continue/3]).
|
||||||
-export([system_terminate/4]).
|
-export([system_terminate/4]).
|
||||||
|
@ -80,11 +81,10 @@
|
||||||
|
|
||||||
-spec init(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts(), module()) -> ok.
|
-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) ->
|
||||||
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(),
|
-spec init(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts(), module(), binary()) -> ok.
|
||||||
binary(), binary() | undefined) -> ok.
|
init(Parent, Ref, Socket, Transport, Opts, Handler, Buffer) ->
|
||||||
init(Parent, Ref, Socket, Transport, Opts, Handler, Buffer, SettingsPayload) ->
|
|
||||||
State = #state{parent=Parent, ref=Ref, socket=Socket,
|
State = #state{parent=Parent, ref=Ref, socket=Socket,
|
||||||
transport=Transport, opts=Opts, handler=Handler},
|
transport=Transport, opts=Opts, handler=Handler},
|
||||||
preface(State),
|
preface(State),
|
||||||
|
@ -93,6 +93,22 @@ init(Parent, Ref, Socket, Transport, Opts, Handler, Buffer, SettingsPayload) ->
|
||||||
_ -> parse(State, Buffer)
|
_ -> parse(State, Buffer)
|
||||||
end.
|
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}) ->
|
preface(#state{socket=Socket, transport=Transport, next_settings=Settings}) ->
|
||||||
%% We send next_settings and use defaults until we get a ack.
|
%% We send next_settings and use defaults until we get a ack.
|
||||||
ok = Transport:send(Socket, cow_http2:settings(Settings)).
|
ok = Transport:send(Socket, cow_http2:settings(Settings)).
|
||||||
|
@ -317,10 +333,13 @@ commands(State, _, []) ->
|
||||||
%% @todo Keep IsFin in the state.
|
%% @todo Keep IsFin in the state.
|
||||||
%% @todo Same two things above apply to DATA, possibly promise too.
|
%% @todo Same two things above apply to DATA, possibly promise too.
|
||||||
commands(State=#state{socket=Socket, transport=Transport, encode_state=EncodeState0}, StreamID,
|
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)},
|
Headers = Headers0#{<<":status">> => integer_to_binary(StatusCode)},
|
||||||
{HeaderBlock, EncodeState} = headers_encode(Headers, EncodeState0),
|
{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);
|
commands(State#state{encode_state=EncodeState}, StreamID, Tail);
|
||||||
%% Send a response body chunk.
|
%% Send a response body chunk.
|
||||||
%%
|
%%
|
||||||
|
@ -361,7 +380,10 @@ commands(State, StreamID, [{upgrade, _Mod, _ModState}]) ->
|
||||||
commands(State, StreamID, []);
|
commands(State, StreamID, []);
|
||||||
commands(State, StreamID, [{upgrade, _Mod, _ModState}|Tail]) ->
|
commands(State, StreamID, [{upgrade, _Mod, _ModState}|Tail]) ->
|
||||||
%% @todo This is an error. Not sure what to do here yet.
|
%% @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,
|
terminate(#state{socket=Socket, transport=Transport, handler=Handler,
|
||||||
streams=Streams, children=Children}, Reason) ->
|
streams=Streams, children=Children}, Reason) ->
|
||||||
|
@ -379,8 +401,8 @@ terminate_all_streams([#stream{id=StreamID, state=StreamState}|Tail], Reason, Ha
|
||||||
|
|
||||||
%% Stream functions.
|
%% Stream functions.
|
||||||
|
|
||||||
stream_init(State0=#state{ref=Ref, socket=Socket, transport=Transport, handler=Handler, opts=Opts,
|
stream_init(State0=#state{ref=Ref, socket=Socket, transport=Transport, decode_state=DecodeState0},
|
||||||
streams=Streams0, decode_state=DecodeState0}, StreamID, IsFin, HeaderBlock) ->
|
StreamID, IsFin, HeaderBlock) ->
|
||||||
%% @todo Add clause for CONNECT requests (no scheme/path).
|
%% @todo Add clause for CONNECT requests (no scheme/path).
|
||||||
try headers_decode(HeaderBlock, DecodeState0) of
|
try headers_decode(HeaderBlock, DecodeState0) of
|
||||||
{Headers0=#{
|
{Headers0=#{
|
||||||
|
@ -425,18 +447,7 @@ stream_init(State0=#state{ref=Ref, socket=Socket, transport=Transport, handler=H
|
||||||
|
|
||||||
%% meta values (cowboy_websocket, cowboy_rest)
|
%% meta values (cowboy_websocket, cowboy_rest)
|
||||||
},
|
},
|
||||||
|
stream_handler_init(State, StreamID, IsFin, Req);
|
||||||
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;
|
|
||||||
{_, DecodeState} ->
|
{_, DecodeState} ->
|
||||||
Transport:send(Socket, cow_http2:rst_stream(StreamID, protocol_error)),
|
Transport:send(Socket, cow_http2:rst_stream(StreamID, protocol_error)),
|
||||||
State0#state{decode_state=DecodeState}
|
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)'})
|
'Error while trying to decode HPACK-encoded header block. (RFC7540 4.3)'})
|
||||||
end.
|
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.
|
%% @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,
|
stream_reset(State=#state{socket=Socket, transport=Transport}, StreamID,
|
||||||
StreamError={internal_error, _, _}) ->
|
StreamError={internal_error, _, _}) ->
|
||||||
|
|
|
@ -70,7 +70,7 @@ http_upgrade_ignore_if_http_10(Config) ->
|
||||||
"GET / HTTP/1.0\r\n"
|
"GET / HTTP/1.0\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 200">>} = gen_tcp:recv(Socket, 12, 1000),
|
{ok, <<"HTTP/1.1 200">>} = gen_tcp:recv(Socket, 12, 1000),
|
||||||
|
@ -84,13 +84,13 @@ http_upgrade_ignore_missing_upgrade_in_connection(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: HTTP2-Settings\r\n"
|
"Connection: HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 200">>} = gen_tcp:recv(Socket, 12, 1000),
|
{ok, <<"HTTP/1.1 200">>} = gen_tcp:recv(Socket, 12, 1000),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
http_upgrade_reject_missing_http2_settings_in_connection(Config) ->
|
http_upgrade_ignore_missing_http2_settings_in_connection(Config) ->
|
||||||
doc("The HTTP2-Settings header must be listed in the "
|
doc("The HTTP2-Settings header must be listed in the "
|
||||||
"Connection header field. (RFC7540 3.2.1, RFC7230 6.7)"),
|
"Connection header field. (RFC7540 3.2.1, RFC7230 6.7)"),
|
||||||
{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
|
{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
|
||||||
|
@ -98,10 +98,10 @@ http_upgrade_reject_missing_http2_settings_in_connection(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade\r\n"
|
"Connection: Upgrade\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 400">>} = gen_tcp:recv(Socket, 12, 1000),
|
{ok, <<"HTTP/1.1 200">>} = gen_tcp:recv(Socket, 12, 1000),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
http_upgrade_reject_zero_http2_settings_header(Config) ->
|
http_upgrade_reject_zero_http2_settings_header(Config) ->
|
||||||
|
@ -112,7 +112,7 @@ http_upgrade_reject_zero_http2_settings_header(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 400">>} = gen_tcp:recv(Socket, 12, 1000),
|
{ok, <<"HTTP/1.1 400">>} = gen_tcp:recv(Socket, 12, 1000),
|
||||||
ok.
|
ok.
|
||||||
|
@ -125,7 +125,7 @@ http_upgrade_reject_two_http2_settings_header(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
|
@ -140,13 +140,23 @@ http_upgrade_reject_bad_http2_settings_header(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
%% We send a full SETTINGS frame on purpose.
|
%% We send a full SETTINGS frame on purpose.
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 400">>} = gen_tcp:recv(Socket, 12, 1000),
|
{ok, <<"HTTP/1.1 400">>} = gen_tcp:recv(Socket, 12, 1000),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
%% Match directly for now.
|
||||||
|
do_recv_101(Socket) ->
|
||||||
|
{ok, <<
|
||||||
|
"HTTP/1.1 101 Switching Protocols\r\n"
|
||||||
|
"connection: Upgrade\r\n"
|
||||||
|
"upgrade: h2c\r\n"
|
||||||
|
"\r\n"
|
||||||
|
>>} = gen_tcp:recv(Socket, 71, 1000),
|
||||||
|
ok.
|
||||||
|
|
||||||
http_upgrade_101(Config) ->
|
http_upgrade_101(Config) ->
|
||||||
doc("A 101 response must be sent on successful upgrade "
|
doc("A 101 response must be sent on successful upgrade "
|
||||||
"to HTTP/2 when using the HTTP Upgrade mechanism. (RFC7540 3.2, RFC7230 6.7)"),
|
"to HTTP/2 when using the HTTP Upgrade mechanism. (RFC7540 3.2, RFC7230 6.7)"),
|
||||||
|
@ -155,10 +165,10 @@ http_upgrade_101(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 101 Switching Protocols\r\n">>} = gen_tcp:recv(Socket, 36, 1000),
|
ok = do_recv_101(Socket),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
http_upgrade_server_preface(Config) ->
|
http_upgrade_server_preface(Config) ->
|
||||||
|
@ -169,10 +179,10 @@ http_upgrade_server_preface(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 101 Switching Protocols\r\n">>} = gen_tcp:recv(Socket, 36, 1000),
|
ok = do_recv_101(Socket),
|
||||||
%% Receive the server preface.
|
%% Receive the server preface.
|
||||||
{ok, << _:24, 4:8, 0:40 >>} = gen_tcp:recv(Socket, 9, 1000),
|
{ok, << _:24, 4:8, 0:40 >>} = gen_tcp:recv(Socket, 9, 1000),
|
||||||
ok.
|
ok.
|
||||||
|
@ -185,15 +195,15 @@ http_upgrade_client_preface_timeout(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 101 Switching Protocols\r\n">>} = gen_tcp:recv(Socket, 36, 1000),
|
ok = do_recv_101(Socket),
|
||||||
%% Receive the server preface.
|
%% Receive the server preface.
|
||||||
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
||||||
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
||||||
%% Do not send the preface. Wait for the server to disconnect us.
|
%% Do not send the preface. Wait for the server to disconnect us.
|
||||||
{error, closed} = gen_tcp:recv(Socket, 3, 6000),
|
{error, closed} = gen_tcp:recv(Socket, 9, 6000),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
http_upgrade_reject_missing_client_preface(Config) ->
|
http_upgrade_reject_missing_client_preface(Config) ->
|
||||||
|
@ -204,17 +214,17 @@ http_upgrade_reject_missing_client_preface(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 101 Switching Protocols\r\n">>} = gen_tcp:recv(Socket, 36, 1000),
|
ok = do_recv_101(Socket),
|
||||||
%% Send a SETTINGS frame directly instead of the proper preface.
|
%% Send a SETTINGS frame directly instead of the proper preface.
|
||||||
ok = gen_tcp:send(Socket, cow_http2:settings(#{})),
|
ok = gen_tcp:send(Socket, cow_http2:settings(#{})),
|
||||||
%% Receive the server preface.
|
%% Receive the server preface.
|
||||||
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
||||||
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
||||||
%% We expect the server to close the connection when it receives a bad preface.
|
%% We expect the server to close the connection when it receives a bad preface.
|
||||||
{error, closed} = gen_tcp:recv(Socket, 3, 1000),
|
{error, closed} = gen_tcp:recv(Socket, 9, 1000),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
http_upgrade_reject_invalid_client_preface(Config) ->
|
http_upgrade_reject_invalid_client_preface(Config) ->
|
||||||
|
@ -225,18 +235,35 @@ http_upgrade_reject_invalid_client_preface(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 101 Switching Protocols\r\n">>} = gen_tcp:recv(Socket, 36, 1000),
|
ok = do_recv_101(Socket),
|
||||||
%% Send a slightly incorrect preface.
|
%% Send a slightly incorrect preface.
|
||||||
ok = gen_tcp:send(Socket, "PRI * HTTP/2.0\r\n\r\nSM: Value\r\n\r\n"),
|
ok = gen_tcp:send(Socket, "PRI * HTTP/2.0\r\n\r\nSM: Value\r\n\r\n"),
|
||||||
%% Receive the server preface.
|
%% Receive the server preface.
|
||||||
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
||||||
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
||||||
%% We expect the server to close the connection when it receives a bad preface.
|
%% We expect the server to close the connection when it receives a bad preface.
|
||||||
{error, closed} = gen_tcp:recv(Socket, 3, 1000),
|
%% The server may however have already started sending the response to the
|
||||||
ok.
|
%% initial HTTP/1.1 request.
|
||||||
|
Received = lists:reverse(lists:foldl(fun(_, Acc) ->
|
||||||
|
case gen_tcp:recv(Socket, 9, 1000) of
|
||||||
|
{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->
|
||||||
|
{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
|
||||||
|
[headers|Acc];
|
||||||
|
{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->
|
||||||
|
{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
|
||||||
|
[data|Acc];
|
||||||
|
{error, _} ->
|
||||||
|
[closed|Acc]
|
||||||
|
end
|
||||||
|
end, [], [1, 2, 3])),
|
||||||
|
case Received of
|
||||||
|
[closed|_] -> ok;
|
||||||
|
[headers, closed|_] -> ok;
|
||||||
|
[headers, data, closed] -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
http_upgrade_reject_missing_client_preface_settings(Config) ->
|
http_upgrade_reject_missing_client_preface_settings(Config) ->
|
||||||
doc("Servers must treat an invalid connection preface as a "
|
doc("Servers must treat an invalid connection preface as a "
|
||||||
|
@ -246,17 +273,17 @@ http_upgrade_reject_missing_client_preface_settings(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 101 Switching Protocols\r\n">>} = gen_tcp:recv(Socket, 36, 1000),
|
ok = do_recv_101(Socket),
|
||||||
%% Send a valid preface sequence except followed by a PING instead of a SETTINGS frame.
|
%% Send a valid preface sequence except followed by a PING instead of a SETTINGS frame.
|
||||||
ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:ping(0)]),
|
ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:ping(0)]),
|
||||||
%% Receive the server preface.
|
%% Receive the server preface.
|
||||||
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
||||||
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
||||||
%% We expect the server to close the connection when it receives a bad preface.
|
%% We expect the server to close the connection when it receives a bad preface.
|
||||||
{error, closed} = gen_tcp:recv(Socket, 3, 1000),
|
{error, closed} = gen_tcp:recv(Socket, 9, 1000),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
http_upgrade_reject_invalid_client_preface_settings(Config) ->
|
http_upgrade_reject_invalid_client_preface_settings(Config) ->
|
||||||
|
@ -267,18 +294,35 @@ http_upgrade_reject_invalid_client_preface_settings(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 101 Switching Protocols\r\n">>} = gen_tcp:recv(Socket, 36, 1000),
|
ok = do_recv_101(Socket),
|
||||||
%% Send a valid preface sequence except followed by a badly formed SETTINGS frame.
|
%% Send a valid preface sequence except followed by a badly formed SETTINGS frame.
|
||||||
ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", << 0:24, 4:8, 0:9, 1:31 >>]),
|
ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", << 0:24, 4:8, 0:9, 1:31 >>]),
|
||||||
%% Receive the server preface.
|
%% Receive the server preface.
|
||||||
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
||||||
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
||||||
%% We expect the server to close the connection when it receives a bad preface.
|
%% We expect the server to close the connection when it receives a bad preface.
|
||||||
{error, closed} = gen_tcp:recv(Socket, 3, 1000),
|
%% The server may however have already started sending the response to the
|
||||||
ok.
|
%% initial HTTP/1.1 request.
|
||||||
|
Received = lists:reverse(lists:foldl(fun(_, Acc) ->
|
||||||
|
case gen_tcp:recv(Socket, 9, 1000) of
|
||||||
|
{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->
|
||||||
|
{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
|
||||||
|
[headers|Acc];
|
||||||
|
{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->
|
||||||
|
{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
|
||||||
|
[data|Acc];
|
||||||
|
{error, _} ->
|
||||||
|
[closed|Acc]
|
||||||
|
end
|
||||||
|
end, [], [1, 2, 3])),
|
||||||
|
case Received of
|
||||||
|
[closed|_] -> ok;
|
||||||
|
[headers, closed|_] -> ok;
|
||||||
|
[headers, data, closed] -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
http_upgrade_accept_client_preface_empty_settings(Config) ->
|
http_upgrade_accept_client_preface_empty_settings(Config) ->
|
||||||
doc("The SETTINGS frame in the client preface may be empty. (RFC7540 3.2, RFC7540 3.5)"),
|
doc("The SETTINGS frame in the client preface may be empty. (RFC7540 3.2, RFC7540 3.5)"),
|
||||||
|
@ -287,10 +331,10 @@ http_upgrade_accept_client_preface_empty_settings(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 101 Switching Protocols\r\n">>} = gen_tcp:recv(Socket, 36, 1000),
|
ok = do_recv_101(Socket),
|
||||||
%% Send a valid preface sequence except followed by an empty SETTINGS frame.
|
%% Send a valid preface sequence except followed by an empty SETTINGS frame.
|
||||||
ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
|
ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
|
||||||
%% Receive the server preface.
|
%% Receive the server preface.
|
||||||
|
@ -307,10 +351,10 @@ http_upgrade_client_preface_settings_ack_timeout(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 101 Switching Protocols\r\n">>} = gen_tcp:recv(Socket, 36, 1000),
|
ok = do_recv_101(Socket),
|
||||||
%% Send a valid preface.
|
%% Send a valid preface.
|
||||||
ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
|
ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
|
||||||
%% Receive the server preface.
|
%% Receive the server preface.
|
||||||
|
@ -324,6 +368,8 @@ http_upgrade_client_preface_settings_ack_timeout(Config) ->
|
||||||
|
|
||||||
%% @todo We need a successful test with actual options in HTTP2-Settings.
|
%% @todo We need a successful test with actual options in HTTP2-Settings.
|
||||||
|
|
||||||
|
%% @todo We need to test an upgrade with a request body (small and too large).
|
||||||
|
|
||||||
%% @todo Also assigned default priority values but not sure how to test that.
|
%% @todo Also assigned default priority values but not sure how to test that.
|
||||||
http_upgrade_response(Config) ->
|
http_upgrade_response(Config) ->
|
||||||
doc("A response must be sent to the initial HTTP/1.1 request "
|
doc("A response must be sent to the initial HTTP/1.1 request "
|
||||||
|
@ -334,10 +380,10 @@ http_upgrade_response(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 101 Switching Protocols\r\n">>} = gen_tcp:recv(Socket, 36, 1000),
|
ok = do_recv_101(Socket),
|
||||||
%% Send a valid preface.
|
%% Send a valid preface.
|
||||||
%% @todo Use non-empty SETTINGS here. Just because.
|
%% @todo Use non-empty SETTINGS here. Just because.
|
||||||
ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
|
ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
|
||||||
|
@ -346,12 +392,24 @@ http_upgrade_response(Config) ->
|
||||||
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
||||||
%% Send the SETTINGS ack.
|
%% Send the SETTINGS ack.
|
||||||
ok = gen_tcp:send(Socket, cow_http2:settings_ack()),
|
ok = gen_tcp:send(Socket, cow_http2:settings_ack()),
|
||||||
%% Receive the SETTINGS ack.
|
%% Receive the SETTINGS ack, and the response HEADERS and DATA (streamid 1).
|
||||||
%% @todo It's possible that we receive the response before the SETTINGS ack.
|
Received = lists:reverse(lists:foldl(fun(_, Acc) ->
|
||||||
{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
|
case gen_tcp:recv(Socket, 9, 1000) of
|
||||||
%% Receive the response to the original request. It uses streamid 1.
|
{ok, << 0:24, 4:8, 1:8, 0:32 >>} ->
|
||||||
{ok, << _:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
|
[settings_ack|Acc];
|
||||||
ok.
|
{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->
|
||||||
|
{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
|
||||||
|
[headers|Acc];
|
||||||
|
{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->
|
||||||
|
{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
|
||||||
|
[data|Acc]
|
||||||
|
end
|
||||||
|
end, [], [1, 2, 3])),
|
||||||
|
case Received of
|
||||||
|
[settings_ack, headers, data] -> ok;
|
||||||
|
[headers, settings_ack, data] -> ok;
|
||||||
|
[headers, data, settings_ack] -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
http_upgrade_response_half_closed(Config) ->
|
http_upgrade_response_half_closed(Config) ->
|
||||||
doc("The stream for the initial HTTP/1.1 request is half-closed. (RFC7540 3.2)"),
|
doc("The stream for the initial HTTP/1.1 request is half-closed. (RFC7540 3.2)"),
|
||||||
|
@ -361,10 +419,10 @@ http_upgrade_response_half_closed(Config) ->
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Host: localhost\r\n"
|
"Host: localhost\r\n"
|
||||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
"Upgrade: h2\r\n"
|
"Upgrade: h2c\r\n"
|
||||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
"\r\n"]),
|
"\r\n"]),
|
||||||
{ok, <<"HTTP/1.1 101 Switching Protocols\r\n">>} = gen_tcp:recv(Socket, 36, 1000),
|
ok = do_recv_101(Socket),
|
||||||
%% Send a valid preface.
|
%% Send a valid preface.
|
||||||
ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
|
ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
|
||||||
%% Send more data on the stream to trigger an error.
|
%% Send more data on the stream to trigger an error.
|
||||||
|
@ -567,7 +625,7 @@ prior_knowledge_reject_invalid_client_preface(Config) ->
|
||||||
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
||||||
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
||||||
%% We expect the server to close the connection when it receives a bad preface.
|
%% We expect the server to close the connection when it receives a bad preface.
|
||||||
{error, closed} = gen_tcp:recv(Socket, 3, 1000),
|
{error, closed} = gen_tcp:recv(Socket, 9, 1000),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
prior_knowledge_reject_missing_client_preface_settings(Config) ->
|
prior_knowledge_reject_missing_client_preface_settings(Config) ->
|
||||||
|
@ -580,7 +638,7 @@ prior_knowledge_reject_missing_client_preface_settings(Config) ->
|
||||||
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
||||||
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
||||||
%% We expect the server to close the connection when it receives a bad preface.
|
%% We expect the server to close the connection when it receives a bad preface.
|
||||||
{error, closed} = gen_tcp:recv(Socket, 3, 1000),
|
{error, closed} = gen_tcp:recv(Socket, 9, 1000),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
prior_knowledge_reject_invalid_client_preface_settings(Config) ->
|
prior_knowledge_reject_invalid_client_preface_settings(Config) ->
|
||||||
|
@ -593,7 +651,7 @@ prior_knowledge_reject_invalid_client_preface_settings(Config) ->
|
||||||
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
|
||||||
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
|
||||||
%% We expect the server to close the connection when it receives a bad preface.
|
%% We expect the server to close the connection when it receives a bad preface.
|
||||||
{error, closed} = gen_tcp:recv(Socket, 3, 1000),
|
{error, closed} = gen_tcp:recv(Socket, 9, 1000),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
prior_knowledge_accept_client_preface_empty_settings(Config) ->
|
prior_knowledge_accept_client_preface_empty_settings(Config) ->
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue