2025-02-17 15:00:02 +01:00
|
|
|
%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>
|
2015-06-11 17:04:21 +02:00
|
|
|
%%
|
|
|
|
%% Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
%% purpose with or without fee is hereby granted, provided that the above
|
|
|
|
%% copyright notice and this permission notice appear in all copies.
|
|
|
|
%%
|
|
|
|
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
|
|
|
-module(cowboy_http2).
|
|
|
|
|
2018-10-30 23:30:05 +01:00
|
|
|
-export([init/6]).
|
|
|
|
-export([init/10]).
|
|
|
|
-export([init/12]).
|
2025-02-07 16:57:58 +01:00
|
|
|
-export([loop/2]).
|
2015-06-11 17:04:21 +02:00
|
|
|
|
|
|
|
-export([system_continue/3]).
|
|
|
|
-export([system_terminate/4]).
|
|
|
|
-export([system_code_change/4]).
|
|
|
|
|
2017-05-05 13:48:25 +02:00
|
|
|
-type opts() :: #{
|
2019-12-04 11:17:34 +01:00
|
|
|
active_n => pos_integer(),
|
2025-02-10 15:26:00 +01:00
|
|
|
alpn_default_protocol => http | http2,
|
2018-11-15 10:11:36 +01:00
|
|
|
compress_buffering => boolean(),
|
2018-02-17 16:43:46 +03:00
|
|
|
compress_threshold => non_neg_integer(),
|
2017-05-05 13:48:25 +02:00
|
|
|
connection_type => worker | supervisor,
|
2019-09-02 14:48:28 +02:00
|
|
|
connection_window_margin_size => 0..16#7fffffff,
|
|
|
|
connection_window_update_threshold => 0..16#7fffffff,
|
Implement dynamic socket buffer sizes
Cowboy will set the socket's buffer size dynamically to
better fit the current workload. When the incoming data
is small, a low buffer size reduces the memory footprint
and improves responsiveness and therefore performance.
When the incoming data is large, such as large HTTP
request bodies, a larger buffer size helps us avoid
doing too many binary appends and related allocations.
Setting a large buffer size for all use cases is
sub-optimal because allocating more than needed
necessarily results in a performance hit (not just
increased memory usage).
By default Cowboy starts with a buffer size of 8192 bytes.
It then doubles or halves the buffer size depending on
the size of the data it receives from the socket. It
stops decreasing at 8192 and increasing at 131072 by
default.
To keep track of the size of the incoming data Cowboy
maintains a moving average. It allows Cowboy to avoid
changing the buffer too often but still react quickly
when necessary. Cowboy will increase the buffer size
when the moving average is above 90% of the current
buffer size, and decrease when the moving average is
below 40% of the current buffer size.
The current buffer size and moving average are
propagated when switching protocols. The dynamic buffer
is implemented in HTTP/1, HTTP/2 and HTTP/1 Websocket.
HTTP/2 Websocket has it disabled because it doesn't
interact directly with the socket; in that case it
is HTTP/2 that has a dynamic buffer.
The dynamic buffer provides a very large performance improvement
in many scenarios, at minimal cost for others. Because it largely
depend on the underlying protocol the improvements are no all equal.
TLS and compression also impact the results.
The improvement when reading a large request body, with the
requests repeated in a fast loop are:
* HTTP: 6x to 20x faster
* HTTPS: 2x to 6x faster
* H2: 4x to 5x faster
* H2C: 20x to 40x faster
I am not sure why H2C's performance was so bad, especially compared
to H2, when using default buffer sizes. Dynamic buffers make H2C a
lot more viable with default settings.
The performance impact on "hello world" type requests is minimal,
it goes from -5% to +5% roughly.
Websocket improvements vary again depending on the protocol, but
also depending on whether compression is enabled:
* HTTP echo: roughly 2x faster
* HTTP send: roughly 4x faster
* H2C echo: roughly 2x faster
* H2C send: 3x to 4x faster
In the echo test we reply back, and Gun doesn't have the dynamic
buffer optimisation, so that probably explains the x2 difference.
With compression however there isn't much improvement. The results
are roughly within -10% to +10% of each other. Zlib compression
seems to be a bottleneck, or at least to modify the performance
profile to such an extent that the size of the buffer does not
matter. This happens to randomly generated binary data as well
so it is probably not caused by the test data.
2025-02-03 15:36:16 +01:00
|
|
|
dynamic_buffer => false | {pos_integer(), pos_integer()},
|
|
|
|
dynamic_buffer_initial_average => non_neg_integer(),
|
|
|
|
dynamic_buffer_initial_size => pos_integer(),
|
2018-04-23 14:34:53 +02:00
|
|
|
enable_connect_protocol => boolean(),
|
2017-05-05 13:48:25 +02:00
|
|
|
env => cowboy_middleware:env(),
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
goaway_initial_timeout => timeout(),
|
|
|
|
goaway_complete_timeout => timeout(),
|
2025-02-07 16:57:58 +01:00
|
|
|
hibernate => boolean(),
|
2018-11-16 16:30:57 +01:00
|
|
|
idle_timeout => timeout(),
|
2017-05-05 13:48:25 +02:00
|
|
|
inactivity_timeout => timeout(),
|
2018-04-26 22:08:05 +02:00
|
|
|
initial_connection_window_size => 65535..16#7fffffff,
|
|
|
|
initial_stream_window_size => 0..16#7fffffff,
|
2020-04-06 14:50:35 +02:00
|
|
|
linger_timeout => timeout(),
|
2018-07-09 10:08:48 +02:00
|
|
|
logger => module(),
|
2018-04-25 21:32:58 +02:00
|
|
|
max_concurrent_streams => non_neg_integer() | infinity,
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
max_connection_buffer_size => non_neg_integer(),
|
2019-09-02 14:48:28 +02:00
|
|
|
max_connection_window_size => 0..16#7fffffff,
|
2018-04-25 16:55:52 +02:00
|
|
|
max_decode_table_size => non_neg_integer(),
|
|
|
|
max_encode_table_size => non_neg_integer(),
|
2024-03-14 12:36:54 +01:00
|
|
|
max_fragmented_header_block_size => 16384..16#7fffffff,
|
2018-04-27 17:58:11 +02:00
|
|
|
max_frame_size_received => 16384..16777215,
|
|
|
|
max_frame_size_sent => 16384..16777215 | infinity,
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
max_received_frame_rate => {pos_integer(), timeout()},
|
|
|
|
max_reset_stream_rate => {pos_integer(), timeout()},
|
2023-10-31 11:51:02 +01:00
|
|
|
max_cancel_stream_rate => {pos_integer(), timeout()},
|
2019-09-13 14:20:04 +02:00
|
|
|
max_stream_buffer_size => non_neg_integer(),
|
2019-09-02 14:48:28 +02:00
|
|
|
max_stream_window_size => 0..16#7fffffff,
|
2018-10-31 11:45:04 +01:00
|
|
|
metrics_callback => cowboy_metrics_h:metrics_callback(),
|
2019-10-07 09:59:36 +02:00
|
|
|
metrics_req_filter => fun((cowboy_req:req()) -> map()),
|
|
|
|
metrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()),
|
2017-05-05 13:48:25 +02:00
|
|
|
middlewares => [module()],
|
|
|
|
preface_timeout => timeout(),
|
2025-02-10 15:26:00 +01:00
|
|
|
protocols => [http | http2],
|
2018-10-31 11:45:04 +01:00
|
|
|
proxy_header => boolean(),
|
2021-02-08 16:05:05 -08:00
|
|
|
reset_idle_timeout_on_send => boolean(),
|
2018-11-03 18:55:40 +01:00
|
|
|
sendfile => boolean(),
|
2018-04-28 10:59:56 +02:00
|
|
|
settings_timeout => timeout(),
|
2017-05-05 13:48:25 +02:00
|
|
|
shutdown_timeout => timeout(),
|
2018-10-31 11:45:04 +01:00
|
|
|
stream_handlers => [module()],
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
stream_window_data_threshold => 0..16#7fffffff,
|
2019-09-02 14:48:28 +02:00
|
|
|
stream_window_margin_size => 0..16#7fffffff,
|
|
|
|
stream_window_update_threshold => 0..16#7fffffff,
|
2018-10-31 11:45:04 +01:00
|
|
|
tracer_callback => cowboy_tracer_h:tracer_callback(),
|
2019-10-07 10:43:22 +02:00
|
|
|
tracer_flags => [atom()],
|
2018-02-17 16:43:46 +03:00
|
|
|
tracer_match_specs => cowboy_tracer_h:tracer_match_specs(),
|
|
|
|
%% Open ended because configured stream handlers might add options.
|
|
|
|
_ => _
|
2017-05-05 13:48:25 +02:00
|
|
|
}.
|
|
|
|
-export_type([opts/0]).
|
|
|
|
|
2019-09-02 14:48:28 +02:00
|
|
|
-record(stream, {
|
|
|
|
%% Whether the stream is currently stopping.
|
|
|
|
status = running :: running | stopping,
|
|
|
|
|
|
|
|
%% Flow requested for this stream.
|
|
|
|
flow = 0 :: non_neg_integer(),
|
|
|
|
|
|
|
|
%% Stream state.
|
|
|
|
state :: {module, any()}
|
|
|
|
}).
|
|
|
|
|
2025-01-08 16:54:14 +01:00
|
|
|
%% We don't want to reset the idle timeout too often,
|
|
|
|
%% so we don't reset it on data. Instead we reset the
|
|
|
|
%% number of ticks we have observed. We divide the
|
|
|
|
%% timeout value by a value and that value becomes
|
|
|
|
%% the number of ticks at which point we can drop
|
|
|
|
%% the connection. This value is the number of ticks.
|
|
|
|
-define(IDLE_TIMEOUT_TICKS, 10).
|
|
|
|
|
2015-06-11 17:04:21 +02:00
|
|
|
-record(state, {
|
|
|
|
parent = undefined :: pid(),
|
|
|
|
ref :: ranch:ref(),
|
|
|
|
socket = undefined :: inet:socket(),
|
|
|
|
transport :: module(),
|
2018-10-30 23:30:05 +01:00
|
|
|
proxy_header :: undefined | ranch_proxy_header:proxy_info(),
|
2017-05-05 13:48:25 +02:00
|
|
|
opts = #{} :: opts(),
|
2015-06-11 17:04:21 +02:00
|
|
|
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
%% Timer for idle_timeout; also used for goaway timers.
|
2018-11-16 16:30:57 +01:00
|
|
|
timer = undefined :: undefined | reference(),
|
2025-01-08 16:54:14 +01:00
|
|
|
idle_timeout_num = 0 :: 0..?IDLE_TIMEOUT_TICKS,
|
2018-11-16 16:30:57 +01:00
|
|
|
|
2016-06-20 17:28:59 +02:00
|
|
|
%% Remote address and port for the connection.
|
|
|
|
peer = undefined :: {inet:ip_address(), inet:port_number()},
|
|
|
|
|
2017-10-25 20:17:21 +01:00
|
|
|
%% Local address and port for the connection.
|
|
|
|
sock = undefined :: {inet:ip_address(), inet:port_number()},
|
|
|
|
|
|
|
|
%% Client certificate (TLS only).
|
|
|
|
cert :: undefined | binary(),
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% HTTP/2 state machine.
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
http2_status :: sequence | settings | upgrade | connected | closing_initiated | closing,
|
2018-10-26 10:18:57 +02:00
|
|
|
http2_machine :: cow_http2_machine:http2_machine(),
|
2015-06-11 17:04:21 +02:00
|
|
|
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
%% HTTP/2 frame rate flood protection.
|
2019-10-02 12:01:40 +02:00
|
|
|
frame_rate_num :: undefined | pos_integer(),
|
|
|
|
frame_rate_time :: undefined | integer(),
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
|
|
|
|
%% HTTP/2 reset stream flood protection.
|
2019-10-02 12:01:40 +02:00
|
|
|
reset_rate_num :: undefined | pos_integer(),
|
|
|
|
reset_rate_time :: undefined | integer(),
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
|
2023-10-31 11:51:02 +01:00
|
|
|
%% HTTP/2 rapid reset attack protection.
|
|
|
|
cancel_rate_num :: undefined | pos_integer(),
|
|
|
|
cancel_rate_time :: undefined | integer(),
|
|
|
|
|
2019-09-02 14:48:28 +02:00
|
|
|
%% Flow requested for all streams.
|
|
|
|
flow = 0 :: non_neg_integer(),
|
|
|
|
|
Implement dynamic socket buffer sizes
Cowboy will set the socket's buffer size dynamically to
better fit the current workload. When the incoming data
is small, a low buffer size reduces the memory footprint
and improves responsiveness and therefore performance.
When the incoming data is large, such as large HTTP
request bodies, a larger buffer size helps us avoid
doing too many binary appends and related allocations.
Setting a large buffer size for all use cases is
sub-optimal because allocating more than needed
necessarily results in a performance hit (not just
increased memory usage).
By default Cowboy starts with a buffer size of 8192 bytes.
It then doubles or halves the buffer size depending on
the size of the data it receives from the socket. It
stops decreasing at 8192 and increasing at 131072 by
default.
To keep track of the size of the incoming data Cowboy
maintains a moving average. It allows Cowboy to avoid
changing the buffer too often but still react quickly
when necessary. Cowboy will increase the buffer size
when the moving average is above 90% of the current
buffer size, and decrease when the moving average is
below 40% of the current buffer size.
The current buffer size and moving average are
propagated when switching protocols. The dynamic buffer
is implemented in HTTP/1, HTTP/2 and HTTP/1 Websocket.
HTTP/2 Websocket has it disabled because it doesn't
interact directly with the socket; in that case it
is HTTP/2 that has a dynamic buffer.
The dynamic buffer provides a very large performance improvement
in many scenarios, at minimal cost for others. Because it largely
depend on the underlying protocol the improvements are no all equal.
TLS and compression also impact the results.
The improvement when reading a large request body, with the
requests repeated in a fast loop are:
* HTTP: 6x to 20x faster
* HTTPS: 2x to 6x faster
* H2: 4x to 5x faster
* H2C: 20x to 40x faster
I am not sure why H2C's performance was so bad, especially compared
to H2, when using default buffer sizes. Dynamic buffers make H2C a
lot more viable with default settings.
The performance impact on "hello world" type requests is minimal,
it goes from -5% to +5% roughly.
Websocket improvements vary again depending on the protocol, but
also depending on whether compression is enabled:
* HTTP echo: roughly 2x faster
* HTTP send: roughly 4x faster
* H2C echo: roughly 2x faster
* H2C send: 3x to 4x faster
In the echo test we reply back, and Gun doesn't have the dynamic
buffer optimisation, so that probably explains the x2 difference.
With compression however there isn't much improvement. The results
are roughly within -10% to +10% of each other. Zlib compression
seems to be a bottleneck, or at least to modify the performance
profile to such an extent that the size of the buffer does not
matter. This happens to randomly generated binary data as well
so it is probably not caused by the test data.
2025-02-03 15:36:16 +01:00
|
|
|
%% Dynamic buffer moving average and current buffer size.
|
|
|
|
dynamic_buffer_size :: pos_integer() | false,
|
|
|
|
dynamic_buffer_moving_average :: non_neg_integer(),
|
|
|
|
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Currently active HTTP/2 streams. Streams may be initiated either
|
|
|
|
%% by the client or by the server through PUSH_PROMISE frames.
|
2019-09-02 14:48:28 +02:00
|
|
|
streams = #{} :: #{cow_http2:streamid() => #stream{}},
|
2018-04-27 20:45:34 +02:00
|
|
|
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Streams can spawn zero or more children which are then managed
|
|
|
|
%% by this module if operating as a supervisor.
|
2018-10-26 10:18:57 +02:00
|
|
|
children = cowboy_children:init() :: cowboy_children:children()
|
2015-06-11 17:04:21 +02:00
|
|
|
}).
|
|
|
|
|
2018-10-30 23:30:05 +01:00
|
|
|
-spec init(pid(), ranch:ref(), inet:socket(), module(),
|
2025-06-20 11:50:38 +02:00
|
|
|
ranch_proxy_header:proxy_info() | undefined, cowboy:opts()) -> no_return().
|
|
|
|
|
2018-10-30 23:30:05 +01:00
|
|
|
init(Parent, Ref, Socket, Transport, ProxyHeader, Opts) ->
|
2023-12-12 12:05:54 +01:00
|
|
|
{ok, Peer} = maybe_socket_error(undefined, Transport:peername(Socket),
|
|
|
|
'A socket error occurred when retrieving the peer name.'),
|
|
|
|
{ok, Sock} = maybe_socket_error(undefined, Transport:sockname(Socket),
|
|
|
|
'A socket error occurred when retrieving the sock name.'),
|
|
|
|
CertResult = case Transport:name() of
|
2017-10-25 20:17:21 +01:00
|
|
|
ssl ->
|
|
|
|
case ssl:peercert(Socket) of
|
|
|
|
{error, no_peercert} ->
|
|
|
|
{ok, undefined};
|
|
|
|
Cert0 ->
|
|
|
|
Cert0
|
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
{ok, undefined}
|
|
|
|
end,
|
2023-12-12 12:05:54 +01:00
|
|
|
{ok, Cert} = maybe_socket_error(undefined, CertResult,
|
|
|
|
'A socket error occurred when retrieving the client TLS certificate.'),
|
|
|
|
init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, <<>>).
|
2016-03-10 23:30:49 +01:00
|
|
|
|
2018-10-30 23:30:05 +01:00
|
|
|
-spec init(pid(), ranch:ref(), inet:socket(), module(),
|
2018-10-31 11:45:04 +01:00
|
|
|
ranch_proxy_header:proxy_info() | undefined, cowboy:opts(),
|
2017-10-25 20:17:21 +01:00
|
|
|
{inet:ip_address(), inet:port_number()}, {inet:ip_address(), inet:port_number()},
|
2025-06-20 11:50:38 +02:00
|
|
|
binary() | undefined, binary()) -> no_return().
|
|
|
|
|
2018-10-30 23:30:05 +01:00
|
|
|
init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, Buffer) ->
|
Implement dynamic socket buffer sizes
Cowboy will set the socket's buffer size dynamically to
better fit the current workload. When the incoming data
is small, a low buffer size reduces the memory footprint
and improves responsiveness and therefore performance.
When the incoming data is large, such as large HTTP
request bodies, a larger buffer size helps us avoid
doing too many binary appends and related allocations.
Setting a large buffer size for all use cases is
sub-optimal because allocating more than needed
necessarily results in a performance hit (not just
increased memory usage).
By default Cowboy starts with a buffer size of 8192 bytes.
It then doubles or halves the buffer size depending on
the size of the data it receives from the socket. It
stops decreasing at 8192 and increasing at 131072 by
default.
To keep track of the size of the incoming data Cowboy
maintains a moving average. It allows Cowboy to avoid
changing the buffer too often but still react quickly
when necessary. Cowboy will increase the buffer size
when the moving average is above 90% of the current
buffer size, and decrease when the moving average is
below 40% of the current buffer size.
The current buffer size and moving average are
propagated when switching protocols. The dynamic buffer
is implemented in HTTP/1, HTTP/2 and HTTP/1 Websocket.
HTTP/2 Websocket has it disabled because it doesn't
interact directly with the socket; in that case it
is HTTP/2 that has a dynamic buffer.
The dynamic buffer provides a very large performance improvement
in many scenarios, at minimal cost for others. Because it largely
depend on the underlying protocol the improvements are no all equal.
TLS and compression also impact the results.
The improvement when reading a large request body, with the
requests repeated in a fast loop are:
* HTTP: 6x to 20x faster
* HTTPS: 2x to 6x faster
* H2: 4x to 5x faster
* H2C: 20x to 40x faster
I am not sure why H2C's performance was so bad, especially compared
to H2, when using default buffer sizes. Dynamic buffers make H2C a
lot more viable with default settings.
The performance impact on "hello world" type requests is minimal,
it goes from -5% to +5% roughly.
Websocket improvements vary again depending on the protocol, but
also depending on whether compression is enabled:
* HTTP echo: roughly 2x faster
* HTTP send: roughly 4x faster
* H2C echo: roughly 2x faster
* H2C send: 3x to 4x faster
In the echo test we reply back, and Gun doesn't have the dynamic
buffer optimisation, so that probably explains the x2 difference.
With compression however there isn't much improvement. The results
are roughly within -10% to +10% of each other. Zlib compression
seems to be a bottleneck, or at least to modify the performance
profile to such an extent that the size of the buffer does not
matter. This happens to randomly generated binary data as well
so it is probably not caused by the test data.
2025-02-03 15:36:16 +01:00
|
|
|
DynamicBuffer = init_dynamic_buffer_size(Opts),
|
2018-10-26 10:18:57 +02:00
|
|
|
{ok, Preface, HTTP2Machine} = cow_http2_machine:init(server, Opts),
|
2023-12-12 12:05:54 +01:00
|
|
|
%% Send the preface before doing all the init in case we get a socket error.
|
|
|
|
ok = maybe_socket_error(undefined, Transport:send(Socket, Preface)),
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
State = set_idle_timeout(init_rate_limiting(#state{parent=Parent, ref=Ref, socket=Socket,
|
2018-10-30 23:30:05 +01:00
|
|
|
transport=Transport, proxy_header=ProxyHeader,
|
|
|
|
opts=Opts, peer=Peer, sock=Sock, cert=Cert,
|
Implement dynamic socket buffer sizes
Cowboy will set the socket's buffer size dynamically to
better fit the current workload. When the incoming data
is small, a low buffer size reduces the memory footprint
and improves responsiveness and therefore performance.
When the incoming data is large, such as large HTTP
request bodies, a larger buffer size helps us avoid
doing too many binary appends and related allocations.
Setting a large buffer size for all use cases is
sub-optimal because allocating more than needed
necessarily results in a performance hit (not just
increased memory usage).
By default Cowboy starts with a buffer size of 8192 bytes.
It then doubles or halves the buffer size depending on
the size of the data it receives from the socket. It
stops decreasing at 8192 and increasing at 131072 by
default.
To keep track of the size of the incoming data Cowboy
maintains a moving average. It allows Cowboy to avoid
changing the buffer too often but still react quickly
when necessary. Cowboy will increase the buffer size
when the moving average is above 90% of the current
buffer size, and decrease when the moving average is
below 40% of the current buffer size.
The current buffer size and moving average are
propagated when switching protocols. The dynamic buffer
is implemented in HTTP/1, HTTP/2 and HTTP/1 Websocket.
HTTP/2 Websocket has it disabled because it doesn't
interact directly with the socket; in that case it
is HTTP/2 that has a dynamic buffer.
The dynamic buffer provides a very large performance improvement
in many scenarios, at minimal cost for others. Because it largely
depend on the underlying protocol the improvements are no all equal.
TLS and compression also impact the results.
The improvement when reading a large request body, with the
requests repeated in a fast loop are:
* HTTP: 6x to 20x faster
* HTTPS: 2x to 6x faster
* H2: 4x to 5x faster
* H2C: 20x to 40x faster
I am not sure why H2C's performance was so bad, especially compared
to H2, when using default buffer sizes. Dynamic buffers make H2C a
lot more viable with default settings.
The performance impact on "hello world" type requests is minimal,
it goes from -5% to +5% roughly.
Websocket improvements vary again depending on the protocol, but
also depending on whether compression is enabled:
* HTTP echo: roughly 2x faster
* HTTP send: roughly 4x faster
* H2C echo: roughly 2x faster
* H2C send: 3x to 4x faster
In the echo test we reply back, and Gun doesn't have the dynamic
buffer optimisation, so that probably explains the x2 difference.
With compression however there isn't much improvement. The results
are roughly within -10% to +10% of each other. Zlib compression
seems to be a bottleneck, or at least to modify the performance
profile to such an extent that the size of the buffer does not
matter. This happens to randomly generated binary data as well
so it is probably not caused by the test data.
2025-02-03 15:36:16 +01:00
|
|
|
dynamic_buffer_size=DynamicBuffer,
|
|
|
|
dynamic_buffer_moving_average=maps:get(dynamic_buffer_initial_average, Opts, 0),
|
2025-01-08 16:54:14 +01:00
|
|
|
http2_status=sequence, http2_machine=HTTP2Machine}), 0),
|
2023-12-12 12:05:54 +01:00
|
|
|
safe_setopts_active(State),
|
2016-03-10 23:30:49 +01:00
|
|
|
case Buffer of
|
2025-02-07 16:57:58 +01:00
|
|
|
<<>> -> before_loop(State, Buffer);
|
2016-03-10 23:30:49 +01:00
|
|
|
_ -> parse(State, Buffer)
|
|
|
|
end.
|
|
|
|
|
2023-10-31 11:51:02 +01:00
|
|
|
init_rate_limiting(State0) ->
|
2020-11-02 11:12:21 +01:00
|
|
|
CurrentTime = erlang:monotonic_time(millisecond),
|
2023-10-31 11:51:02 +01:00
|
|
|
State1 = init_frame_rate_limiting(State0, CurrentTime),
|
|
|
|
State2 = init_reset_rate_limiting(State1, CurrentTime),
|
|
|
|
init_cancel_rate_limiting(State2, CurrentTime).
|
2020-11-02 11:12:21 +01:00
|
|
|
|
|
|
|
init_frame_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->
|
2020-03-29 13:51:21 +02:00
|
|
|
{FrameRateNum, FrameRatePeriod} = maps:get(max_received_frame_rate, Opts, {10000, 10000}),
|
2020-11-02 11:12:21 +01:00
|
|
|
State#state{
|
|
|
|
frame_rate_num=FrameRateNum, frame_rate_time=add_period(CurrentTime, FrameRatePeriod)
|
|
|
|
}.
|
|
|
|
|
|
|
|
init_reset_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
{ResetRateNum, ResetRatePeriod} = maps:get(max_reset_stream_rate, Opts, {10, 10000}),
|
|
|
|
State#state{
|
|
|
|
reset_rate_num=ResetRateNum, reset_rate_time=add_period(CurrentTime, ResetRatePeriod)
|
|
|
|
}.
|
|
|
|
|
2023-10-31 11:51:02 +01:00
|
|
|
init_cancel_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->
|
|
|
|
{CancelRateNum, CancelRatePeriod} = maps:get(max_cancel_stream_rate, Opts, {500, 10000}),
|
|
|
|
State#state{
|
|
|
|
cancel_rate_num=CancelRateNum, cancel_rate_time=add_period(CurrentTime, CancelRatePeriod)
|
|
|
|
}.
|
|
|
|
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
add_period(_, infinity) -> infinity;
|
|
|
|
add_period(Time, Period) -> Time + Period.
|
|
|
|
|
2016-03-12 18:25:35 +01:00
|
|
|
%% @todo Add an argument for the request body.
|
2018-10-30 23:30:05 +01:00
|
|
|
-spec init(pid(), ranch:ref(), inet:socket(), module(),
|
2018-10-31 11:45:04 +01:00
|
|
|
ranch_proxy_header:proxy_info() | undefined, cowboy:opts(),
|
2017-10-25 20:17:21 +01:00
|
|
|
{inet:ip_address(), inet:port_number()}, {inet:ip_address(), inet:port_number()},
|
2025-06-20 11:50:38 +02:00
|
|
|
binary() | undefined, binary(), map() | undefined, cowboy_req:req()) -> no_return().
|
|
|
|
|
2018-10-30 23:30:05 +01:00
|
|
|
init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, Buffer,
|
2018-10-26 10:18:57 +02:00
|
|
|
_Settings, Req=#{method := Method}) ->
|
Implement dynamic socket buffer sizes
Cowboy will set the socket's buffer size dynamically to
better fit the current workload. When the incoming data
is small, a low buffer size reduces the memory footprint
and improves responsiveness and therefore performance.
When the incoming data is large, such as large HTTP
request bodies, a larger buffer size helps us avoid
doing too many binary appends and related allocations.
Setting a large buffer size for all use cases is
sub-optimal because allocating more than needed
necessarily results in a performance hit (not just
increased memory usage).
By default Cowboy starts with a buffer size of 8192 bytes.
It then doubles or halves the buffer size depending on
the size of the data it receives from the socket. It
stops decreasing at 8192 and increasing at 131072 by
default.
To keep track of the size of the incoming data Cowboy
maintains a moving average. It allows Cowboy to avoid
changing the buffer too often but still react quickly
when necessary. Cowboy will increase the buffer size
when the moving average is above 90% of the current
buffer size, and decrease when the moving average is
below 40% of the current buffer size.
The current buffer size and moving average are
propagated when switching protocols. The dynamic buffer
is implemented in HTTP/1, HTTP/2 and HTTP/1 Websocket.
HTTP/2 Websocket has it disabled because it doesn't
interact directly with the socket; in that case it
is HTTP/2 that has a dynamic buffer.
The dynamic buffer provides a very large performance improvement
in many scenarios, at minimal cost for others. Because it largely
depend on the underlying protocol the improvements are no all equal.
TLS and compression also impact the results.
The improvement when reading a large request body, with the
requests repeated in a fast loop are:
* HTTP: 6x to 20x faster
* HTTPS: 2x to 6x faster
* H2: 4x to 5x faster
* H2C: 20x to 40x faster
I am not sure why H2C's performance was so bad, especially compared
to H2, when using default buffer sizes. Dynamic buffers make H2C a
lot more viable with default settings.
The performance impact on "hello world" type requests is minimal,
it goes from -5% to +5% roughly.
Websocket improvements vary again depending on the protocol, but
also depending on whether compression is enabled:
* HTTP echo: roughly 2x faster
* HTTP send: roughly 4x faster
* H2C echo: roughly 2x faster
* H2C send: 3x to 4x faster
In the echo test we reply back, and Gun doesn't have the dynamic
buffer optimisation, so that probably explains the x2 difference.
With compression however there isn't much improvement. The results
are roughly within -10% to +10% of each other. Zlib compression
seems to be a bottleneck, or at least to modify the performance
profile to such an extent that the size of the buffer does not
matter. This happens to randomly generated binary data as well
so it is probably not caused by the test data.
2025-02-03 15:36:16 +01:00
|
|
|
DynamicBuffer = init_dynamic_buffer_size(Opts),
|
2018-10-26 10:18:57 +02:00
|
|
|
{ok, Preface, HTTP2Machine0} = cow_http2_machine:init(server, Opts),
|
|
|
|
{ok, StreamID, HTTP2Machine}
|
|
|
|
= cow_http2_machine:init_upgrade_stream(Method, HTTP2Machine0),
|
2016-03-12 18:25:35 +01:00
|
|
|
State0 = #state{parent=Parent, ref=Ref, socket=Socket,
|
2018-10-30 23:30:05 +01:00
|
|
|
transport=Transport, proxy_header=ProxyHeader,
|
|
|
|
opts=Opts, peer=Peer, sock=Sock, cert=Cert,
|
Implement dynamic socket buffer sizes
Cowboy will set the socket's buffer size dynamically to
better fit the current workload. When the incoming data
is small, a low buffer size reduces the memory footprint
and improves responsiveness and therefore performance.
When the incoming data is large, such as large HTTP
request bodies, a larger buffer size helps us avoid
doing too many binary appends and related allocations.
Setting a large buffer size for all use cases is
sub-optimal because allocating more than needed
necessarily results in a performance hit (not just
increased memory usage).
By default Cowboy starts with a buffer size of 8192 bytes.
It then doubles or halves the buffer size depending on
the size of the data it receives from the socket. It
stops decreasing at 8192 and increasing at 131072 by
default.
To keep track of the size of the incoming data Cowboy
maintains a moving average. It allows Cowboy to avoid
changing the buffer too often but still react quickly
when necessary. Cowboy will increase the buffer size
when the moving average is above 90% of the current
buffer size, and decrease when the moving average is
below 40% of the current buffer size.
The current buffer size and moving average are
propagated when switching protocols. The dynamic buffer
is implemented in HTTP/1, HTTP/2 and HTTP/1 Websocket.
HTTP/2 Websocket has it disabled because it doesn't
interact directly with the socket; in that case it
is HTTP/2 that has a dynamic buffer.
The dynamic buffer provides a very large performance improvement
in many scenarios, at minimal cost for others. Because it largely
depend on the underlying protocol the improvements are no all equal.
TLS and compression also impact the results.
The improvement when reading a large request body, with the
requests repeated in a fast loop are:
* HTTP: 6x to 20x faster
* HTTPS: 2x to 6x faster
* H2: 4x to 5x faster
* H2C: 20x to 40x faster
I am not sure why H2C's performance was so bad, especially compared
to H2, when using default buffer sizes. Dynamic buffers make H2C a
lot more viable with default settings.
The performance impact on "hello world" type requests is minimal,
it goes from -5% to +5% roughly.
Websocket improvements vary again depending on the protocol, but
also depending on whether compression is enabled:
* HTTP echo: roughly 2x faster
* HTTP send: roughly 4x faster
* H2C echo: roughly 2x faster
* H2C send: 3x to 4x faster
In the echo test we reply back, and Gun doesn't have the dynamic
buffer optimisation, so that probably explains the x2 difference.
With compression however there isn't much improvement. The results
are roughly within -10% to +10% of each other. Zlib compression
seems to be a bottleneck, or at least to modify the performance
profile to such an extent that the size of the buffer does not
matter. This happens to randomly generated binary data as well
so it is probably not caused by the test data.
2025-02-03 15:36:16 +01:00
|
|
|
dynamic_buffer_size=DynamicBuffer,
|
|
|
|
dynamic_buffer_moving_average=maps:get(dynamic_buffer_initial_average, Opts, 0),
|
2019-08-16 16:37:04 +02:00
|
|
|
http2_status=upgrade, http2_machine=HTTP2Machine},
|
2018-10-26 10:18:57 +02:00
|
|
|
State1 = headers_frame(State0#state{
|
|
|
|
http2_machine=HTTP2Machine}, StreamID, Req),
|
2017-04-18 14:06:34 +02:00
|
|
|
%% We assume that the upgrade will be applied. A stream handler
|
|
|
|
%% must not prevent the normal operations of the server.
|
2018-10-27 00:16:13 +02:00
|
|
|
State2 = info(State1, 1, {switch_protocol, #{
|
2017-04-18 14:06:34 +02:00
|
|
|
<<"connection">> => <<"Upgrade">>,
|
|
|
|
<<"upgrade">> => <<"h2c">>
|
|
|
|
}, ?MODULE, undefined}), %% @todo undefined or #{}?
|
2025-01-08 16:54:14 +01:00
|
|
|
State = set_idle_timeout(init_rate_limiting(State2#state{http2_status=sequence}), 0),
|
2023-12-12 12:05:54 +01:00
|
|
|
%% In the case of HTTP/1.1 Upgrade we cannot send the Preface
|
|
|
|
%% until we send the 101 response.
|
|
|
|
ok = maybe_socket_error(State, Transport:send(Socket, Preface)),
|
|
|
|
safe_setopts_active(State),
|
2016-03-12 18:25:35 +01:00
|
|
|
case Buffer of
|
2025-02-07 16:57:58 +01:00
|
|
|
<<>> -> before_loop(State, Buffer);
|
2016-03-12 18:25:35 +01:00
|
|
|
_ -> parse(State, Buffer)
|
|
|
|
end.
|
|
|
|
|
Implement dynamic socket buffer sizes
Cowboy will set the socket's buffer size dynamically to
better fit the current workload. When the incoming data
is small, a low buffer size reduces the memory footprint
and improves responsiveness and therefore performance.
When the incoming data is large, such as large HTTP
request bodies, a larger buffer size helps us avoid
doing too many binary appends and related allocations.
Setting a large buffer size for all use cases is
sub-optimal because allocating more than needed
necessarily results in a performance hit (not just
increased memory usage).
By default Cowboy starts with a buffer size of 8192 bytes.
It then doubles or halves the buffer size depending on
the size of the data it receives from the socket. It
stops decreasing at 8192 and increasing at 131072 by
default.
To keep track of the size of the incoming data Cowboy
maintains a moving average. It allows Cowboy to avoid
changing the buffer too often but still react quickly
when necessary. Cowboy will increase the buffer size
when the moving average is above 90% of the current
buffer size, and decrease when the moving average is
below 40% of the current buffer size.
The current buffer size and moving average are
propagated when switching protocols. The dynamic buffer
is implemented in HTTP/1, HTTP/2 and HTTP/1 Websocket.
HTTP/2 Websocket has it disabled because it doesn't
interact directly with the socket; in that case it
is HTTP/2 that has a dynamic buffer.
The dynamic buffer provides a very large performance improvement
in many scenarios, at minimal cost for others. Because it largely
depend on the underlying protocol the improvements are no all equal.
TLS and compression also impact the results.
The improvement when reading a large request body, with the
requests repeated in a fast loop are:
* HTTP: 6x to 20x faster
* HTTPS: 2x to 6x faster
* H2: 4x to 5x faster
* H2C: 20x to 40x faster
I am not sure why H2C's performance was so bad, especially compared
to H2, when using default buffer sizes. Dynamic buffers make H2C a
lot more viable with default settings.
The performance impact on "hello world" type requests is minimal,
it goes from -5% to +5% roughly.
Websocket improvements vary again depending on the protocol, but
also depending on whether compression is enabled:
* HTTP echo: roughly 2x faster
* HTTP send: roughly 4x faster
* H2C echo: roughly 2x faster
* H2C send: 3x to 4x faster
In the echo test we reply back, and Gun doesn't have the dynamic
buffer optimisation, so that probably explains the x2 difference.
With compression however there isn't much improvement. The results
are roughly within -10% to +10% of each other. Zlib compression
seems to be a bottleneck, or at least to modify the performance
profile to such an extent that the size of the buffer does not
matter. This happens to randomly generated binary data as well
so it is probably not caused by the test data.
2025-02-03 15:36:16 +01:00
|
|
|
-include("cowboy_dynamic_buffer.hrl").
|
|
|
|
|
2019-12-04 11:17:34 +01:00
|
|
|
%% Because HTTP/2 has flow control and Cowboy has other rate limiting
|
|
|
|
%% mechanisms implemented, a very large active_n value should be fine,
|
|
|
|
%% as long as the stream handlers do their work in a timely manner.
|
Implement dynamic socket buffer sizes
Cowboy will set the socket's buffer size dynamically to
better fit the current workload. When the incoming data
is small, a low buffer size reduces the memory footprint
and improves responsiveness and therefore performance.
When the incoming data is large, such as large HTTP
request bodies, a larger buffer size helps us avoid
doing too many binary appends and related allocations.
Setting a large buffer size for all use cases is
sub-optimal because allocating more than needed
necessarily results in a performance hit (not just
increased memory usage).
By default Cowboy starts with a buffer size of 8192 bytes.
It then doubles or halves the buffer size depending on
the size of the data it receives from the socket. It
stops decreasing at 8192 and increasing at 131072 by
default.
To keep track of the size of the incoming data Cowboy
maintains a moving average. It allows Cowboy to avoid
changing the buffer too often but still react quickly
when necessary. Cowboy will increase the buffer size
when the moving average is above 90% of the current
buffer size, and decrease when the moving average is
below 40% of the current buffer size.
The current buffer size and moving average are
propagated when switching protocols. The dynamic buffer
is implemented in HTTP/1, HTTP/2 and HTTP/1 Websocket.
HTTP/2 Websocket has it disabled because it doesn't
interact directly with the socket; in that case it
is HTTP/2 that has a dynamic buffer.
The dynamic buffer provides a very large performance improvement
in many scenarios, at minimal cost for others. Because it largely
depend on the underlying protocol the improvements are no all equal.
TLS and compression also impact the results.
The improvement when reading a large request body, with the
requests repeated in a fast loop are:
* HTTP: 6x to 20x faster
* HTTPS: 2x to 6x faster
* H2: 4x to 5x faster
* H2C: 20x to 40x faster
I am not sure why H2C's performance was so bad, especially compared
to H2, when using default buffer sizes. Dynamic buffers make H2C a
lot more viable with default settings.
The performance impact on "hello world" type requests is minimal,
it goes from -5% to +5% roughly.
Websocket improvements vary again depending on the protocol, but
also depending on whether compression is enabled:
* HTTP echo: roughly 2x faster
* HTTP send: roughly 4x faster
* H2C echo: roughly 2x faster
* H2C send: 3x to 4x faster
In the echo test we reply back, and Gun doesn't have the dynamic
buffer optimisation, so that probably explains the x2 difference.
With compression however there isn't much improvement. The results
are roughly within -10% to +10% of each other. Zlib compression
seems to be a bottleneck, or at least to modify the performance
profile to such an extent that the size of the buffer does not
matter. This happens to randomly generated binary data as well
so it is probably not caused by the test data.
2025-02-03 15:36:16 +01:00
|
|
|
%% However large active_n values reduce the impact of dynamic_buffer.
|
2019-12-04 11:17:34 +01:00
|
|
|
setopts_active(#state{socket=Socket, transport=Transport, opts=Opts}) ->
|
Implement dynamic socket buffer sizes
Cowboy will set the socket's buffer size dynamically to
better fit the current workload. When the incoming data
is small, a low buffer size reduces the memory footprint
and improves responsiveness and therefore performance.
When the incoming data is large, such as large HTTP
request bodies, a larger buffer size helps us avoid
doing too many binary appends and related allocations.
Setting a large buffer size for all use cases is
sub-optimal because allocating more than needed
necessarily results in a performance hit (not just
increased memory usage).
By default Cowboy starts with a buffer size of 8192 bytes.
It then doubles or halves the buffer size depending on
the size of the data it receives from the socket. It
stops decreasing at 8192 and increasing at 131072 by
default.
To keep track of the size of the incoming data Cowboy
maintains a moving average. It allows Cowboy to avoid
changing the buffer too often but still react quickly
when necessary. Cowboy will increase the buffer size
when the moving average is above 90% of the current
buffer size, and decrease when the moving average is
below 40% of the current buffer size.
The current buffer size and moving average are
propagated when switching protocols. The dynamic buffer
is implemented in HTTP/1, HTTP/2 and HTTP/1 Websocket.
HTTP/2 Websocket has it disabled because it doesn't
interact directly with the socket; in that case it
is HTTP/2 that has a dynamic buffer.
The dynamic buffer provides a very large performance improvement
in many scenarios, at minimal cost for others. Because it largely
depend on the underlying protocol the improvements are no all equal.
TLS and compression also impact the results.
The improvement when reading a large request body, with the
requests repeated in a fast loop are:
* HTTP: 6x to 20x faster
* HTTPS: 2x to 6x faster
* H2: 4x to 5x faster
* H2C: 20x to 40x faster
I am not sure why H2C's performance was so bad, especially compared
to H2, when using default buffer sizes. Dynamic buffers make H2C a
lot more viable with default settings.
The performance impact on "hello world" type requests is minimal,
it goes from -5% to +5% roughly.
Websocket improvements vary again depending on the protocol, but
also depending on whether compression is enabled:
* HTTP echo: roughly 2x faster
* HTTP send: roughly 4x faster
* H2C echo: roughly 2x faster
* H2C send: 3x to 4x faster
In the echo test we reply back, and Gun doesn't have the dynamic
buffer optimisation, so that probably explains the x2 difference.
With compression however there isn't much improvement. The results
are roughly within -10% to +10% of each other. Zlib compression
seems to be a bottleneck, or at least to modify the performance
profile to such an extent that the size of the buffer does not
matter. This happens to randomly generated binary data as well
so it is probably not caused by the test data.
2025-02-03 15:36:16 +01:00
|
|
|
N = maps:get(active_n, Opts, 1),
|
2019-12-04 11:17:34 +01:00
|
|
|
Transport:setopts(Socket, [{active, N}]).
|
|
|
|
|
2023-12-12 12:05:54 +01:00
|
|
|
safe_setopts_active(State) ->
|
|
|
|
ok = maybe_socket_error(State, setopts_active(State)).
|
|
|
|
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(State=#state{opts=#{hibernate := true}}, Buffer) ->
|
|
|
|
proc_lib:hibernate(?MODULE, loop, [State, Buffer]);
|
|
|
|
before_loop(State, Buffer) ->
|
|
|
|
loop(State, Buffer).
|
|
|
|
|
2025-06-20 11:50:38 +02:00
|
|
|
-spec loop(#state{}, binary()) -> no_return().
|
2025-02-07 16:57:58 +01:00
|
|
|
|
2016-03-13 23:14:57 +01:00
|
|
|
loop(State=#state{parent=Parent, socket=Socket, transport=Transport,
|
2018-11-16 16:30:57 +01:00
|
|
|
opts=Opts, timer=TimerRef, children=Children}, Buffer) ->
|
2019-06-06 17:52:34 +02:00
|
|
|
Messages = Transport:messages(),
|
2017-05-05 13:48:25 +02:00
|
|
|
InactivityTimeout = maps:get(inactivity_timeout, Opts, 300000),
|
2015-06-11 17:04:21 +02:00
|
|
|
receive
|
|
|
|
%% Socket messages.
|
2019-06-06 17:52:34 +02:00
|
|
|
{OK, Socket, Data} when OK =:= element(1, Messages) ->
|
Implement dynamic socket buffer sizes
Cowboy will set the socket's buffer size dynamically to
better fit the current workload. When the incoming data
is small, a low buffer size reduces the memory footprint
and improves responsiveness and therefore performance.
When the incoming data is large, such as large HTTP
request bodies, a larger buffer size helps us avoid
doing too many binary appends and related allocations.
Setting a large buffer size for all use cases is
sub-optimal because allocating more than needed
necessarily results in a performance hit (not just
increased memory usage).
By default Cowboy starts with a buffer size of 8192 bytes.
It then doubles or halves the buffer size depending on
the size of the data it receives from the socket. It
stops decreasing at 8192 and increasing at 131072 by
default.
To keep track of the size of the incoming data Cowboy
maintains a moving average. It allows Cowboy to avoid
changing the buffer too often but still react quickly
when necessary. Cowboy will increase the buffer size
when the moving average is above 90% of the current
buffer size, and decrease when the moving average is
below 40% of the current buffer size.
The current buffer size and moving average are
propagated when switching protocols. The dynamic buffer
is implemented in HTTP/1, HTTP/2 and HTTP/1 Websocket.
HTTP/2 Websocket has it disabled because it doesn't
interact directly with the socket; in that case it
is HTTP/2 that has a dynamic buffer.
The dynamic buffer provides a very large performance improvement
in many scenarios, at minimal cost for others. Because it largely
depend on the underlying protocol the improvements are no all equal.
TLS and compression also impact the results.
The improvement when reading a large request body, with the
requests repeated in a fast loop are:
* HTTP: 6x to 20x faster
* HTTPS: 2x to 6x faster
* H2: 4x to 5x faster
* H2C: 20x to 40x faster
I am not sure why H2C's performance was so bad, especially compared
to H2, when using default buffer sizes. Dynamic buffers make H2C a
lot more viable with default settings.
The performance impact on "hello world" type requests is minimal,
it goes from -5% to +5% roughly.
Websocket improvements vary again depending on the protocol, but
also depending on whether compression is enabled:
* HTTP echo: roughly 2x faster
* HTTP send: roughly 4x faster
* H2C echo: roughly 2x faster
* H2C send: 3x to 4x faster
In the echo test we reply back, and Gun doesn't have the dynamic
buffer optimisation, so that probably explains the x2 difference.
With compression however there isn't much improvement. The results
are roughly within -10% to +10% of each other. Zlib compression
seems to be a bottleneck, or at least to modify the performance
profile to such an extent that the size of the buffer does not
matter. This happens to randomly generated binary data as well
so it is probably not caused by the test data.
2025-02-03 15:36:16 +01:00
|
|
|
State1 = maybe_resize_buffer(State, Data),
|
|
|
|
parse(State1#state{idle_timeout_num=0}, << Buffer/binary, Data/binary >>);
|
2019-06-06 17:52:34 +02:00
|
|
|
{Closed, Socket} when Closed =:= element(2, Messages) ->
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
Reason = case State#state.http2_status of
|
|
|
|
closing -> {stop, closed, 'The client is going away.'};
|
|
|
|
_ -> {socket_error, closed, 'The socket has been closed.'}
|
|
|
|
end,
|
|
|
|
terminate(State, Reason);
|
2019-06-06 17:52:34 +02:00
|
|
|
{Error, Socket, Reason} when Error =:= element(3, Messages) ->
|
2015-06-11 17:04:21 +02:00
|
|
|
terminate(State, {socket_error, Reason, 'An error has occurred on the socket.'});
|
2019-12-04 11:17:34 +01:00
|
|
|
{Passive, Socket} when Passive =:= element(4, Messages);
|
|
|
|
%% Hardcoded for compatibility with Ranch 1.x.
|
|
|
|
Passive =:= tcp_passive; Passive =:= ssl_passive ->
|
2023-12-12 12:05:54 +01:00
|
|
|
safe_setopts_active(State),
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(State, Buffer);
|
2015-06-11 17:04:21 +02:00
|
|
|
%% System messages.
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
{'EXIT', Parent, shutdown} ->
|
|
|
|
Reason = {stop, {exit, shutdown}, 'Parent process requested shutdown.'},
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(initiate_closing(State, Reason), Buffer);
|
2015-06-11 17:04:21 +02:00
|
|
|
{'EXIT', Parent, Reason} ->
|
2018-10-28 10:20:43 +01:00
|
|
|
terminate(State, {stop, {exit, Reason}, 'Parent process terminated.'});
|
2015-06-11 17:04:21 +02:00
|
|
|
{system, From, Request} ->
|
|
|
|
sys:handle_system_msg(Request, From, Parent, ?MODULE, [], {State, Buffer});
|
2017-08-08 16:59:33 +02:00
|
|
|
%% Timeouts.
|
2018-11-16 16:30:57 +01:00
|
|
|
{timeout, TimerRef, idle_timeout} ->
|
2025-01-08 16:54:14 +01:00
|
|
|
tick_idle_timeout(State, Buffer);
|
2017-08-08 16:59:33 +02:00
|
|
|
{timeout, Ref, {shutdown, Pid}} ->
|
|
|
|
cowboy_children:shutdown_timeout(Children, Ref, Pid),
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(State, Buffer);
|
2018-10-27 00:16:13 +02:00
|
|
|
{timeout, TRef, {cow_http2_machine, Name}} ->
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(timeout(State, Name, TRef), Buffer);
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
{timeout, TimerRef, {goaway_initial_timeout, Reason}} ->
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(closing(State, Reason), Buffer);
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
{timeout, TimerRef, {goaway_complete_timeout, Reason}} ->
|
|
|
|
terminate(State, {stop, stop_reason(Reason),
|
|
|
|
'Graceful shutdown timed out.'});
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Messages pertaining to a stream.
|
Initial commit with connection/streams
Breaking changes with previous commit. This is a very large change,
and I am giving up on making a single commit that fixes everything.
More commits will follow slowly adding back features, introducing
new tests and fixing the documentation.
This change contains most of the work toward unifying the interface
for handling both HTTP/1.1 and HTTP/2. HTTP/1.1 connections are now
no longer 1 process per connection; instead by default 1 process per
request is also created. This has a number of pros and cons.
Because it has cons, we also allow users to use a lower-level API
that acts on "streams" (requests/responses) directly at the connection
process-level. If performance is a concern, one can always write a
stream handler. The performance in this case will be even greater
than with Cowboy 1, although all the special handlers are unavailable.
When switching to Websocket, after the handler returns from init/2,
Cowboy stops the stream and the Websocket protocol takes over the
connection process. Websocket then calls websocket_init/2 for any
additional initialization such as timers, because the process is
different in init/2 and websocket_*/* functions. This however would
allow us to use websocket_init/2 for sending messages on connect,
instead of sending ourselves a message and be subject to races.
Note that websocket_init/2 is optional.
This is all a big change and while most of the tests pass, some
functionality currently doesn't. SPDY is broken and will be removed
soon in favor of HTTP/2. Automatic compression is currently disabled.
The cowboy_req interface probably still have a few functions that
need to be updated. The docs and examples do not refer the current
functionality anymore.
Everything will be fixed over time. Feedback is more than welcome.
Open a ticket!
2016-02-10 17:28:32 +01:00
|
|
|
{{Pid, StreamID}, Msg} when Pid =:= self() ->
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(info(State, StreamID, Msg), Buffer);
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Exit signal from children.
|
|
|
|
Msg = {'EXIT', Pid, _} ->
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(down(State, Pid, Msg), Buffer);
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Calls from supervisor module.
|
2018-03-13 10:40:14 +01:00
|
|
|
{'$gen_call', From, Call} ->
|
|
|
|
cowboy_children:handle_supervisor_call(Call, From, Children, ?MODULE),
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(State, Buffer);
|
2015-06-11 17:04:21 +02:00
|
|
|
Msg ->
|
2018-06-28 17:10:18 +02:00
|
|
|
cowboy:log(warning, "Received stray message ~p.", [Msg], Opts),
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(State, Buffer)
|
2017-05-05 13:48:25 +02:00
|
|
|
after InactivityTimeout ->
|
2015-06-11 17:04:21 +02:00
|
|
|
terminate(State, {internal_error, timeout, 'No message or data received before timeout.'})
|
|
|
|
end.
|
|
|
|
|
2025-01-08 16:54:14 +01:00
|
|
|
tick_idle_timeout(State=#state{idle_timeout_num=?IDLE_TIMEOUT_TICKS}, _) ->
|
|
|
|
terminate(State, {stop, timeout,
|
|
|
|
'Connection idle longer than configuration allows.'});
|
|
|
|
tick_idle_timeout(State=#state{idle_timeout_num=TimeoutNum}, Buffer) ->
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(set_idle_timeout(State, TimeoutNum + 1), Buffer).
|
2025-01-08 16:54:14 +01:00
|
|
|
|
|
|
|
set_idle_timeout(State=#state{http2_status=Status, timer=TimerRef}, _)
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
when Status =:= closing_initiated orelse Status =:= closing,
|
|
|
|
TimerRef =/= undefined ->
|
|
|
|
State;
|
2025-01-08 16:54:14 +01:00
|
|
|
set_idle_timeout(State=#state{opts=Opts}, TimeoutNum) ->
|
|
|
|
case maps:get(idle_timeout, Opts, 60000) of
|
|
|
|
infinity ->
|
|
|
|
State#state{timer=undefined};
|
|
|
|
Timeout ->
|
|
|
|
set_timeout(State#state{idle_timeout_num=TimeoutNum},
|
|
|
|
Timeout div ?IDLE_TIMEOUT_TICKS, idle_timeout)
|
|
|
|
end.
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
|
|
|
|
set_timeout(State=#state{timer=TimerRef0}, Timeout, Message) ->
|
2018-11-16 16:30:57 +01:00
|
|
|
ok = case TimerRef0 of
|
|
|
|
undefined -> ok;
|
|
|
|
_ -> erlang:cancel_timer(TimerRef0, [{async, true}, {info, false}])
|
|
|
|
end,
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
TimerRef = case Timeout of
|
2018-11-16 16:30:57 +01:00
|
|
|
infinity -> undefined;
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
Timeout -> erlang:start_timer(Timeout, self(), Message)
|
2018-11-16 16:30:57 +01:00
|
|
|
end,
|
|
|
|
State#state{timer=TimerRef}.
|
|
|
|
|
2021-02-08 16:05:05 -08:00
|
|
|
maybe_reset_idle_timeout(State=#state{opts=Opts}) ->
|
|
|
|
case maps:get(reset_idle_timeout_on_send, Opts, false) of
|
|
|
|
true ->
|
2025-01-08 16:54:14 +01:00
|
|
|
State#state{idle_timeout_num=0};
|
2021-02-08 16:05:05 -08:00
|
|
|
false ->
|
|
|
|
State
|
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% HTTP/2 protocol parsing.
|
|
|
|
|
2019-08-16 16:37:04 +02:00
|
|
|
parse(State=#state{http2_status=sequence}, Data) ->
|
2018-10-26 18:51:32 +02:00
|
|
|
case cow_http2:parse_sequence(Data) of
|
|
|
|
{ok, Rest} ->
|
2019-08-16 16:37:04 +02:00
|
|
|
parse(State#state{http2_status=settings}, Rest);
|
2018-10-26 18:51:32 +02:00
|
|
|
more ->
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(State, Data);
|
2018-10-26 18:51:32 +02:00
|
|
|
Error = {connection_error, _, _} ->
|
|
|
|
terminate(State, Error)
|
2015-06-11 17:04:21 +02:00
|
|
|
end;
|
2019-08-16 16:37:04 +02:00
|
|
|
parse(State=#state{http2_status=Status, http2_machine=HTTP2Machine, streams=Streams}, Data) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
MaxFrameSize = cow_http2_machine:get_local_setting(max_frame_size, HTTP2Machine),
|
2017-02-25 20:05:31 +01:00
|
|
|
case cow_http2:parse(Data, MaxFrameSize) of
|
2015-06-11 17:04:21 +02:00
|
|
|
{ok, Frame, Rest} ->
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
parse(frame_rate(State, Frame), Rest);
|
2017-02-26 13:24:15 +01:00
|
|
|
{ignore, Rest} ->
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
parse(frame_rate(State, ignore), Rest);
|
2015-06-11 17:04:21 +02:00
|
|
|
{stream_error, StreamID, Reason, Human, Rest} ->
|
2018-10-28 11:47:49 +01:00
|
|
|
parse(reset_stream(State, StreamID, {stream_error, Reason, Human}), Rest);
|
2015-06-11 17:04:21 +02:00
|
|
|
Error = {connection_error, _, _} ->
|
|
|
|
terminate(State, Error);
|
2019-08-16 16:37:04 +02:00
|
|
|
%% Terminate the connection if we are closing and all streams have completed.
|
|
|
|
more when Status =:= closing, Streams =:= #{} ->
|
|
|
|
terminate(State, {stop, normal, 'The connection is going away.'});
|
2015-06-11 17:04:21 +02:00
|
|
|
more ->
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(State, Data)
|
2015-06-11 17:04:21 +02:00
|
|
|
end.
|
|
|
|
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
%% Frame rate flood protection.
|
|
|
|
|
2020-11-02 11:12:21 +01:00
|
|
|
frame_rate(State0=#state{frame_rate_num=Num0, frame_rate_time=Time}, Frame) ->
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
{Result, State} = case Num0 - 1 of
|
|
|
|
0 ->
|
|
|
|
CurrentTime = erlang:monotonic_time(millisecond),
|
|
|
|
if
|
|
|
|
CurrentTime < Time ->
|
|
|
|
{error, State0};
|
|
|
|
true ->
|
|
|
|
%% When the option has a period of infinity we cannot reach this clause.
|
2020-11-02 11:12:21 +01:00
|
|
|
{ok, init_frame_rate_limiting(State0, CurrentTime)}
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
end;
|
|
|
|
Num ->
|
|
|
|
{ok, State0#state{frame_rate_num=Num}}
|
|
|
|
end,
|
|
|
|
case {Result, Frame} of
|
|
|
|
{ok, ignore} -> ignored_frame(State);
|
|
|
|
{ok, _} -> frame(State, Frame);
|
|
|
|
{error, _} -> terminate(State, {connection_error, enhance_your_calm,
|
|
|
|
'Frame rate larger than configuration allows. Flood? (CVE-2019-9512, CVE-2019-9515, CVE-2019-9518)'})
|
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% Frames received.
|
|
|
|
|
2019-10-02 10:09:30 +02:00
|
|
|
%% We do nothing when receiving a lingering DATA frame.
|
|
|
|
%% We already removed the stream flow from the connection
|
|
|
|
%% flow and are therefore already accounting for the window
|
|
|
|
%% being reduced by these frames.
|
2018-10-26 10:18:57 +02:00
|
|
|
frame(State=#state{http2_machine=HTTP2Machine0}, Frame) ->
|
|
|
|
case cow_http2_machine:frame(Frame, HTTP2Machine0) of
|
|
|
|
{ok, HTTP2Machine} ->
|
2018-10-27 00:16:13 +02:00
|
|
|
maybe_ack(State#state{http2_machine=HTTP2Machine}, Frame);
|
2018-10-26 10:18:57 +02:00
|
|
|
{ok, {data, StreamID, IsFin, Data}, HTTP2Machine} ->
|
|
|
|
data_frame(State#state{http2_machine=HTTP2Machine}, StreamID, IsFin, Data);
|
|
|
|
{ok, {headers, StreamID, IsFin, Headers, PseudoHeaders, BodyLen}, HTTP2Machine} ->
|
|
|
|
headers_frame(State#state{http2_machine=HTTP2Machine},
|
|
|
|
StreamID, IsFin, Headers, PseudoHeaders, BodyLen);
|
|
|
|
{ok, {trailers, _StreamID, _Trailers}, HTTP2Machine} ->
|
|
|
|
%% @todo Propagate trailers.
|
|
|
|
State#state{http2_machine=HTTP2Machine};
|
|
|
|
{ok, {rst_stream, StreamID, Reason}, HTTP2Machine} ->
|
|
|
|
rst_stream_frame(State#state{http2_machine=HTTP2Machine}, StreamID, Reason);
|
2019-08-16 16:37:04 +02:00
|
|
|
{ok, GoAway={goaway, _, _, _}, HTTP2Machine} ->
|
|
|
|
goaway(State#state{http2_machine=HTTP2Machine}, GoAway);
|
2018-10-26 10:18:57 +02:00
|
|
|
{send, SendData, HTTP2Machine} ->
|
2019-09-13 14:20:04 +02:00
|
|
|
%% We may need to send an alarm for each of the streams sending data.
|
2021-02-08 16:05:05 -08:00
|
|
|
State1 = lists:foldl(
|
2019-09-13 14:20:04 +02:00
|
|
|
fun({StreamID, _, _}, S) -> maybe_send_data_alarm(S, HTTP2Machine0, StreamID) end,
|
2020-01-02 13:29:56 +01:00
|
|
|
send_data(maybe_ack(State#state{http2_machine=HTTP2Machine}, Frame), SendData, []),
|
2021-02-08 16:05:05 -08:00
|
|
|
SendData),
|
|
|
|
maybe_reset_idle_timeout(State1);
|
2018-10-26 10:18:57 +02:00
|
|
|
{error, {stream_error, StreamID, Reason, Human}, HTTP2Machine} ->
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State#state{http2_machine=HTTP2Machine},
|
2018-10-26 10:18:57 +02:00
|
|
|
StreamID, {stream_error, Reason, Human});
|
|
|
|
{error, Error={connection_error, _, _}, HTTP2Machine} ->
|
|
|
|
terminate(State#state{http2_machine=HTTP2Machine}, Error)
|
|
|
|
end.
|
|
|
|
|
2019-08-16 16:37:04 +02:00
|
|
|
%% We use this opportunity to mark the HTTP/2 status as connected
|
|
|
|
%% if we were still waiting for a SETTINGS frame.
|
|
|
|
maybe_ack(State=#state{http2_status=settings}, Frame) ->
|
|
|
|
maybe_ack(State#state{http2_status=connected}, Frame);
|
2021-02-08 16:05:05 -08:00
|
|
|
%% We do not reset the idle timeout on send here because we are
|
|
|
|
%% sending data as a consequence of receiving data, which means
|
|
|
|
%% we already resetted the idle timeout.
|
2018-10-26 10:18:57 +02:00
|
|
|
maybe_ack(State=#state{socket=Socket, transport=Transport}, Frame) ->
|
|
|
|
case Frame of
|
2023-12-12 12:05:54 +01:00
|
|
|
{settings, _} ->
|
|
|
|
ok = maybe_socket_error(State, Transport:send(Socket, cow_http2:settings_ack()));
|
|
|
|
{ping, Opaque} ->
|
|
|
|
ok = maybe_socket_error(State, Transport:send(Socket, cow_http2:ping_ack(Opaque)));
|
2018-10-26 10:18:57 +02:00
|
|
|
_ -> ok
|
|
|
|
end,
|
|
|
|
State.
|
|
|
|
|
2021-02-08 16:05:05 -08:00
|
|
|
data_frame(State0=#state{opts=Opts, flow=Flow0, streams=Streams}, StreamID, IsFin, Data) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
case Streams of
|
2019-09-02 14:48:28 +02:00
|
|
|
#{StreamID := Stream=#stream{status=running, flow=StreamFlow, state=StreamState0}} ->
|
2018-10-26 10:18:57 +02:00
|
|
|
try cowboy_stream:data(StreamID, IsFin, Data, StreamState0) of
|
|
|
|
{Commands, StreamState} ->
|
2019-09-02 14:48:28 +02:00
|
|
|
%% Remove the amount of data received from the flow.
|
|
|
|
%% We may receive more data than we requested. We ensure
|
|
|
|
%% that the flow value doesn't go lower than 0.
|
|
|
|
Size = byte_size(Data),
|
2021-02-08 16:05:05 -08:00
|
|
|
Flow = max(0, Flow0 - Size),
|
|
|
|
%% We would normally update the window when changing the flow
|
|
|
|
%% value. But because we are running commands, which themselves
|
|
|
|
%% may update the window, and we want to avoid updating the
|
|
|
|
%% window twice in a row, we first run the commands and then
|
|
|
|
%% only update the window a flow command was executed. We know
|
|
|
|
%% that it was because the flow value changed in the state.
|
|
|
|
State1 = State0#state{flow=Flow,
|
2019-09-02 14:48:28 +02:00
|
|
|
streams=Streams#{StreamID => Stream#stream{
|
|
|
|
flow=max(0, StreamFlow - Size), state=StreamState}}},
|
2021-02-08 16:05:05 -08:00
|
|
|
State = commands(State1, StreamID, Commands),
|
|
|
|
case State of
|
|
|
|
%% No flow command was executed. We must update the window
|
|
|
|
%% because we changed the flow value earlier.
|
|
|
|
#state{flow=Flow} ->
|
|
|
|
update_window(State, StreamID);
|
|
|
|
%% Otherwise the window was updated already.
|
|
|
|
_ ->
|
|
|
|
State
|
|
|
|
end
|
2019-12-31 15:10:38 +01:00
|
|
|
catch Class:Exception:Stacktrace ->
|
2018-10-26 10:18:57 +02:00
|
|
|
cowboy:log(cowboy_stream:make_error_log(data,
|
|
|
|
[StreamID, IsFin, Data, StreamState0],
|
2019-12-31 15:10:38 +01:00
|
|
|
Class, Exception, Stacktrace), Opts),
|
2019-09-02 14:48:28 +02:00
|
|
|
reset_stream(State0, StreamID, {internal_error, {Class, Exception},
|
2018-10-26 10:18:57 +02:00
|
|
|
'Unhandled exception in cowboy_stream:data/4.'})
|
|
|
|
end;
|
|
|
|
%% We ignore DATA frames for streams that are stopping.
|
|
|
|
#{} ->
|
2019-09-02 14:48:28 +02:00
|
|
|
State0
|
2018-10-26 10:18:57 +02:00
|
|
|
end.
|
2015-06-11 17:04:21 +02:00
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
headers_frame(State, StreamID, IsFin, Headers,
|
|
|
|
PseudoHeaders=#{method := <<"CONNECT">>}, _)
|
|
|
|
when map_size(PseudoHeaders) =:= 2 ->
|
|
|
|
early_error(State, StreamID, IsFin, Headers, PseudoHeaders, 501,
|
|
|
|
'The CONNECT method is currently not implemented. (RFC7231 4.3.6)');
|
|
|
|
headers_frame(State, StreamID, IsFin, Headers,
|
|
|
|
PseudoHeaders=#{method := <<"TRACE">>}, _) ->
|
|
|
|
early_error(State, StreamID, IsFin, Headers, PseudoHeaders, 501,
|
|
|
|
'The TRACE method is currently not implemented. (RFC7231 4.3.8)');
|
2019-01-22 17:50:40 +01:00
|
|
|
headers_frame(State, StreamID, IsFin, Headers, PseudoHeaders=#{authority := Authority}, BodyLen) ->
|
|
|
|
headers_frame_parse_host(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen, Authority);
|
|
|
|
headers_frame(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen) ->
|
|
|
|
case lists:keyfind(<<"host">>, 1, Headers) of
|
|
|
|
{_, Authority} ->
|
|
|
|
headers_frame_parse_host(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen, Authority);
|
|
|
|
_ ->
|
|
|
|
reset_stream(State, StreamID, {stream_error, protocol_error,
|
|
|
|
'Requests translated from HTTP/1.1 must include a host header. (RFC7540 8.1.2.3, RFC7230 5.4)'})
|
|
|
|
end.
|
|
|
|
|
|
|
|
headers_frame_parse_host(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert, proxy_header=ProxyHeader},
|
|
|
|
StreamID, IsFin, Headers, PseudoHeaders=#{method := Method, scheme := Scheme, path := PathWithQs},
|
|
|
|
BodyLen, Authority) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
try cow_http_hd:parse_host(Authority) of
|
|
|
|
{Host, Port0} ->
|
|
|
|
Port = ensure_port(Scheme, Port0),
|
|
|
|
try cow_http:parse_fullpath(PathWithQs) of
|
|
|
|
{<<>>, _} ->
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State, StreamID, {stream_error, protocol_error,
|
2018-10-28 11:42:18 +01:00
|
|
|
'The path component must not be empty. (RFC7540 8.1.2.3)'});
|
2018-10-26 10:18:57 +02:00
|
|
|
{Path, Qs} ->
|
|
|
|
Req0 = #{
|
|
|
|
ref => Ref,
|
|
|
|
pid => self(),
|
|
|
|
streamid => StreamID,
|
|
|
|
peer => Peer,
|
|
|
|
sock => Sock,
|
|
|
|
cert => Cert,
|
|
|
|
method => Method,
|
|
|
|
scheme => Scheme,
|
|
|
|
host => Host,
|
|
|
|
port => Port,
|
|
|
|
path => Path,
|
|
|
|
qs => Qs,
|
|
|
|
version => 'HTTP/2',
|
|
|
|
headers => headers_to_map(Headers, #{}),
|
|
|
|
has_body => IsFin =:= nofin,
|
|
|
|
body_length => BodyLen
|
|
|
|
},
|
2018-10-30 23:30:05 +01:00
|
|
|
%% We add the PROXY header information if any.
|
|
|
|
Req1 = case ProxyHeader of
|
|
|
|
undefined -> Req0;
|
|
|
|
_ -> Req0#{proxy_header => ProxyHeader}
|
|
|
|
end,
|
2018-10-26 10:18:57 +02:00
|
|
|
%% We add the protocol information for extended CONNECTs.
|
|
|
|
Req = case PseudoHeaders of
|
2018-10-30 23:30:05 +01:00
|
|
|
#{protocol := Protocol} -> Req1#{protocol => Protocol};
|
|
|
|
_ -> Req1
|
2018-10-26 10:18:57 +02:00
|
|
|
end,
|
|
|
|
headers_frame(State, StreamID, Req)
|
|
|
|
catch _:_ ->
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State, StreamID, {stream_error, protocol_error,
|
2018-10-28 11:42:18 +01:00
|
|
|
'The :path pseudo-header is invalid. (RFC7540 8.1.2.3)'})
|
2018-10-26 10:18:57 +02:00
|
|
|
end
|
|
|
|
catch _:_ ->
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State, StreamID, {stream_error, protocol_error,
|
2018-10-28 11:42:18 +01:00
|
|
|
'The :authority pseudo-header is invalid. (RFC7540 8.1.2.3)'})
|
2018-04-30 13:47:33 +02:00
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
ensure_port(<<"http">>, undefined) -> 80;
|
|
|
|
ensure_port(<<"https">>, undefined) -> 443;
|
|
|
|
ensure_port(_, Port) -> Port.
|
|
|
|
|
|
|
|
%% This function is necessary to properly handle duplicate headers
|
|
|
|
%% and the special-case cookie header.
|
|
|
|
headers_to_map([], Acc) ->
|
|
|
|
Acc;
|
|
|
|
headers_to_map([{Name, Value}|Tail], Acc0) ->
|
|
|
|
Acc = case Acc0 of
|
|
|
|
%% The cookie header does not use proper HTTP header lists.
|
|
|
|
#{Name := Value0} when Name =:= <<"cookie">> ->
|
|
|
|
Acc0#{Name => << Value0/binary, "; ", Value/binary >>};
|
|
|
|
#{Name := Value0} ->
|
|
|
|
Acc0#{Name => << Value0/binary, ", ", Value/binary >>};
|
|
|
|
_ ->
|
|
|
|
Acc0#{Name => Value}
|
|
|
|
end,
|
|
|
|
headers_to_map(Tail, Acc).
|
|
|
|
|
|
|
|
headers_frame(State=#state{opts=Opts, streams=Streams}, StreamID, Req) ->
|
|
|
|
try cowboy_stream:init(StreamID, Req, Opts) of
|
2018-04-30 13:47:33 +02:00
|
|
|
{Commands, StreamState} ->
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State#state{
|
2019-09-02 14:48:28 +02:00
|
|
|
streams=Streams#{StreamID => #stream{state=StreamState}}},
|
2018-10-26 10:18:57 +02:00
|
|
|
StreamID, Commands)
|
2019-12-31 15:10:38 +01:00
|
|
|
catch Class:Exception:Stacktrace ->
|
2018-10-26 10:18:57 +02:00
|
|
|
cowboy:log(cowboy_stream:make_error_log(init,
|
|
|
|
[StreamID, Req, Opts],
|
2019-12-31 15:10:38 +01:00
|
|
|
Class, Exception, Stacktrace), Opts),
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State, StreamID, {internal_error, {Class, Exception},
|
2018-10-26 10:18:57 +02:00
|
|
|
'Unhandled exception in cowboy_stream:init/3.'})
|
|
|
|
end.
|
|
|
|
|
|
|
|
early_error(State0=#state{ref=Ref, opts=Opts, peer=Peer},
|
2019-10-03 15:42:58 +02:00
|
|
|
StreamID, _IsFin, Headers, #{method := Method},
|
2018-10-26 10:18:57 +02:00
|
|
|
StatusCode0, HumanReadable) ->
|
|
|
|
%% We automatically terminate the stream but it is not an error
|
|
|
|
%% per se (at least not in the first implementation).
|
|
|
|
Reason = {stream_error, no_error, HumanReadable},
|
|
|
|
%% The partial Req is minimal for now. We only have one case
|
|
|
|
%% where it can be called (when a method is completely disabled).
|
|
|
|
%% @todo Fill in the other elements.
|
|
|
|
PartialReq = #{
|
|
|
|
ref => Ref,
|
|
|
|
peer => Peer,
|
2019-10-03 15:42:58 +02:00
|
|
|
method => Method,
|
|
|
|
headers => headers_to_map(Headers, #{})
|
2018-10-26 10:18:57 +02:00
|
|
|
},
|
|
|
|
Resp = {response, StatusCode0, RespHeaders0=#{<<"content-length">> => <<"0">>}, <<>>},
|
|
|
|
try cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts) of
|
|
|
|
{response, StatusCode, RespHeaders, RespBody} ->
|
|
|
|
send_response(State0, StreamID, StatusCode, RespHeaders, RespBody)
|
2019-12-31 15:10:38 +01:00
|
|
|
catch Class:Exception:Stacktrace ->
|
2018-10-26 10:18:57 +02:00
|
|
|
cowboy:log(cowboy_stream:make_error_log(early_error,
|
|
|
|
[StreamID, Reason, PartialReq, Resp, Opts],
|
2019-12-31 15:10:38 +01:00
|
|
|
Class, Exception, Stacktrace), Opts),
|
2018-10-26 10:18:57 +02:00
|
|
|
%% We still need to send an error response, so send what we initially
|
|
|
|
%% wanted to send. It's better than nothing.
|
|
|
|
send_headers(State0, StreamID, fin, StatusCode0, RespHeaders0)
|
2018-04-30 13:47:33 +02:00
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
rst_stream_frame(State=#state{streams=Streams0, children=Children0}, StreamID, Reason) ->
|
|
|
|
case maps:take(StreamID, Streams0) of
|
2019-09-02 14:48:28 +02:00
|
|
|
{#stream{state=StreamState}, Streams} ->
|
2018-10-28 11:47:49 +01:00
|
|
|
terminate_stream_handler(State, StreamID, Reason, StreamState),
|
2018-10-26 10:18:57 +02:00
|
|
|
Children = cowboy_children:shutdown(Children0, StreamID),
|
2023-10-31 11:51:02 +01:00
|
|
|
cancel_rate_limit(State#state{streams=Streams, children=Children});
|
2018-10-26 10:18:57 +02:00
|
|
|
error ->
|
|
|
|
State
|
|
|
|
end.
|
2018-04-30 13:47:33 +02:00
|
|
|
|
2023-10-31 11:51:02 +01:00
|
|
|
cancel_rate_limit(State0=#state{cancel_rate_num=Num0, cancel_rate_time=Time}) ->
|
|
|
|
case Num0 - 1 of
|
|
|
|
0 ->
|
|
|
|
CurrentTime = erlang:monotonic_time(millisecond),
|
|
|
|
if
|
|
|
|
CurrentTime < Time ->
|
|
|
|
terminate(State0, {connection_error, enhance_your_calm,
|
|
|
|
'Stream cancel rate larger than configuration allows. Flood? (CVE-2023-44487)'});
|
|
|
|
true ->
|
|
|
|
%% When the option has a period of infinity we cannot reach this clause.
|
|
|
|
init_cancel_rate_limiting(State0, CurrentTime)
|
|
|
|
end;
|
|
|
|
Num ->
|
|
|
|
State0#state{cancel_rate_num=Num}
|
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
ignored_frame(State=#state{http2_machine=HTTP2Machine0}) ->
|
|
|
|
case cow_http2_machine:ignored_frame(HTTP2Machine0) of
|
|
|
|
{ok, HTTP2Machine} ->
|
|
|
|
State#state{http2_machine=HTTP2Machine};
|
|
|
|
{error, Error={connection_error, _, _}, HTTP2Machine} ->
|
|
|
|
terminate(State#state{http2_machine=HTTP2Machine}, Error)
|
|
|
|
end.
|
|
|
|
|
2018-10-27 00:16:13 +02:00
|
|
|
%% HTTP/2 timeouts.
|
|
|
|
|
2018-10-27 10:07:34 +02:00
|
|
|
timeout(State=#state{http2_machine=HTTP2Machine0}, Name, TRef) ->
|
2018-10-27 00:16:13 +02:00
|
|
|
case cow_http2_machine:timeout(Name, TRef, HTTP2Machine0) of
|
|
|
|
{ok, HTTP2Machine} ->
|
|
|
|
State#state{http2_machine=HTTP2Machine};
|
|
|
|
{error, Error={connection_error, _, _}, HTTP2Machine} ->
|
|
|
|
terminate(State#state{http2_machine=HTTP2Machine}, Error)
|
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% Erlang messages.
|
2015-06-11 17:04:21 +02:00
|
|
|
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
down(State0=#state{opts=Opts, children=Children0}, Pid, Msg) ->
|
|
|
|
State = case cowboy_children:down(Children0, Pid) of
|
2017-08-08 16:59:33 +02:00
|
|
|
%% The stream was terminated already.
|
|
|
|
{ok, undefined, Children} ->
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
State0#state{children=Children};
|
2017-08-08 16:59:33 +02:00
|
|
|
%% The stream is still running.
|
|
|
|
{ok, StreamID, Children} ->
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
info(State0#state{children=Children}, StreamID, Msg);
|
2017-08-08 16:59:33 +02:00
|
|
|
%% The process was unknown.
|
|
|
|
error ->
|
2018-06-28 17:10:18 +02:00
|
|
|
cowboy:log(warning, "Received EXIT signal ~p for unknown process ~p.~n",
|
|
|
|
[Msg, Pid], Opts),
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
State0
|
|
|
|
end,
|
|
|
|
if
|
|
|
|
State#state.http2_status =:= closing, State#state.streams =:= #{} ->
|
|
|
|
terminate(State, {stop, normal, 'The connection is going away.'});
|
|
|
|
true ->
|
2015-06-11 17:04:21 +02:00
|
|
|
State
|
|
|
|
end.
|
|
|
|
|
2019-10-10 17:06:24 +02:00
|
|
|
info(State=#state{opts=Opts, http2_machine=HTTP2Machine, streams=Streams}, StreamID, Msg) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
case Streams of
|
2019-09-02 14:48:28 +02:00
|
|
|
#{StreamID := Stream=#stream{state=StreamState0}} ->
|
2017-01-16 14:22:43 +01:00
|
|
|
try cowboy_stream:info(StreamID, Msg, StreamState0) of
|
2015-06-11 17:04:21 +02:00
|
|
|
{Commands, StreamState} ->
|
2019-09-02 14:48:28 +02:00
|
|
|
commands(State#state{streams=Streams#{StreamID => Stream#stream{state=StreamState}}},
|
2018-10-26 10:18:57 +02:00
|
|
|
StreamID, Commands)
|
2019-12-31 15:10:38 +01:00
|
|
|
catch Class:Exception:Stacktrace ->
|
2018-06-28 17:10:18 +02:00
|
|
|
cowboy:log(cowboy_stream:make_error_log(info,
|
2017-09-21 12:53:21 +02:00
|
|
|
[StreamID, Msg, StreamState0],
|
2019-12-31 15:10:38 +01:00
|
|
|
Class, Exception, Stacktrace), Opts),
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State, StreamID, {internal_error, {Class, Exception},
|
2017-09-27 14:17:27 +02:00
|
|
|
'Unhandled exception in cowboy_stream:info/3.'})
|
2015-06-11 17:04:21 +02:00
|
|
|
end;
|
2018-10-26 10:18:57 +02:00
|
|
|
_ ->
|
2019-10-10 17:06:24 +02:00
|
|
|
case cow_http2_machine:is_lingering_stream(StreamID, HTTP2Machine) of
|
|
|
|
true ->
|
|
|
|
ok;
|
|
|
|
false ->
|
|
|
|
cowboy:log(warning, "Received message ~p for unknown stream ~p.",
|
|
|
|
[Msg, StreamID], Opts)
|
|
|
|
end,
|
2015-06-11 17:04:21 +02:00
|
|
|
State
|
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% Stream handler commands.
|
|
|
|
%%
|
2018-04-30 18:39:27 +02:00
|
|
|
%% @todo Kill the stream if it tries to send a response, headers,
|
|
|
|
%% data or push promise when the stream is closed or half-closed.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State, _, []) ->
|
|
|
|
State;
|
2016-08-10 17:15:02 +02:00
|
|
|
%% Error responses are sent only if a response wasn't sent already.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State=#state{http2_machine=HTTP2Machine}, StreamID,
|
|
|
|
[{error_response, StatusCode, Headers, Body}|Tail]) ->
|
|
|
|
case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine) of
|
|
|
|
{ok, idle, _} ->
|
|
|
|
commands(State, StreamID, [{response, StatusCode, Headers, Body}|Tail]);
|
|
|
|
_ ->
|
|
|
|
commands(State, StreamID, Tail)
|
|
|
|
end;
|
2017-10-29 19:52:27 +00:00
|
|
|
%% Send an informational response.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0, StreamID, [{inform, StatusCode, Headers}|Tail]) ->
|
2021-02-08 16:05:05 -08:00
|
|
|
State1 = send_headers(State0, StreamID, idle, StatusCode, Headers),
|
|
|
|
State = maybe_reset_idle_timeout(State1),
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State, StreamID, Tail);
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Send response headers.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0, StreamID, [{response, StatusCode, Headers, Body}|Tail]) ->
|
2021-02-08 16:05:05 -08:00
|
|
|
State1 = send_response(State0, StreamID, StatusCode, Headers, Body),
|
|
|
|
State = maybe_reset_idle_timeout(State1),
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State, StreamID, Tail);
|
2017-09-25 13:52:58 +02:00
|
|
|
%% Send response headers.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0, StreamID, [{headers, StatusCode, Headers}|Tail]) ->
|
2021-02-08 16:05:05 -08:00
|
|
|
State1 = send_headers(State0, StreamID, nofin, StatusCode, Headers),
|
|
|
|
State = maybe_reset_idle_timeout(State1),
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State, StreamID, Tail);
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Send a response body chunk.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0, StreamID, [{data, IsFin, Data}|Tail]) ->
|
2021-02-08 16:05:05 -08:00
|
|
|
State = case maybe_send_data(State0, StreamID, IsFin, Data, []) of
|
|
|
|
{data_sent, State1} ->
|
|
|
|
maybe_reset_idle_timeout(State1);
|
|
|
|
{no_data_sent, State1} ->
|
|
|
|
State1
|
|
|
|
end,
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State, StreamID, Tail);
|
2017-11-15 14:58:49 +01:00
|
|
|
%% Send trailers.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0, StreamID, [{trailers, Trailers}|Tail]) ->
|
2021-02-08 16:05:05 -08:00
|
|
|
State = case maybe_send_data(State0, StreamID, fin,
|
|
|
|
{trailers, maps:to_list(Trailers)}, []) of
|
|
|
|
{data_sent, State1} ->
|
|
|
|
maybe_reset_idle_timeout(State1);
|
|
|
|
{no_data_sent, State1} ->
|
|
|
|
State1
|
|
|
|
end,
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State, StreamID, Tail);
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Send a push promise.
|
|
|
|
%%
|
2018-10-26 10:18:57 +02:00
|
|
|
%% @todo Responses sent as a result of a push_promise request
|
|
|
|
%% must not send push_promise frames themselves.
|
2019-08-16 16:37:04 +02:00
|
|
|
%%
|
|
|
|
%% @todo We should not send push_promise frames when we are
|
|
|
|
%% in the closing http2_status.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0=#state{socket=Socket, transport=Transport, http2_machine=HTTP2Machine0},
|
|
|
|
StreamID, [{push, Method, Scheme, Host, Port, Path, Qs, Headers0}|Tail]) ->
|
2016-08-10 11:49:31 +02:00
|
|
|
Authority = case {Scheme, Port} of
|
|
|
|
{<<"http">>, 80} -> Host;
|
|
|
|
{<<"https">>, 443} -> Host;
|
2017-07-26 17:31:12 +02:00
|
|
|
_ -> iolist_to_binary([Host, $:, integer_to_binary(Port)])
|
2016-08-10 11:49:31 +02:00
|
|
|
end,
|
2017-11-30 15:01:01 +01:00
|
|
|
PathWithQs = iolist_to_binary(case Qs of
|
2016-08-10 11:49:31 +02:00
|
|
|
<<>> -> Path;
|
|
|
|
_ -> [Path, $?, Qs]
|
2017-11-30 15:01:01 +01:00
|
|
|
end),
|
2018-10-26 10:18:57 +02:00
|
|
|
PseudoHeaders = #{
|
|
|
|
method => Method,
|
|
|
|
scheme => Scheme,
|
|
|
|
authority => Authority,
|
|
|
|
path => PathWithQs
|
|
|
|
},
|
2017-07-26 17:31:12 +02:00
|
|
|
%% We need to make sure the header value is binary before we can
|
2018-10-26 10:18:57 +02:00
|
|
|
%% create the Req object, as it expects them to be flat.
|
|
|
|
Headers = maps:to_list(maps:map(fun(_, V) -> iolist_to_binary(V) end, Headers0)),
|
|
|
|
State = case cow_http2_machine:prepare_push_promise(StreamID, HTTP2Machine0,
|
|
|
|
PseudoHeaders, Headers) of
|
|
|
|
{ok, PromisedStreamID, HeaderBlock, HTTP2Machine} ->
|
2023-12-12 12:05:54 +01:00
|
|
|
State1 = State0#state{http2_machine=HTTP2Machine},
|
|
|
|
ok = maybe_socket_error(State1, Transport:send(Socket,
|
|
|
|
cow_http2:push_promise(StreamID, PromisedStreamID, HeaderBlock))),
|
2021-02-08 16:05:05 -08:00
|
|
|
State2 = maybe_reset_idle_timeout(State1),
|
|
|
|
headers_frame(State2, PromisedStreamID, fin, Headers, PseudoHeaders, 0);
|
2018-10-26 10:18:57 +02:00
|
|
|
{error, no_push} ->
|
|
|
|
State0
|
|
|
|
end,
|
|
|
|
commands(State, StreamID, Tail);
|
2019-09-02 14:48:28 +02:00
|
|
|
%% Read the request body.
|
|
|
|
commands(State0=#state{flow=Flow, streams=Streams}, StreamID, [{flow, Size}|Tail]) ->
|
|
|
|
#{StreamID := Stream=#stream{flow=StreamFlow}} = Streams,
|
|
|
|
State = update_window(State0#state{flow=Flow + Size,
|
|
|
|
streams=Streams#{StreamID => Stream#stream{flow=StreamFlow + Size}}},
|
|
|
|
StreamID),
|
|
|
|
commands(State, StreamID, Tail);
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Supervise a child process.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State=#state{children=Children}, StreamID, [{spawn, Pid, Shutdown}|Tail]) ->
|
2017-08-08 16:59:33 +02:00
|
|
|
commands(State#state{children=cowboy_children:up(Children, Pid, StreamID, Shutdown)},
|
2018-10-26 10:18:57 +02:00
|
|
|
StreamID, Tail);
|
2016-06-06 17:28:35 +02:00
|
|
|
%% Error handling.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State, StreamID, [Error = {internal_error, _, _}|_Tail]) ->
|
2016-06-16 19:46:57 +02:00
|
|
|
%% @todo Do we want to run the commands after an internal_error?
|
|
|
|
%% @todo Do we even allow commands after?
|
2016-06-06 17:28:35 +02:00
|
|
|
%% @todo Only reset when the stream still exists.
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State, StreamID, Error);
|
2017-04-18 14:06:34 +02:00
|
|
|
%% Upgrade to HTTP/2. This is triggered by cowboy_http2 itself.
|
2021-02-08 16:05:05 -08:00
|
|
|
%%
|
|
|
|
%% We do not need to reset the idle timeout on send because it
|
|
|
|
%% hasn't been set yet. This is called from init/12.
|
2019-08-16 16:37:04 +02:00
|
|
|
commands(State=#state{socket=Socket, transport=Transport, http2_status=upgrade},
|
2018-10-26 10:18:57 +02:00
|
|
|
StreamID, [{switch_protocol, Headers, ?MODULE, _}|Tail]) ->
|
|
|
|
%% @todo This 101 response needs to be passed through stream handlers.
|
2023-12-12 12:05:54 +01:00
|
|
|
ok = maybe_socket_error(State, Transport:send(Socket,
|
|
|
|
cow_http:response(101, 'HTTP/1.1', maps:to_list(Headers)))),
|
2018-10-27 00:16:13 +02:00
|
|
|
commands(State, StreamID, Tail);
|
2018-04-04 17:23:37 +02:00
|
|
|
%% Use a different protocol within the stream (CONNECT :protocol).
|
|
|
|
%% @todo Make sure we error out when the feature is disabled.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0, StreamID, [{switch_protocol, Headers, _Mod, _ModState}|Tail]) ->
|
|
|
|
State = info(State0, StreamID, {headers, 200, Headers}),
|
|
|
|
commands(State, StreamID, Tail);
|
2018-11-16 13:09:01 +01:00
|
|
|
%% Set options dynamically.
|
|
|
|
commands(State, StreamID, [{set_options, _Opts}|Tail]) ->
|
|
|
|
commands(State, StreamID, Tail);
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State, StreamID, [stop|_Tail]) ->
|
2016-03-12 18:25:35 +01:00
|
|
|
%% @todo Do we want to run the commands after a stop?
|
2016-06-16 19:46:57 +02:00
|
|
|
%% @todo Do we even allow commands after?
|
2018-10-26 10:18:57 +02:00
|
|
|
stop_stream(State, StreamID);
|
2018-06-28 17:10:18 +02:00
|
|
|
%% Log event.
|
|
|
|
commands(State=#state{opts=Opts}, StreamID, [Log={log, _, _, _}|Tail]) ->
|
|
|
|
cowboy:log(Log, Opts),
|
|
|
|
commands(State, StreamID, Tail).
|
2016-06-16 19:46:57 +02:00
|
|
|
|
2019-09-02 14:48:28 +02:00
|
|
|
%% Tentatively update the window after the flow was updated.
|
|
|
|
|
2023-12-12 12:05:54 +01:00
|
|
|
update_window(State0=#state{socket=Socket, transport=Transport,
|
2019-09-02 14:48:28 +02:00
|
|
|
http2_machine=HTTP2Machine0, flow=Flow, streams=Streams}, StreamID) ->
|
|
|
|
{Data1, HTTP2Machine2} = case cow_http2_machine:ensure_window(Flow, HTTP2Machine0) of
|
|
|
|
ok -> {<<>>, HTTP2Machine0};
|
|
|
|
{ok, Increment1, HTTP2Machine1} -> {cow_http2:window_update(Increment1), HTTP2Machine1}
|
|
|
|
end,
|
2023-12-21 15:26:21 +01:00
|
|
|
{Data2, HTTP2Machine} = case Streams of
|
|
|
|
#{StreamID := #stream{flow=StreamFlow}} ->
|
|
|
|
case cow_http2_machine:ensure_window(StreamID, StreamFlow, HTTP2Machine2) of
|
|
|
|
ok ->
|
|
|
|
{<<>>, HTTP2Machine2};
|
|
|
|
{ok, Increment2, HTTP2Machine3} ->
|
|
|
|
{cow_http2:window_update(StreamID, Increment2), HTTP2Machine3}
|
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
%% Don't update the stream's window if it stopped.
|
|
|
|
{<<>>, HTTP2Machine2}
|
2019-09-02 14:48:28 +02:00
|
|
|
end,
|
2023-12-12 12:05:54 +01:00
|
|
|
State = State0#state{http2_machine=HTTP2Machine},
|
2019-09-02 14:48:28 +02:00
|
|
|
case {Data1, Data2} of
|
2021-02-08 16:05:05 -08:00
|
|
|
{<<>>, <<>>} ->
|
|
|
|
State;
|
|
|
|
_ ->
|
|
|
|
ok = maybe_socket_error(State, Transport:send(Socket, [Data1, Data2])),
|
|
|
|
maybe_reset_idle_timeout(State)
|
|
|
|
end.
|
2019-09-02 14:48:28 +02:00
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% Send the response, trailers or data.
|
2017-05-19 20:18:00 +02:00
|
|
|
|
2020-01-02 13:29:56 +01:00
|
|
|
send_response(State0=#state{http2_machine=HTTP2Machine0}, StreamID, StatusCode, Headers, Body) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
Size = case Body of
|
|
|
|
{sendfile, _, Bytes, _} -> Bytes;
|
|
|
|
_ -> iolist_size(Body)
|
|
|
|
end,
|
|
|
|
case Size of
|
|
|
|
0 ->
|
|
|
|
State = send_headers(State0, StreamID, fin, StatusCode, Headers),
|
|
|
|
maybe_terminate_stream(State, StreamID, fin);
|
2017-10-23 14:49:33 +01:00
|
|
|
_ ->
|
2020-01-02 13:29:56 +01:00
|
|
|
%% @todo Add a test for HEAD to make sure we don't send the body when
|
|
|
|
%% returning {response...} from a stream handler (or {headers...} then {data...}).
|
|
|
|
{ok, _IsFin, HeaderBlock, HTTP2Machine}
|
|
|
|
= cow_http2_machine:prepare_headers(StreamID, HTTP2Machine0, nofin,
|
|
|
|
#{status => cow_http:status_to_integer(StatusCode)},
|
|
|
|
headers_to_list(Headers)),
|
2021-02-08 16:05:05 -08:00
|
|
|
{_, State} = maybe_send_data(State0#state{http2_machine=HTTP2Machine},
|
|
|
|
StreamID, fin, Body, [cow_http2:headers(StreamID, nofin, HeaderBlock)]),
|
|
|
|
State
|
2017-10-23 14:49:33 +01:00
|
|
|
end.
|
2017-05-19 20:18:00 +02:00
|
|
|
|
2023-12-12 12:05:54 +01:00
|
|
|
send_headers(State0=#state{socket=Socket, transport=Transport,
|
2018-10-26 10:18:57 +02:00
|
|
|
http2_machine=HTTP2Machine0}, StreamID, IsFin0, StatusCode, Headers) ->
|
|
|
|
{ok, IsFin, HeaderBlock, HTTP2Machine}
|
|
|
|
= cow_http2_machine:prepare_headers(StreamID, HTTP2Machine0, IsFin0,
|
|
|
|
#{status => cow_http:status_to_integer(StatusCode)},
|
|
|
|
headers_to_list(Headers)),
|
2023-12-12 12:05:54 +01:00
|
|
|
State = State0#state{http2_machine=HTTP2Machine},
|
|
|
|
ok = maybe_socket_error(State, Transport:send(Socket,
|
|
|
|
cow_http2:headers(StreamID, IsFin, HeaderBlock))),
|
|
|
|
State.
|
2017-08-21 16:55:30 +02:00
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% The set-cookie header is special; we can only send one cookie per header.
|
|
|
|
headers_to_list(Headers0=#{<<"set-cookie">> := SetCookies}) ->
|
|
|
|
Headers = maps:to_list(maps:remove(<<"set-cookie">>, Headers0)),
|
|
|
|
Headers ++ [{<<"set-cookie">>, Value} || Value <- SetCookies];
|
|
|
|
headers_to_list(Headers) ->
|
|
|
|
maps:to_list(Headers).
|
|
|
|
|
2020-01-02 13:29:56 +01:00
|
|
|
maybe_send_data(State0=#state{socket=Socket, transport=Transport,
|
|
|
|
http2_machine=HTTP2Machine0}, StreamID, IsFin, Data0, Prefix) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
Data = case is_tuple(Data0) of
|
|
|
|
false -> {data, Data0};
|
|
|
|
true -> Data0
|
|
|
|
end,
|
|
|
|
case cow_http2_machine:send_or_queue_data(StreamID, HTTP2Machine0, IsFin, Data) of
|
|
|
|
{ok, HTTP2Machine} ->
|
2023-12-12 12:05:54 +01:00
|
|
|
State1 = State0#state{http2_machine=HTTP2Machine},
|
2020-01-02 13:29:56 +01:00
|
|
|
%% If we have prefix data (like a HEADERS frame) we need to send it
|
|
|
|
%% even if we do not send any DATA frames.
|
2021-02-08 16:05:05 -08:00
|
|
|
WasDataSent = case Prefix of
|
|
|
|
[] ->
|
|
|
|
no_data_sent;
|
|
|
|
_ ->
|
|
|
|
ok = maybe_socket_error(State1, Transport:send(Socket, Prefix)),
|
|
|
|
data_sent
|
2020-01-02 13:29:56 +01:00
|
|
|
end,
|
2021-02-08 16:05:05 -08:00
|
|
|
State = maybe_send_data_alarm(State1, HTTP2Machine0, StreamID),
|
|
|
|
{WasDataSent, State};
|
2018-10-26 10:18:57 +02:00
|
|
|
{send, SendData, HTTP2Machine} ->
|
2019-08-16 16:37:04 +02:00
|
|
|
State = #state{http2_status=Status, streams=Streams}
|
2020-01-02 13:29:56 +01:00
|
|
|
= send_data(State0#state{http2_machine=HTTP2Machine}, SendData, Prefix),
|
2019-08-16 16:37:04 +02:00
|
|
|
%% Terminate the connection if we are closing and all streams have completed.
|
|
|
|
if
|
|
|
|
Status =:= closing, Streams =:= #{} ->
|
|
|
|
terminate(State, {stop, normal, 'The connection is going away.'});
|
|
|
|
true ->
|
2021-02-08 16:05:05 -08:00
|
|
|
{data_sent, maybe_send_data_alarm(State, HTTP2Machine0, StreamID)}
|
2019-08-16 16:37:04 +02:00
|
|
|
end
|
2017-02-05 13:45:35 +01:00
|
|
|
end.
|
|
|
|
|
2020-01-02 13:29:56 +01:00
|
|
|
send_data(State0=#state{socket=Socket, transport=Transport, opts=Opts}, SendData, Prefix) ->
|
|
|
|
{Acc, State} = prepare_data(State0, SendData, [], Prefix),
|
|
|
|
_ = [case Data of
|
|
|
|
{sendfile, Offset, Bytes, Path} ->
|
|
|
|
%% When sendfile is disabled we explicitly use the fallback.
|
2023-12-12 12:05:54 +01:00
|
|
|
{ok, _} = maybe_socket_error(State,
|
|
|
|
case maps:get(sendfile, Opts, true) of
|
|
|
|
true -> Transport:sendfile(Socket, Path, Offset, Bytes);
|
|
|
|
false -> ranch_transport:sendfile(Transport, Socket, Path, Offset, Bytes, [])
|
|
|
|
end
|
|
|
|
),
|
|
|
|
ok;
|
2020-01-02 13:29:56 +01:00
|
|
|
_ ->
|
2023-12-12 12:05:54 +01:00
|
|
|
ok = maybe_socket_error(State, Transport:send(Socket, Data))
|
2020-01-02 13:29:56 +01:00
|
|
|
end || Data <- Acc],
|
2022-09-19 14:10:32 +02:00
|
|
|
send_data_terminate(State, SendData).
|
|
|
|
|
|
|
|
send_data_terminate(State, []) ->
|
|
|
|
State;
|
|
|
|
send_data_terminate(State0, [{StreamID, IsFin, _}|Tail]) ->
|
|
|
|
State = maybe_terminate_stream(State0, StreamID, IsFin),
|
|
|
|
send_data_terminate(State, Tail).
|
2020-01-02 13:29:56 +01:00
|
|
|
|
|
|
|
prepare_data(State, [], Acc, []) ->
|
|
|
|
{lists:reverse(Acc), State};
|
|
|
|
prepare_data(State, [], Acc, Buffer) ->
|
|
|
|
{lists:reverse([lists:reverse(Buffer)|Acc]), State};
|
|
|
|
prepare_data(State0, [{StreamID, IsFin, SendData}|Tail], Acc0, Buffer0) ->
|
|
|
|
{Acc, Buffer, State} = prepare_data(State0, StreamID, IsFin, SendData, Acc0, Buffer0),
|
|
|
|
prepare_data(State, Tail, Acc, Buffer).
|
|
|
|
|
2022-09-19 14:10:32 +02:00
|
|
|
prepare_data(State, _, _, [], Acc, Buffer) ->
|
2020-01-02 13:29:56 +01:00
|
|
|
{Acc, Buffer, State};
|
|
|
|
prepare_data(State0, StreamID, IsFin, [FrameData|Tail], Acc, Buffer) ->
|
|
|
|
FrameIsFin = case Tail of
|
|
|
|
[] -> IsFin;
|
|
|
|
_ -> nofin
|
2018-11-03 18:55:40 +01:00
|
|
|
end,
|
2020-01-02 13:29:56 +01:00
|
|
|
case prepare_data_frame(State0, StreamID, FrameIsFin, FrameData) of
|
|
|
|
{{MoreData, Sendfile}, State} when is_tuple(Sendfile) ->
|
|
|
|
case Buffer of
|
|
|
|
[] ->
|
|
|
|
prepare_data(State, StreamID, IsFin, Tail,
|
|
|
|
[Sendfile, MoreData|Acc], []);
|
|
|
|
_ ->
|
|
|
|
prepare_data(State, StreamID, IsFin, Tail,
|
|
|
|
[Sendfile, lists:reverse([MoreData|Buffer])|Acc], [])
|
|
|
|
end;
|
|
|
|
{MoreData, State} ->
|
|
|
|
prepare_data(State, StreamID, IsFin, Tail,
|
|
|
|
Acc, [MoreData|Buffer])
|
|
|
|
end.
|
|
|
|
|
|
|
|
prepare_data_frame(State, StreamID, IsFin, {data, Data}) ->
|
|
|
|
{cow_http2:data(StreamID, IsFin, Data),
|
|
|
|
State};
|
|
|
|
prepare_data_frame(State, StreamID, IsFin, Sendfile={sendfile, _, Bytes, _}) ->
|
|
|
|
{{cow_http2:data_header(StreamID, IsFin, Bytes), Sendfile},
|
|
|
|
State};
|
2018-10-26 10:18:57 +02:00
|
|
|
%% The stream is terminated in cow_http2_machine:prepare_trailers.
|
2020-01-02 13:29:56 +01:00
|
|
|
prepare_data_frame(State=#state{http2_machine=HTTP2Machine0},
|
|
|
|
StreamID, nofin, {trailers, Trailers}) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
{ok, HeaderBlock, HTTP2Machine}
|
|
|
|
= cow_http2_machine:prepare_trailers(StreamID, HTTP2Machine0, Trailers),
|
2020-01-02 13:29:56 +01:00
|
|
|
{cow_http2:headers(StreamID, fin, HeaderBlock),
|
|
|
|
State#state{http2_machine=HTTP2Machine}}.
|
2017-05-19 20:18:00 +02:00
|
|
|
|
2019-09-13 14:20:04 +02:00
|
|
|
%% After we have sent or queued data we may need to set or clear an alarm.
|
|
|
|
%% We do this by comparing the HTTP2Machine buffer state before/after for
|
|
|
|
%% the relevant streams.
|
|
|
|
maybe_send_data_alarm(State=#state{opts=Opts, http2_machine=HTTP2Machine}, HTTP2Machine0, StreamID) ->
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
ConnBufferSizeBefore = cow_http2_machine:get_connection_local_buffer_size(HTTP2Machine0),
|
|
|
|
ConnBufferSizeAfter = cow_http2_machine:get_connection_local_buffer_size(HTTP2Machine),
|
|
|
|
{ok, StreamBufferSizeBefore} = cow_http2_machine:get_stream_local_buffer_size(StreamID, HTTP2Machine0),
|
2019-09-13 14:20:04 +02:00
|
|
|
%% When the stream ends up closed after it finished sending data,
|
|
|
|
%% we do not want to trigger an alarm. We act as if the buffer
|
|
|
|
%% size did not change.
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
StreamBufferSizeAfter = case cow_http2_machine:get_stream_local_buffer_size(StreamID, HTTP2Machine) of
|
2019-09-13 14:20:04 +02:00
|
|
|
{ok, BSA} -> BSA;
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
{error, closed} -> StreamBufferSizeBefore
|
2019-09-13 14:20:04 +02:00
|
|
|
end,
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
MaxConnBufferSize = maps:get(max_connection_buffer_size, Opts, 16000000),
|
|
|
|
MaxStreamBufferSize = maps:get(max_stream_buffer_size, Opts, 8000000),
|
|
|
|
%% I do not want to document these internal events yet. I am not yet
|
2019-09-13 14:20:04 +02:00
|
|
|
%% convinced it should be {alarm, Name, on|off} and not {internal_event, E}
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
%% or something else entirely. Though alarms are probably right.
|
2019-09-13 14:20:04 +02:00
|
|
|
if
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
ConnBufferSizeBefore >= MaxConnBufferSize, ConnBufferSizeAfter < MaxConnBufferSize ->
|
|
|
|
connection_alarm(State, connection_buffer_full, off);
|
|
|
|
ConnBufferSizeBefore < MaxConnBufferSize, ConnBufferSizeAfter >= MaxConnBufferSize ->
|
|
|
|
connection_alarm(State, connection_buffer_full, on);
|
|
|
|
StreamBufferSizeBefore >= MaxStreamBufferSize, StreamBufferSizeAfter < MaxStreamBufferSize ->
|
|
|
|
stream_alarm(State, StreamID, stream_buffer_full, off);
|
|
|
|
StreamBufferSizeBefore < MaxStreamBufferSize, StreamBufferSizeAfter >= MaxStreamBufferSize ->
|
|
|
|
stream_alarm(State, StreamID, stream_buffer_full, on);
|
2019-09-13 14:20:04 +02:00
|
|
|
true ->
|
|
|
|
State
|
|
|
|
end.
|
|
|
|
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
connection_alarm(State0=#state{streams=Streams}, Name, Value) ->
|
|
|
|
lists:foldl(fun(StreamID, State) ->
|
|
|
|
stream_alarm(State, StreamID, Name, Value)
|
|
|
|
end, State0, maps:keys(Streams)).
|
|
|
|
|
|
|
|
stream_alarm(State, StreamID, Name, Value) ->
|
|
|
|
info(State, StreamID, {alarm, Name, Value}).
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% Terminate a stream or the connection.
|
2017-11-29 14:54:47 +01:00
|
|
|
|
2019-08-16 16:37:04 +02:00
|
|
|
%% We may have to cancel streams even if we receive multiple
|
|
|
|
%% GOAWAY frames as the LastStreamID value may be lower than
|
|
|
|
%% the one previously received.
|
2021-02-08 16:05:05 -08:00
|
|
|
%%
|
|
|
|
%% We do not reset the idle timeout on send here. We already
|
|
|
|
%% disabled it if we initiated shutdown; and we already reset
|
|
|
|
%% it if the client sent a GOAWAY frame.
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
goaway(State0=#state{socket=Socket, transport=Transport, http2_machine=HTTP2Machine0,
|
2019-08-16 16:37:04 +02:00
|
|
|
http2_status=Status, streams=Streams0}, {goaway, LastStreamID, Reason, _})
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
when Status =:= connected; Status =:= closing_initiated; Status =:= closing ->
|
2019-08-16 16:37:04 +02:00
|
|
|
Streams = goaway_streams(State0, maps:to_list(Streams0), LastStreamID,
|
|
|
|
{stop, {goaway, Reason}, 'The connection is going away.'}, []),
|
2023-12-12 12:05:54 +01:00
|
|
|
State1 = State0#state{streams=maps:from_list(Streams)},
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
if
|
|
|
|
Status =:= connected; Status =:= closing_initiated ->
|
|
|
|
{OurLastStreamID, HTTP2Machine} =
|
|
|
|
cow_http2_machine:set_last_streamid(HTTP2Machine0),
|
2023-12-12 12:05:54 +01:00
|
|
|
State = State1#state{http2_status=closing, http2_machine=HTTP2Machine},
|
|
|
|
ok = maybe_socket_error(State, Transport:send(Socket,
|
|
|
|
cow_http2:goaway(OurLastStreamID, no_error, <<>>))),
|
|
|
|
State;
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
true ->
|
2023-12-12 12:05:54 +01:00
|
|
|
State1
|
2019-08-16 16:37:04 +02:00
|
|
|
end;
|
|
|
|
%% We terminate the connection immediately if it hasn't fully been initialized.
|
|
|
|
goaway(State, {goaway, _, Reason, _}) ->
|
|
|
|
terminate(State, {stop, {goaway, Reason}, 'The connection is going away.'}).
|
|
|
|
|
|
|
|
%% Cancel client-initiated streams that are above LastStreamID.
|
|
|
|
goaway_streams(_, [], _, _, Acc) ->
|
|
|
|
Acc;
|
2019-09-02 14:48:28 +02:00
|
|
|
goaway_streams(State, [{StreamID, #stream{state=StreamState}}|Tail], LastStreamID, Reason, Acc)
|
2019-08-16 16:37:04 +02:00
|
|
|
when StreamID > LastStreamID, (StreamID rem 2) =:= 0 ->
|
|
|
|
terminate_stream_handler(State, StreamID, Reason, StreamState),
|
|
|
|
goaway_streams(State, Tail, LastStreamID, Reason, Acc);
|
|
|
|
goaway_streams(State, [Stream|Tail], LastStreamID, Reason, Acc) ->
|
|
|
|
goaway_streams(State, Tail, LastStreamID, Reason, [Stream|Acc]).
|
|
|
|
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
%% A server that is attempting to gracefully shut down a connection SHOULD send
|
|
|
|
%% an initial GOAWAY frame with the last stream identifier set to 2^31-1 and a
|
|
|
|
%% NO_ERROR code. This signals to the client that a shutdown is imminent and
|
|
|
|
%% that initiating further requests is prohibited. After allowing time for any
|
|
|
|
%% in-flight stream creation (at least one round-trip time), the server can send
|
|
|
|
%% another GOAWAY frame with an updated last stream identifier. This ensures
|
|
|
|
%% that a connection can be cleanly shut down without losing requests.
|
2025-06-20 11:50:38 +02:00
|
|
|
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
-spec initiate_closing(#state{}, _) -> #state{}.
|
2025-06-20 11:50:38 +02:00
|
|
|
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
initiate_closing(State=#state{http2_status=connected, socket=Socket,
|
|
|
|
transport=Transport, opts=Opts}, Reason) ->
|
2023-12-12 12:05:54 +01:00
|
|
|
ok = maybe_socket_error(State, Transport:send(Socket,
|
|
|
|
cow_http2:goaway(16#7fffffff, no_error, <<>>))),
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
Timeout = maps:get(goaway_initial_timeout, Opts, 1000),
|
|
|
|
Message = {goaway_initial_timeout, Reason},
|
|
|
|
set_timeout(State#state{http2_status=closing_initiated}, Timeout, Message);
|
|
|
|
initiate_closing(State=#state{http2_status=Status}, _Reason)
|
|
|
|
when Status =:= closing_initiated; Status =:= closing ->
|
|
|
|
%% This happens if sys:terminate/2,3 is called twice or if the supervisor
|
|
|
|
%% tells us to shutdown after sys:terminate/2,3 is called or vice versa.
|
|
|
|
State;
|
|
|
|
initiate_closing(State, Reason) ->
|
|
|
|
terminate(State, {stop, stop_reason(Reason), 'The connection is going away.'}).
|
|
|
|
|
|
|
|
%% Switch to 'closing' state and stop accepting new streams.
|
2025-06-20 11:50:38 +02:00
|
|
|
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
-spec closing(#state{}, Reason :: term()) -> #state{}.
|
2025-06-20 11:50:38 +02:00
|
|
|
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
closing(State=#state{streams=Streams}, Reason) when Streams =:= #{} ->
|
|
|
|
terminate(State, Reason);
|
2023-12-12 12:05:54 +01:00
|
|
|
closing(State0=#state{http2_status=closing_initiated,
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
http2_machine=HTTP2Machine0, socket=Socket, transport=Transport},
|
|
|
|
Reason) ->
|
|
|
|
%% Stop accepting new streams.
|
|
|
|
{LastStreamID, HTTP2Machine} =
|
|
|
|
cow_http2_machine:set_last_streamid(HTTP2Machine0),
|
2023-12-12 12:05:54 +01:00
|
|
|
State = State0#state{http2_status=closing, http2_machine=HTTP2Machine},
|
|
|
|
ok = maybe_socket_error(State, Transport:send(Socket,
|
|
|
|
cow_http2:goaway(LastStreamID, no_error, <<>>))),
|
|
|
|
closing(State, Reason);
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
closing(State=#state{http2_status=closing, opts=Opts}, Reason) ->
|
|
|
|
%% If client sent GOAWAY, we may already be in 'closing' but without the
|
|
|
|
%% goaway complete timeout set.
|
|
|
|
Timeout = maps:get(goaway_complete_timeout, Opts, 3000),
|
|
|
|
Message = {goaway_complete_timeout, Reason},
|
|
|
|
set_timeout(State, Timeout, Message).
|
|
|
|
|
|
|
|
stop_reason({stop, Reason, _}) -> Reason;
|
|
|
|
stop_reason(Reason) -> Reason.
|
|
|
|
|
2023-12-12 12:05:54 +01:00
|
|
|
%% Function copied from cowboy_http.
|
|
|
|
maybe_socket_error(State, {error, closed}) ->
|
|
|
|
terminate(State, {socket_error, closed, 'The socket has been closed.'});
|
|
|
|
maybe_socket_error(State, Reason) ->
|
|
|
|
maybe_socket_error(State, Reason, 'An error has occurred on the socket.').
|
|
|
|
|
|
|
|
maybe_socket_error(_, Result = ok, _) ->
|
|
|
|
Result;
|
|
|
|
maybe_socket_error(_, Result = {ok, _}, _) ->
|
|
|
|
Result;
|
|
|
|
maybe_socket_error(State, {error, Reason}, Human) ->
|
|
|
|
terminate(State, {socket_error, Reason, Human}).
|
|
|
|
|
2023-01-31 11:07:31 +01:00
|
|
|
-spec terminate(#state{} | undefined, _) -> no_return().
|
2025-06-20 11:50:38 +02:00
|
|
|
|
2017-02-25 20:05:31 +01:00
|
|
|
terminate(undefined, Reason) ->
|
|
|
|
exit({shutdown, Reason});
|
2019-08-16 16:37:04 +02:00
|
|
|
terminate(State=#state{socket=Socket, transport=Transport, http2_status=Status,
|
|
|
|
http2_machine=HTTP2Machine, streams=Streams, children=Children}, Reason)
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
when Status =:= connected; Status =:= closing_initiated; Status =:= closing ->
|
2017-02-25 20:05:31 +01:00
|
|
|
%% @todo We might want to optionally send the Reason value
|
|
|
|
%% as debug data in the GOAWAY frame here. Perhaps more.
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
if
|
|
|
|
Status =:= connected; Status =:= closing_initiated ->
|
2023-12-12 12:05:54 +01:00
|
|
|
%% We are terminating so it's OK if we can't send the GOAWAY anymore.
|
|
|
|
_ = Transport:send(Socket, cow_http2:goaway(
|
2019-08-16 16:37:04 +02:00
|
|
|
cow_http2_machine:get_last_streamid(HTTP2Machine),
|
|
|
|
terminate_reason(Reason), <<>>));
|
|
|
|
%% We already sent the GOAWAY frame.
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
Status =:= closing ->
|
2019-08-16 16:37:04 +02:00
|
|
|
ok
|
|
|
|
end,
|
2018-10-26 10:18:57 +02:00
|
|
|
terminate_all_streams(State, maps:to_list(Streams), Reason),
|
2017-08-08 16:59:33 +02:00
|
|
|
cowboy_children:terminate(Children),
|
2023-12-19 11:09:54 +01:00
|
|
|
%% @todo Don't linger on connection errors.
|
2020-04-06 14:50:35 +02:00
|
|
|
terminate_linger(State),
|
2018-10-26 10:18:57 +02:00
|
|
|
exit({shutdown, Reason});
|
2023-12-12 12:05:54 +01:00
|
|
|
%% We are not fully connected so we can just terminate the connection.
|
|
|
|
terminate(_State, Reason) ->
|
2015-06-11 17:04:21 +02:00
|
|
|
exit({shutdown, Reason}).
|
|
|
|
|
2017-02-25 20:05:31 +01:00
|
|
|
terminate_reason({connection_error, Reason, _}) -> Reason;
|
|
|
|
terminate_reason({stop, _, _}) -> no_error;
|
|
|
|
terminate_reason({socket_error, _, _}) -> internal_error;
|
|
|
|
terminate_reason({internal_error, _, _}) -> internal_error.
|
|
|
|
|
2018-06-28 17:10:18 +02:00
|
|
|
terminate_all_streams(_, [], _) ->
|
2015-06-11 17:04:21 +02:00
|
|
|
ok;
|
2019-09-02 14:48:28 +02:00
|
|
|
terminate_all_streams(State, [{StreamID, #stream{state=StreamState}}|Tail], Reason) ->
|
2018-10-28 11:47:49 +01:00
|
|
|
terminate_stream_handler(State, StreamID, Reason, StreamState),
|
2018-06-28 17:10:18 +02:00
|
|
|
terminate_all_streams(State, Tail, Reason).
|
2015-06-11 17:04:21 +02:00
|
|
|
|
2020-04-06 14:50:35 +02:00
|
|
|
%% This code is copied from cowboy_http.
|
|
|
|
terminate_linger(State=#state{socket=Socket, transport=Transport, opts=Opts}) ->
|
|
|
|
case Transport:shutdown(Socket, write) of
|
|
|
|
ok ->
|
|
|
|
case maps:get(linger_timeout, Opts, 1000) of
|
|
|
|
0 ->
|
|
|
|
ok;
|
|
|
|
infinity ->
|
|
|
|
terminate_linger_before_loop(State, undefined, Transport:messages());
|
|
|
|
Timeout ->
|
|
|
|
TimerRef = erlang:start_timer(Timeout, self(), linger_timeout),
|
|
|
|
terminate_linger_before_loop(State, TimerRef, Transport:messages())
|
|
|
|
end;
|
|
|
|
{error, _} ->
|
|
|
|
ok
|
|
|
|
end.
|
|
|
|
|
|
|
|
terminate_linger_before_loop(State, TimerRef, Messages) ->
|
|
|
|
%% We may already be in active mode when we do this
|
|
|
|
%% but it's OK because we are shutting down anyway.
|
2023-12-12 12:05:54 +01:00
|
|
|
%%
|
|
|
|
%% We specially handle the socket error to terminate
|
|
|
|
%% when an error occurs.
|
2020-04-06 14:50:35 +02:00
|
|
|
case setopts_active(State) of
|
|
|
|
ok ->
|
|
|
|
terminate_linger_loop(State, TimerRef, Messages);
|
|
|
|
{error, _} ->
|
|
|
|
ok
|
|
|
|
end.
|
|
|
|
|
|
|
|
terminate_linger_loop(State=#state{socket=Socket}, TimerRef, Messages) ->
|
|
|
|
receive
|
|
|
|
{OK, Socket, _} when OK =:= element(1, Messages) ->
|
|
|
|
terminate_linger_loop(State, TimerRef, Messages);
|
|
|
|
{Closed, Socket} when Closed =:= element(2, Messages) ->
|
|
|
|
ok;
|
|
|
|
{Error, Socket, _} when Error =:= element(3, Messages) ->
|
|
|
|
ok;
|
|
|
|
{Passive, Socket} when Passive =:= tcp_passive; Passive =:= ssl_passive ->
|
|
|
|
terminate_linger_before_loop(State, TimerRef, Messages);
|
|
|
|
{timeout, TimerRef, linger_timeout} ->
|
|
|
|
ok;
|
|
|
|
_ ->
|
|
|
|
terminate_linger_loop(State, TimerRef, Messages)
|
|
|
|
end.
|
|
|
|
|
2017-10-23 14:49:33 +01:00
|
|
|
%% @todo Don't send an RST_STREAM if one was already sent.
|
2021-02-08 16:05:05 -08:00
|
|
|
%%
|
|
|
|
%% When resetting the stream we are technically sending data
|
|
|
|
%% on the socket. However due to implementation complexities
|
|
|
|
%% we do not attempt to reset the idle timeout on send.
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
reset_stream(State0=#state{socket=Socket, transport=Transport,
|
2018-10-26 10:18:57 +02:00
|
|
|
http2_machine=HTTP2Machine0}, StreamID, Error) ->
|
|
|
|
Reason = case Error of
|
2017-10-23 14:49:33 +01:00
|
|
|
{internal_error, _, _} -> internal_error;
|
|
|
|
{stream_error, Reason0, _} -> Reason0
|
|
|
|
end,
|
2023-12-12 12:05:54 +01:00
|
|
|
ok = maybe_socket_error(State0, Transport:send(Socket,
|
|
|
|
cow_http2:rst_stream(StreamID, Reason))),
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
State1 = case cow_http2_machine:reset_stream(StreamID, HTTP2Machine0) of
|
2018-10-26 10:18:57 +02:00
|
|
|
{ok, HTTP2Machine} ->
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
terminate_stream(State0#state{http2_machine=HTTP2Machine}, StreamID, Error);
|
2018-10-26 10:18:57 +02:00
|
|
|
{error, not_found} ->
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
terminate_stream(State0, StreamID, Error)
|
|
|
|
end,
|
|
|
|
case reset_rate(State1) of
|
|
|
|
{ok, State} ->
|
|
|
|
State;
|
|
|
|
error ->
|
|
|
|
terminate(State1, {connection_error, enhance_your_calm,
|
|
|
|
'Stream reset rate larger than configuration allows. Flood? (CVE-2019-9514)'})
|
|
|
|
end.
|
|
|
|
|
2020-11-02 11:12:21 +01:00
|
|
|
reset_rate(State0=#state{reset_rate_num=Num0, reset_rate_time=Time}) ->
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
case Num0 - 1 of
|
|
|
|
0 ->
|
|
|
|
CurrentTime = erlang:monotonic_time(millisecond),
|
|
|
|
if
|
|
|
|
CurrentTime < Time ->
|
|
|
|
error;
|
|
|
|
true ->
|
|
|
|
%% When the option has a period of infinity we cannot reach this clause.
|
2020-11-02 11:12:21 +01:00
|
|
|
{ok, init_reset_rate_limiting(State0, CurrentTime)}
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
end;
|
|
|
|
Num ->
|
|
|
|
{ok, State0#state{reset_rate_num=Num}}
|
2018-10-26 10:18:57 +02:00
|
|
|
end.
|
2018-04-27 20:45:34 +02:00
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
stop_stream(State=#state{http2_machine=HTTP2Machine}, StreamID) ->
|
|
|
|
case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine) of
|
2017-04-18 16:07:01 +02:00
|
|
|
%% When the stream terminates normally (without sending RST_STREAM)
|
|
|
|
%% and no response was sent, we need to send a proper response back to the client.
|
2018-10-26 10:18:57 +02:00
|
|
|
%% We delay the termination of the stream until the response is fully sent.
|
|
|
|
{ok, idle, _} ->
|
|
|
|
info(stopping(State, StreamID), StreamID, {response, 204, #{}, <<>>});
|
2017-04-18 16:07:01 +02:00
|
|
|
%% When a response was sent but not terminated, we need to close the stream.
|
2018-10-26 10:18:57 +02:00
|
|
|
%% We delay the termination of the stream until the response is fully sent.
|
|
|
|
{ok, nofin, fin} ->
|
|
|
|
stopping(State, StreamID);
|
|
|
|
%% We only send a final DATA frame if there isn't one queued yet.
|
|
|
|
{ok, nofin, _} ->
|
|
|
|
info(stopping(State, StreamID), StreamID, {data, fin, <<>>});
|
|
|
|
%% When a response was sent fully we can terminate the stream,
|
|
|
|
%% regardless of the stream being in half-closed or closed state.
|
|
|
|
_ ->
|
|
|
|
terminate_stream(State, StreamID)
|
2015-06-11 17:04:21 +02:00
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
stopping(State=#state{streams=Streams}, StreamID) ->
|
2019-09-02 14:48:28 +02:00
|
|
|
#{StreamID := Stream} = Streams,
|
|
|
|
State#state{streams=Streams#{StreamID => Stream#stream{status=stopping}}}.
|
2018-10-26 10:18:57 +02:00
|
|
|
|
|
|
|
%% If we finished sending data and the stream is stopping, terminate it.
|
|
|
|
maybe_terminate_stream(State=#state{streams=Streams}, StreamID, fin) ->
|
|
|
|
case Streams of
|
2019-09-02 14:48:28 +02:00
|
|
|
#{StreamID := #stream{status=stopping}} ->
|
2018-10-26 10:18:57 +02:00
|
|
|
terminate_stream(State, StreamID);
|
|
|
|
_ ->
|
|
|
|
State
|
|
|
|
end;
|
|
|
|
maybe_terminate_stream(State, _, _) ->
|
|
|
|
State.
|
|
|
|
|
2017-10-23 14:49:33 +01:00
|
|
|
%% When the stream stops normally without reading the request
|
|
|
|
%% body fully we need to tell the client to stop sending it.
|
|
|
|
%% We do this by sending an RST_STREAM with reason NO_ERROR. (RFC7540 8.1.0)
|
2018-10-26 10:18:57 +02:00
|
|
|
terminate_stream(State0=#state{socket=Socket, transport=Transport,
|
|
|
|
http2_machine=HTTP2Machine0}, StreamID) ->
|
|
|
|
State = case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine0) of
|
|
|
|
{ok, fin, _} ->
|
2023-12-12 12:05:54 +01:00
|
|
|
ok = maybe_socket_error(State0, Transport:send(Socket,
|
|
|
|
cow_http2:rst_stream(StreamID, no_error))),
|
2018-10-26 10:18:57 +02:00
|
|
|
{ok, HTTP2Machine} = cow_http2_machine:reset_stream(StreamID, HTTP2Machine0),
|
|
|
|
State0#state{http2_machine=HTTP2Machine};
|
|
|
|
{error, closed} ->
|
|
|
|
State0
|
|
|
|
end,
|
|
|
|
terminate_stream(State, StreamID, normal).
|
|
|
|
|
2019-09-02 14:48:28 +02:00
|
|
|
%% We remove the stream flow from the connection flow. Any further
|
|
|
|
%% data received for this stream is therefore fully contained within
|
|
|
|
%% the extra window we allocated for this stream.
|
|
|
|
terminate_stream(State=#state{flow=Flow, streams=Streams0, children=Children0}, StreamID, Reason) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
case maps:take(StreamID, Streams0) of
|
2019-09-02 14:48:28 +02:00
|
|
|
{#stream{flow=StreamFlow, state=StreamState}, Streams} ->
|
2018-10-28 11:47:49 +01:00
|
|
|
terminate_stream_handler(State, StreamID, Reason, StreamState),
|
2018-10-26 10:18:57 +02:00
|
|
|
Children = cowboy_children:shutdown(Children0, StreamID),
|
2019-09-02 14:48:28 +02:00
|
|
|
State#state{flow=Flow - StreamFlow, streams=Streams, children=Children};
|
2018-10-26 10:18:57 +02:00
|
|
|
error ->
|
|
|
|
State
|
|
|
|
end.
|
2017-10-23 14:49:33 +01:00
|
|
|
|
2018-10-28 11:47:49 +01:00
|
|
|
terminate_stream_handler(#state{opts=Opts}, StreamID, Reason, StreamState) ->
|
2015-06-11 17:04:21 +02:00
|
|
|
try
|
2017-01-16 14:22:43 +01:00
|
|
|
cowboy_stream:terminate(StreamID, Reason, StreamState)
|
2019-12-31 15:10:38 +01:00
|
|
|
catch Class:Exception:Stacktrace ->
|
2018-06-28 17:10:18 +02:00
|
|
|
cowboy:log(cowboy_stream:make_error_log(terminate,
|
2017-09-21 12:53:21 +02:00
|
|
|
[StreamID, Reason, StreamState],
|
2019-12-31 15:10:38 +01:00
|
|
|
Class, Exception, Stacktrace), Opts)
|
2015-06-11 17:04:21 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
%% System callbacks.
|
|
|
|
|
2025-06-20 11:50:38 +02:00
|
|
|
-spec system_continue(_, _, {#state{}, binary()}) -> no_return().
|
|
|
|
|
2015-06-11 17:04:21 +02:00
|
|
|
system_continue(_, _, {State, Buffer}) ->
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(State, Buffer).
|
2015-06-11 17:04:21 +02:00
|
|
|
|
2017-08-08 16:59:33 +02:00
|
|
|
-spec system_terminate(any(), _, _, {#state{}, binary()}) -> no_return().
|
2025-06-20 11:50:38 +02:00
|
|
|
|
Graceful shutdown
Note: This commit makes cowboy depend on cowlib master.
Graceful shutdown for HTTP/2:
1. A GOAWAY frame with the last stream id set to 2^31-1 is sent and a
timer is started (goaway_initial_timeout, default 1000ms), to wait
for any in-flight requests sent by the client, and the status is set
to 'closing_initiated'. If the client responds with GOAWAY and closes
the connection, we're done.
2. A second GOAWAY frame is sent with the actual last stream id and the
status is set to 'closing'. If no streams exist, the connection
terminates. Otherwise a second timer (goaway_complete_timeout,
default 3000ms) is started, to wait for the streams to complete. New
streams are not accepted when status is 'closing'.
3. If all streams haven't completed after the second timeout, the
connection is forcefully terminated.
Graceful shutdown for HTTP/1.x:
1. If a request is currently being handled, it is waited for and the
response is sent back to the client with the header "Connection:
close". Then, the connection is closed.
2. If the current request handler is not finished within the time
configured in transport option 'shutdown' (default 5000ms), the
connection process is killed by its supervisor (ranch).
Implemented for HTTP/1.x and HTTP/2 in the following scenarios:
* When receiving exit signal 'shutdown' from the supervisor (e.g. when
cowboy:stop_listener/3 is called).
* When a connection process is requested to terminate using
sys:terminate/2,3.
LH: Edited tests a bit and added todos for useful tests to add.
2020-10-08 17:53:25 +02:00
|
|
|
system_terminate(Reason0, _, _, {State, Buffer}) ->
|
|
|
|
Reason = {stop, {exit, Reason0}, 'sys:terminate/2,3 was called.'},
|
2025-02-07 16:57:58 +01:00
|
|
|
before_loop(initiate_closing(State, Reason), Buffer).
|
2015-06-11 17:04:21 +02:00
|
|
|
|
|
|
|
-spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::{#state{}, binary()}.
|
2025-06-20 11:50:38 +02:00
|
|
|
|
2015-06-11 17:04:21 +02:00
|
|
|
system_code_change(Misc, _, _, _) ->
|
|
|
|
{ok, Misc}.
|