mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Provide better control over which HTTP protocols are enabled
Over cleartext TCP the `protocols` option lists the enabled protocols. The default is to allow both HTTP/1.1 and HTTP/2. Over TLS the default protocol to use when ALPN is not used can now be configured via the `alpn_default_protocol` option. Performing an HTTP/1.1 upgrade to HTTP/2 over TLS is now rejected with an error as connecting to HTTP/2 over TLS requires the use of ALPN (or that HTTP/2 be the default when connecting over TLS).
This commit is contained in:
parent
971684788d
commit
053e233c56
8 changed files with 160 additions and 28 deletions
|
@ -18,6 +18,7 @@ as a Ranch protocol.
|
||||||
----
|
----
|
||||||
opts() :: #{
|
opts() :: #{
|
||||||
active_n => pos_integer(),
|
active_n => pos_integer(),
|
||||||
|
alpn_default_protocol => http | http2,
|
||||||
chunked => boolean(),
|
chunked => boolean(),
|
||||||
connection_type => worker | supervisor,
|
connection_type => worker | supervisor,
|
||||||
dynamic_buffer => false | {pos_integer(), pos_integer()},
|
dynamic_buffer => false | {pos_integer(), pos_integer()},
|
||||||
|
@ -36,6 +37,7 @@ opts() :: #{
|
||||||
max_method_length => non_neg_integer(),
|
max_method_length => non_neg_integer(),
|
||||||
max_request_line_length => non_neg_integer(),
|
max_request_line_length => non_neg_integer(),
|
||||||
max_skip_body_length => non_neg_integer(),
|
max_skip_body_length => non_neg_integer(),
|
||||||
|
protocols => [http | http2],
|
||||||
proxy_header => boolean(),
|
proxy_header => boolean(),
|
||||||
request_timeout => timeout(),
|
request_timeout => timeout(),
|
||||||
reset_idle_timeout_on_send => boolean(),
|
reset_idle_timeout_on_send => boolean(),
|
||||||
|
@ -63,6 +65,12 @@ values reduce the number of times Cowboy need to request more
|
||||||
packets from the port driver at the expense of potentially
|
packets from the port driver at the expense of potentially
|
||||||
higher memory being used.
|
higher memory being used.
|
||||||
|
|
||||||
|
alpn_default_protocol (http)::
|
||||||
|
|
||||||
|
Default protocol to use when the client connects over TLS
|
||||||
|
without ALPN. Can be set to `http2` to disable HTTP/1.1
|
||||||
|
entirely.
|
||||||
|
|
||||||
chunked (true)::
|
chunked (true)::
|
||||||
|
|
||||||
Whether chunked transfer-encoding is enabled for HTTP/1.1 connections.
|
Whether chunked transfer-encoding is enabled for HTTP/1.1 connections.
|
||||||
|
@ -156,6 +164,13 @@ max_skip_body_length (1000000)::
|
||||||
Maximum length Cowboy is willing to skip when the user code did not read the body fully.
|
Maximum length Cowboy is willing to skip when the user code did not read the body fully.
|
||||||
When the remaining length is too large or unknown Cowboy will close the connection.
|
When the remaining length is too large or unknown Cowboy will close the connection.
|
||||||
|
|
||||||
|
protocols ([http2, http])::
|
||||||
|
|
||||||
|
Protocols that may be used when the client connects over
|
||||||
|
cleartext TCP. The default is to allow both HTTP/1.1 and
|
||||||
|
HTTP/2. HTTP/1.1 and HTTP/2 can be disabled entirely by
|
||||||
|
omitting them from the list.
|
||||||
|
|
||||||
proxy_header (false)::
|
proxy_header (false)::
|
||||||
|
|
||||||
Whether incoming connections have a PROXY protocol header. The
|
Whether incoming connections have a PROXY protocol header. The
|
||||||
|
|
|
@ -18,6 +18,7 @@ as a Ranch protocol.
|
||||||
----
|
----
|
||||||
opts() :: #{
|
opts() :: #{
|
||||||
active_n => pos_integer(),
|
active_n => pos_integer(),
|
||||||
|
alpn_default_protocol => http | http2,
|
||||||
connection_type => worker | supervisor,
|
connection_type => worker | supervisor,
|
||||||
connection_window_margin_size => 0..16#7fffffff,
|
connection_window_margin_size => 0..16#7fffffff,
|
||||||
connection_window_update_threshold => 0..16#7fffffff,
|
connection_window_update_threshold => 0..16#7fffffff,
|
||||||
|
@ -46,6 +47,7 @@ opts() :: #{
|
||||||
max_stream_buffer_size => non_neg_integer(),
|
max_stream_buffer_size => non_neg_integer(),
|
||||||
max_stream_window_size => 0..16#7fffffff,
|
max_stream_window_size => 0..16#7fffffff,
|
||||||
preface_timeout => timeout(),
|
preface_timeout => timeout(),
|
||||||
|
protocols => [http | http2],
|
||||||
proxy_header => boolean(),
|
proxy_header => boolean(),
|
||||||
reset_idle_timeout_on_send => boolean(),
|
reset_idle_timeout_on_send => boolean(),
|
||||||
sendfile => boolean(),
|
sendfile => boolean(),
|
||||||
|
@ -76,6 +78,12 @@ values reduce the number of times Cowboy need to request more
|
||||||
packets from the port driver at the expense of potentially
|
packets from the port driver at the expense of potentially
|
||||||
higher memory being used.
|
higher memory being used.
|
||||||
|
|
||||||
|
alpn_default_protocol (http)::
|
||||||
|
|
||||||
|
Default protocol to use when the client connects over TLS
|
||||||
|
without ALPN. Can be set to `http2` to disable HTTP/1.1
|
||||||
|
entirely.
|
||||||
|
|
||||||
connection_type (supervisor)::
|
connection_type (supervisor)::
|
||||||
|
|
||||||
Whether the connection process also acts as a supervisor.
|
Whether the connection process also acts as a supervisor.
|
||||||
|
@ -259,6 +267,13 @@ preface_timeout (5000)::
|
||||||
|
|
||||||
Time in ms Cowboy is willing to wait for the connection preface.
|
Time in ms Cowboy is willing to wait for the connection preface.
|
||||||
|
|
||||||
|
protocols ([http2, http])::
|
||||||
|
|
||||||
|
Protocols that may be used when the client connects over
|
||||||
|
cleartext TCP. The default is to allow both HTTP/1.1 and
|
||||||
|
HTTP/2. HTTP/1.1 and HTTP/2 can be disabled entirely by
|
||||||
|
omitting them from the list.
|
||||||
|
|
||||||
proxy_header (false)::
|
proxy_header (false)::
|
||||||
|
|
||||||
Whether incoming connections have a PROXY protocol header. The
|
Whether incoming connections have a PROXY protocol header. The
|
||||||
|
|
|
@ -36,10 +36,6 @@ connection_process(Parent, Ref, Transport, Opts) ->
|
||||||
ProxyInfo = get_proxy_info(Ref, Opts),
|
ProxyInfo = get_proxy_info(Ref, Opts),
|
||||||
{ok, Socket} = ranch:handshake(Ref),
|
{ok, Socket} = ranch:handshake(Ref),
|
||||||
%% Use cowboy_http2 directly only when 'http' is missing.
|
%% Use cowboy_http2 directly only when 'http' is missing.
|
||||||
%% Otherwise switch to cowboy_http2 from cowboy_http.
|
|
||||||
%%
|
|
||||||
%% @todo Extend this option to cowboy_tls and allow disabling
|
|
||||||
%% the switch to cowboy_http2 in cowboy_http. Also document it.
|
|
||||||
Protocol = case maps:get(protocols, Opts, [http2, http]) of
|
Protocol = case maps:get(protocols, Opts, [http2, http]) of
|
||||||
[http2] -> cowboy_http2;
|
[http2] -> cowboy_http2;
|
||||||
[_|_] -> cowboy_http
|
[_|_] -> cowboy_http
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
-type opts() :: #{
|
-type opts() :: #{
|
||||||
active_n => pos_integer(),
|
active_n => pos_integer(),
|
||||||
|
alpn_default_protocol => http | http2,
|
||||||
chunked => boolean(),
|
chunked => boolean(),
|
||||||
compress_buffering => boolean(),
|
compress_buffering => boolean(),
|
||||||
compress_threshold => non_neg_integer(),
|
compress_threshold => non_neg_integer(),
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
metrics_req_filter => fun((cowboy_req:req()) -> map()),
|
metrics_req_filter => fun((cowboy_req:req()) -> map()),
|
||||||
metrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()),
|
metrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()),
|
||||||
middlewares => [module()],
|
middlewares => [module()],
|
||||||
|
protocols => [http | http2],
|
||||||
proxy_header => boolean(),
|
proxy_header => boolean(),
|
||||||
request_timeout => timeout(),
|
request_timeout => timeout(),
|
||||||
reset_idle_timeout_on_send => boolean(),
|
reset_idle_timeout_on_send => boolean(),
|
||||||
|
@ -511,8 +513,13 @@ parse_request(Buffer, State=#state{opts=Opts, in_streamid=InStreamID}, EmptyLine
|
||||||
'The TRACE method is currently not implemented. (RFC7231 4.3.8)'});
|
'The TRACE method is currently not implemented. (RFC7231 4.3.8)'});
|
||||||
%% 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.
|
case lists:member(http2, maps:get(protocols, Opts, [http2, http])) of
|
||||||
http2_upgrade(State, Buffer);
|
true ->
|
||||||
|
http2_upgrade(State, Buffer);
|
||||||
|
false ->
|
||||||
|
error_terminate(501, State, {connection_error, no_error,
|
||||||
|
'Prior knowledge upgrade to HTTP/2 is disabled by configuration.'})
|
||||||
|
end;
|
||||||
_ ->
|
_ ->
|
||||||
parse_method(Buffer, State, <<>>,
|
parse_method(Buffer, State, <<>>,
|
||||||
maps:get(max_method_length, Opts, 32))
|
maps:get(max_method_length, Opts, 32))
|
||||||
|
@ -800,7 +807,7 @@ default_port(_) -> 80.
|
||||||
%% End of request parsing.
|
%% End of request parsing.
|
||||||
|
|
||||||
request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, sock=Sock, cert=Cert,
|
request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, sock=Sock, cert=Cert,
|
||||||
proxy_header=ProxyHeader, in_streamid=StreamID, in_state=
|
opts=Opts, proxy_header=ProxyHeader, in_streamid=StreamID, in_state=
|
||||||
PS=#ps_header{method=Method, path=Path, qs=Qs, version=Version}},
|
PS=#ps_header{method=Method, path=Path, qs=Qs, version=Version}},
|
||||||
Headers, Host, Port) ->
|
Headers, Host, Port) ->
|
||||||
Scheme = case Transport:secure() of
|
Scheme = case Transport:secure() of
|
||||||
|
@ -864,7 +871,7 @@ request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, sock=Sock
|
||||||
undefined -> Req0;
|
undefined -> Req0;
|
||||||
_ -> Req0#{proxy_header => ProxyHeader}
|
_ -> Req0#{proxy_header => ProxyHeader}
|
||||||
end,
|
end,
|
||||||
case is_http2_upgrade(Headers, Version) of
|
case is_http2_upgrade(Headers, Version, Opts) of
|
||||||
false ->
|
false ->
|
||||||
State = case HasBody of
|
State = case HasBody of
|
||||||
true ->
|
true ->
|
||||||
|
@ -886,12 +893,13 @@ request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, sock=Sock
|
||||||
|
|
||||||
%% HTTP/2 upgrade.
|
%% HTTP/2 upgrade.
|
||||||
|
|
||||||
%% @todo We must not upgrade to h2c over a TLS connection.
|
|
||||||
is_http2_upgrade(#{<<"connection">> := Conn, <<"upgrade">> := Upgrade,
|
is_http2_upgrade(#{<<"connection">> := Conn, <<"upgrade">> := Upgrade,
|
||||||
<<"http2-settings">> := HTTP2Settings}, 'HTTP/1.1') ->
|
<<"http2-settings">> := HTTP2Settings}, 'HTTP/1.1', Opts) ->
|
||||||
Conns = cow_http_hd:parse_connection(Conn),
|
Conns = cow_http_hd:parse_connection(Conn),
|
||||||
case {lists:member(<<"upgrade">>, Conns), lists:member(<<"http2-settings">>, Conns)} of
|
case lists:member(<<"upgrade">>, Conns)
|
||||||
{true, true} ->
|
andalso lists:member(<<"http2-settings">>, Conns)
|
||||||
|
andalso lists:member(http2, maps:get(protocols, Opts, [http2, http])) of
|
||||||
|
true ->
|
||||||
Protocols = cow_http_hd:parse_upgrade(Upgrade),
|
Protocols = cow_http_hd:parse_upgrade(Upgrade),
|
||||||
case lists:member(<<"h2c">>, Protocols) of
|
case lists:member(<<"h2c">>, Protocols) of
|
||||||
true ->
|
true ->
|
||||||
|
@ -902,7 +910,7 @@ is_http2_upgrade(#{<<"connection">> := Conn, <<"upgrade">> := Upgrade,
|
||||||
_ ->
|
_ ->
|
||||||
false
|
false
|
||||||
end;
|
end;
|
||||||
is_http2_upgrade(_, _) ->
|
is_http2_upgrade(_, _, _) ->
|
||||||
false.
|
false.
|
||||||
|
|
||||||
%% Prior knowledge upgrade, without an HTTP/1.1 request.
|
%% Prior knowledge upgrade, without an HTTP/1.1 request.
|
||||||
|
@ -922,18 +930,24 @@ http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Tran
|
||||||
http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Transport,
|
http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Transport,
|
||||||
proxy_header=ProxyHeader, peer=Peer, sock=Sock, cert=Cert},
|
proxy_header=ProxyHeader, peer=Peer, sock=Sock, cert=Cert},
|
||||||
Buffer, HTTP2Settings, Req) ->
|
Buffer, HTTP2Settings, Req) ->
|
||||||
%% @todo
|
case Transport:secure() of
|
||||||
%% However if the client sent a body, we need to read the body in full
|
false ->
|
||||||
%% and if we can't do that, return a 413 response. Some options are in order.
|
%% @todo
|
||||||
%% Always half-closed stream coming from this side.
|
%% However if the client sent a body, we need to read the body in full
|
||||||
try cow_http_hd:parse_http2_settings(HTTP2Settings) of
|
%% and if we can't do that, return a 413 response. Some options are in order.
|
||||||
Settings ->
|
%% Always half-closed stream coming from this side.
|
||||||
_ = cancel_timeout(State),
|
try cow_http_hd:parse_http2_settings(HTTP2Settings) of
|
||||||
cowboy_http2:init(Parent, Ref, Socket, Transport, ProxyHeader,
|
Settings ->
|
||||||
opts_for_upgrade(State), Peer, Sock, Cert, Buffer, Settings, Req)
|
_ = cancel_timeout(State),
|
||||||
catch _:_ ->
|
cowboy_http2:init(Parent, Ref, Socket, Transport, ProxyHeader,
|
||||||
error_terminate(400, State, {connection_error, protocol_error,
|
opts_for_upgrade(State), Peer, Sock, Cert, Buffer, Settings, Req)
|
||||||
'The HTTP2-Settings header must contain a base64 SETTINGS payload. (RFC7540 3.2, RFC7540 3.2.1)'})
|
catch _:_ ->
|
||||||
|
error_terminate(400, State, {connection_error, protocol_error,
|
||||||
|
'The HTTP2-Settings header must contain a base64 SETTINGS payload. (RFC7540 3.2, RFC7540 3.2.1)'})
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
|
error_terminate(400, State, {connection_error, protocol_error,
|
||||||
|
'Clients that support HTTP/2 over TLS MUST use ALPN. (RFC7540 3.4)'})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
opts_for_upgrade(#state{opts=Opts, dynamic_buffer_size=false}) ->
|
opts_for_upgrade(#state{opts=Opts, dynamic_buffer_size=false}) ->
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
-type opts() :: #{
|
-type opts() :: #{
|
||||||
active_n => pos_integer(),
|
active_n => pos_integer(),
|
||||||
|
alpn_default_protocol => http | http2,
|
||||||
compress_buffering => boolean(),
|
compress_buffering => boolean(),
|
||||||
compress_threshold => non_neg_integer(),
|
compress_threshold => non_neg_integer(),
|
||||||
connection_type => worker | supervisor,
|
connection_type => worker | supervisor,
|
||||||
|
@ -62,6 +63,7 @@
|
||||||
metrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()),
|
metrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()),
|
||||||
middlewares => [module()],
|
middlewares => [module()],
|
||||||
preface_timeout => timeout(),
|
preface_timeout => timeout(),
|
||||||
|
protocols => [http | http2],
|
||||||
proxy_header => boolean(),
|
proxy_header => boolean(),
|
||||||
reset_idle_timeout_on_send => boolean(),
|
reset_idle_timeout_on_send => boolean(),
|
||||||
sendfile => boolean(),
|
sendfile => boolean(),
|
||||||
|
|
|
@ -39,7 +39,11 @@ connection_process(Parent, Ref, Transport, Opts) ->
|
||||||
{ok, <<"h2">>} ->
|
{ok, <<"h2">>} ->
|
||||||
init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, cowboy_http2);
|
init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, cowboy_http2);
|
||||||
_ -> %% http/1.1 or no protocol negotiated.
|
_ -> %% http/1.1 or no protocol negotiated.
|
||||||
init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, cowboy_http)
|
Protocol = case maps:get(alpn_default_protocol, Opts, http) of
|
||||||
|
http -> cowboy_http;
|
||||||
|
http2 -> cowboy_http2
|
||||||
|
end,
|
||||||
|
init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, Protocol)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, Protocol) ->
|
init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, Protocol) ->
|
||||||
|
|
|
@ -199,6 +199,73 @@ do_chunked_body(ChunkSize0, Data, Acc) ->
|
||||||
do_chunked_body(ChunkSize, Rest,
|
do_chunked_body(ChunkSize, Rest,
|
||||||
[iolist_to_binary(cow_http_te:chunk(Chunk))|Acc]).
|
[iolist_to_binary(cow_http_te:chunk(Chunk))|Acc]).
|
||||||
|
|
||||||
|
disable_http1_tls(Config) ->
|
||||||
|
doc("Ensure that we can disable HTTP/1.1 over TLS (force HTTP/2)."),
|
||||||
|
TlsOpts = ct_helper:get_certs_from_ets(),
|
||||||
|
{ok, _} = cowboy:start_tls(?FUNCTION_NAME, TlsOpts ++ [{port, 0}], #{
|
||||||
|
env => #{dispatch => init_dispatch(Config)},
|
||||||
|
alpn_default_protocol => http2
|
||||||
|
}),
|
||||||
|
Port = ranch:get_port(?FUNCTION_NAME),
|
||||||
|
try
|
||||||
|
{ok, Socket} = ssl:connect("localhost", Port,
|
||||||
|
[binary, {active, false}|TlsOpts]),
|
||||||
|
%% ALPN was not negotiated but we're still over HTTP/2.
|
||||||
|
{error, protocol_not_negotiated} = ssl:negotiated_protocol(Socket),
|
||||||
|
%% Send a valid preface.
|
||||||
|
ok = ssl:send(Socket, [
|
||||||
|
"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n",
|
||||||
|
cow_http2:settings(#{})]),
|
||||||
|
%% Receive the server preface.
|
||||||
|
{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),
|
||||||
|
{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),
|
||||||
|
ok
|
||||||
|
after
|
||||||
|
cowboy:stop_listener(?FUNCTION_NAME)
|
||||||
|
end.
|
||||||
|
|
||||||
|
disable_http2_prior_knowledge(Config) ->
|
||||||
|
doc("Ensure that we can disable prior knowledge HTTP/2 upgrade."),
|
||||||
|
{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{
|
||||||
|
env => #{dispatch => init_dispatch(Config)},
|
||||||
|
protocols => [http]
|
||||||
|
}),
|
||||||
|
Port = ranch:get_port(?FUNCTION_NAME),
|
||||||
|
try
|
||||||
|
{ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}]),
|
||||||
|
%% 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, <<"HTTP/1.1 501">>} = gen_tcp:recv(Socket, 12, 1000),
|
||||||
|
ok
|
||||||
|
after
|
||||||
|
cowboy:stop_listener(?FUNCTION_NAME)
|
||||||
|
end.
|
||||||
|
|
||||||
|
disable_http2_upgrade(Config) ->
|
||||||
|
doc("Ensure that we can disable HTTP/1.1 upgrade to HTTP/2."),
|
||||||
|
{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{
|
||||||
|
env => #{dispatch => init_dispatch(Config)},
|
||||||
|
protocols => [http]
|
||||||
|
}),
|
||||||
|
Port = ranch:get_port(?FUNCTION_NAME),
|
||||||
|
try
|
||||||
|
{ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}]),
|
||||||
|
%% Send a valid preface.
|
||||||
|
ok = gen_tcp:send(Socket, [
|
||||||
|
"GET / HTTP/1.1\r\n"
|
||||||
|
"Host: localhost\r\n"
|
||||||
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
|
"Upgrade: h2c\r\n"
|
||||||
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
|
"\r\n"]),
|
||||||
|
{ok, <<"HTTP/1.1 200">>} = gen_tcp:recv(Socket, 12, 1000),
|
||||||
|
ok
|
||||||
|
after
|
||||||
|
cowboy:stop_listener(?FUNCTION_NAME)
|
||||||
|
end.
|
||||||
|
|
||||||
hibernate(Config) ->
|
hibernate(Config) ->
|
||||||
doc("Ensure that we can enable hibernation for HTTP/1.1 connections."),
|
doc("Ensure that we can enable hibernation for HTTP/1.1 connections."),
|
||||||
{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{
|
{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{
|
||||||
|
|
|
@ -35,8 +35,9 @@ all() -> [{group, clear}, {group, tls}].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
Tests = ct_helper:all(?MODULE),
|
Tests = ct_helper:all(?MODULE),
|
||||||
Clear = [T || T <- Tests, lists:sublist(atom_to_list(T), 4) =/= "alpn"] -- [prior_knowledge_reject_tls],
|
RejectTLS = [http_upgrade_reject_tls, prior_knowledge_reject_tls],
|
||||||
TLS = [T || T <- Tests, lists:sublist(atom_to_list(T), 4) =:= "alpn"] ++ [prior_knowledge_reject_tls],
|
Clear = [T || T <- Tests, lists:sublist(atom_to_list(T), 4) =/= "alpn"] -- RejectTLS,
|
||||||
|
TLS = [T || T <- Tests, lists:sublist(atom_to_list(T), 4) =:= "alpn"] ++ RejectTLS,
|
||||||
[{clear, [parallel], Clear}, {tls, [parallel], TLS}].
|
[{clear, [parallel], Clear}, {tls, [parallel], TLS}].
|
||||||
|
|
||||||
init_per_group(Name = clear, Config) ->
|
init_per_group(Name = clear, Config) ->
|
||||||
|
@ -68,6 +69,24 @@ init_routes(_) -> [
|
||||||
|
|
||||||
%% Starting HTTP/2 for "http" URIs.
|
%% Starting HTTP/2 for "http" URIs.
|
||||||
|
|
||||||
|
http_upgrade_reject_tls(Config) ->
|
||||||
|
doc("Implementations that support HTTP/2 over TLS must use ALPN. (RFC7540 3.4)"),
|
||||||
|
TlsOpts = ct_helper:get_certs_from_ets(),
|
||||||
|
{ok, Socket} = ssl:connect("localhost", config(port, Config),
|
||||||
|
[binary, {active, false}|TlsOpts]),
|
||||||
|
%% Send a valid preface.
|
||||||
|
ok = ssl:send(Socket, [
|
||||||
|
"GET / HTTP/1.1\r\n"
|
||||||
|
"Host: localhost\r\n"
|
||||||
|
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||||
|
"Upgrade: h2c\r\n"
|
||||||
|
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||||
|
"\r\n"]),
|
||||||
|
%% We expect the server to send an HTTP 400 error
|
||||||
|
%% when trying to use HTTP/2 without going through ALPN negotiation.
|
||||||
|
{ok, <<"HTTP/1.1 400">>} = ssl:recv(Socket, 12, 1000),
|
||||||
|
ok.
|
||||||
|
|
||||||
http_upgrade_ignore_h2(Config) ->
|
http_upgrade_ignore_h2(Config) ->
|
||||||
doc("An h2 token in an Upgrade field must be ignored. (RFC7540 3.2)"),
|
doc("An h2 token in an Upgrade field must be ignored. (RFC7540 3.2)"),
|
||||||
{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
|
{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue