nghttpx: Add frontend-header-timeout option

Add frontend-header-timeout option.  frontend-read-timeout is
deprecated and has no effect.  Use frontend-header-timeout as a
replacement.  This also fixes the bug that HTTP/3 header timeout is
not set per stream.
This commit is contained in:
Tatsuhiro Tsujikawa
2024-03-16 11:19:44 +09:00
parent 566737ccc8
commit c8e149994e
10 changed files with 108 additions and 36 deletions

View File

@@ -201,6 +201,7 @@ OPTIONS = [
"require-http-scheme", "require-http-scheme",
"tls-ktls", "tls-ktls",
"alpn-list", "alpn-list",
"frontend-header-timeout",
] ]
LOGVARS = [ LOGVARS = [

View File

@@ -2006,6 +2006,7 @@ void fill_default_config(Config *config) {
httpconf.xfp.add = true; httpconf.xfp.add = true;
httpconf.xfp.strip_incoming = true; httpconf.xfp.strip_incoming = true;
httpconf.early_data.strip_incoming = true; httpconf.early_data.strip_incoming = true;
httpconf.timeout.header = 1_min;
auto &http2conf = config->http2; auto &http2conf = config->http2;
{ {
@@ -2138,9 +2139,6 @@ void fill_default_config(Config *config) {
// Read timeout for HTTP3 upstream connection // Read timeout for HTTP3 upstream connection
timeoutconf.http3_read = 3_min; timeoutconf.http3_read = 3_min;
// Read timeout for non-HTTP2 upstream connection
timeoutconf.read = 1_min;
// Write timeout for HTTP2/non-HTTP2 upstream connection // Write timeout for HTTP2/non-HTTP2 upstream connection
timeoutconf.write = 30_s; timeoutconf.write = 30_s;
@@ -2645,17 +2643,17 @@ Performance:
Timeout: Timeout:
--frontend-http2-read-timeout=<DURATION> --frontend-http2-read-timeout=<DURATION>
Specify read timeout for HTTP/2 frontend connection. Specify idle timeout for HTTP/2 frontend connection. If
no active streams exist for this duration, connection is
closed.
Default: )" Default: )"
<< util::duration_str(config->conn.upstream.timeout.http2_read) << R"( << util::duration_str(config->conn.upstream.timeout.http2_read) << R"(
--frontend-http3-read-timeout=<DURATION> --frontend-http3-read-timeout=<DURATION>
Specify read timeout for HTTP/3 frontend connection. Specify idle timeout for HTTP/3 frontend connection. If
no active streams exist for this duration, connection is
closed.
Default: )" Default: )"
<< util::duration_str(config->conn.upstream.timeout.http3_read) << R"( << util::duration_str(config->conn.upstream.timeout.http3_read) << R"(
--frontend-read-timeout=<DURATION>
Specify read timeout for HTTP/1.1 frontend connection.
Default: )"
<< util::duration_str(config->conn.upstream.timeout.read) << R"(
--frontend-write-timeout=<DURATION> --frontend-write-timeout=<DURATION>
Specify write timeout for all frontend connections. Specify write timeout for all frontend connections.
Default: )" Default: )"
@@ -2665,6 +2663,14 @@ Timeout:
connection. connection.
Default: )" Default: )"
<< util::duration_str(config->conn.upstream.timeout.idle_read) << R"( << util::duration_str(config->conn.upstream.timeout.idle_read) << R"(
--frontend-header-timeout=<DURATION>
Specify duration that the server waits for an HTTP
request header fields to be received completely. On
timeout, HTTP/1 and HTTP/2 connections are closed. For
HTTP/3, the stream is shutdown, and the connection
itself is left intact.
Default: )"
<< util::duration_str(config->http.timeout.header) << R"(
--stream-read-timeout=<DURATION> --stream-read-timeout=<DURATION>
Specify read timeout for HTTP/2 streams. 0 means no Specify read timeout for HTTP/2 streams. 0 means no
timeout. timeout.
@@ -4377,6 +4383,8 @@ int main(int argc, char **argv) {
{SHRPX_OPT_REQUIRE_HTTP_SCHEME.c_str(), no_argument, &flag, 191}, {SHRPX_OPT_REQUIRE_HTTP_SCHEME.c_str(), no_argument, &flag, 191},
{SHRPX_OPT_TLS_KTLS.c_str(), no_argument, &flag, 192}, {SHRPX_OPT_TLS_KTLS.c_str(), no_argument, &flag, 192},
{SHRPX_OPT_ALPN_LIST.c_str(), required_argument, &flag, 193}, {SHRPX_OPT_ALPN_LIST.c_str(), required_argument, &flag, 193},
{SHRPX_OPT_FRONTEND_HEADER_TIMEOUT.c_str(), required_argument, &flag,
194},
{nullptr, 0, nullptr, 0}}; {nullptr, 0, nullptr, 0}};
int option_index = 0; int option_index = 0;
@@ -5294,6 +5302,11 @@ int main(int argc, char **argv) {
// --alpn-list // --alpn-list
cmdcfgs.emplace_back(SHRPX_OPT_ALPN_LIST, StringRef{optarg}); cmdcfgs.emplace_back(SHRPX_OPT_ALPN_LIST, StringRef{optarg});
break; break;
case 194:
// --frontend-header-timeout
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HEADER_TIMEOUT,
StringRef{optarg});
break;
default: default:
break; break;
} }

View File

@@ -444,7 +444,7 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
rb_(worker->get_mcpool()), rb_(worker->get_mcpool()),
conn_(worker->get_loop(), fd, ssl, worker->get_mcpool(), conn_(worker->get_loop(), fd, ssl, worker->get_mcpool(),
get_config()->conn.upstream.timeout.write, get_config()->conn.upstream.timeout.write,
get_config()->conn.upstream.timeout.read, get_config()->conn.upstream.timeout.idle_read,
get_config()->conn.upstream.ratelimit.write, get_config()->conn.upstream.ratelimit.write,
get_config()->conn.upstream.ratelimit.read, writecb, readcb, get_config()->conn.upstream.ratelimit.read, writecb, readcb,
timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
@@ -591,16 +591,14 @@ struct ev_loop *ClientHandler::get_loop() const { return conn_.loop; }
void ClientHandler::reset_upstream_read_timeout(ev_tstamp t) { void ClientHandler::reset_upstream_read_timeout(ev_tstamp t) {
conn_.rt.repeat = t; conn_.rt.repeat = t;
if (ev_is_active(&conn_.rt)) {
ev_timer_again(conn_.loop, &conn_.rt); ev_timer_again(conn_.loop, &conn_.rt);
}
} }
void ClientHandler::reset_upstream_write_timeout(ev_tstamp t) { void ClientHandler::reset_upstream_write_timeout(ev_tstamp t) {
conn_.wt.repeat = t; conn_.wt.repeat = t;
if (ev_is_active(&conn_.wt)) {
ev_timer_again(conn_.loop, &conn_.wt); ev_timer_again(conn_.loop, &conn_.wt);
}
} }
void ClientHandler::repeat_read_timer() { void ClientHandler::repeat_read_timer() {

View File

@@ -2396,6 +2396,9 @@ int option_lookup_token(const char *name, size_t namelen) {
if (util::strieq_l("backend-connect-timeou", name, 22)) { if (util::strieq_l("backend-connect-timeou", name, 22)) {
return SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT; return SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT;
} }
if (util::strieq_l("frontend-header-timeou", name, 22)) {
return SHRPX_OPTID_FRONTEND_HEADER_TIMEOUT;
}
break; break;
} }
break; break;
@@ -3031,7 +3034,9 @@ int parse_config(Config *config, int optid, const StringRef &opt,
return parse_duration(&config->conn.upstream.timeout.http2_read, opt, return parse_duration(&config->conn.upstream.timeout.http2_read, opt,
optarg); optarg);
case SHRPX_OPTID_FRONTEND_READ_TIMEOUT: case SHRPX_OPTID_FRONTEND_READ_TIMEOUT:
return parse_duration(&config->conn.upstream.timeout.read, opt, optarg); LOG(WARN) << opt << ": deprecated. Use frontend-header-timeout";
return 0;
case SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT: case SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT:
return parse_duration(&config->conn.upstream.timeout.write, opt, optarg); return parse_duration(&config->conn.upstream.timeout.write, opt, optarg);
case SHRPX_OPTID_BACKEND_READ_TIMEOUT: case SHRPX_OPTID_BACKEND_READ_TIMEOUT:

View File

@@ -406,6 +406,8 @@ constexpr auto SHRPX_OPT_REQUIRE_HTTP_SCHEME =
StringRef::from_lit("require-http-scheme"); StringRef::from_lit("require-http-scheme");
constexpr auto SHRPX_OPT_TLS_KTLS = StringRef::from_lit("tls-ktls"); constexpr auto SHRPX_OPT_TLS_KTLS = StringRef::from_lit("tls-ktls");
constexpr auto SHRPX_OPT_ALPN_LIST = StringRef::from_lit("alpn-list"); constexpr auto SHRPX_OPT_ALPN_LIST = StringRef::from_lit("alpn-list");
constexpr auto SHRPX_OPT_FRONTEND_HEADER_TIMEOUT =
StringRef::from_lit("frontend-header-timeout");
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
@@ -864,6 +866,9 @@ struct HttpConfig {
struct { struct {
bool strip_incoming; bool strip_incoming;
} early_data; } early_data;
struct {
ev_tstamp header;
} timeout;
std::vector<AltSvc> altsvcs; std::vector<AltSvc> altsvcs;
// altsvcs serialized in a wire format. // altsvcs serialized in a wire format.
StringRef altsvc_header_value; StringRef altsvc_header_value;
@@ -1050,7 +1055,6 @@ struct ConnectionConfig {
struct { struct {
ev_tstamp http2_read; ev_tstamp http2_read;
ev_tstamp http3_read; ev_tstamp http3_read;
ev_tstamp read;
ev_tstamp write; ev_tstamp write;
ev_tstamp idle_read; ev_tstamp idle_read;
} timeout; } timeout;
@@ -1249,6 +1253,7 @@ enum {
SHRPX_OPTID_FORWARDED_FOR, SHRPX_OPTID_FORWARDED_FOR,
SHRPX_OPTID_FRONTEND, SHRPX_OPTID_FRONTEND,
SHRPX_OPTID_FRONTEND_FRAME_DEBUG, SHRPX_OPTID_FRONTEND_FRAME_DEBUG,
SHRPX_OPTID_FRONTEND_HEADER_TIMEOUT,
SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS, SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS,
SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE, SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE,
SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE,

View File

@@ -45,6 +45,23 @@
namespace shrpx { namespace shrpx {
namespace {
void header_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
auto downstream = static_cast<Downstream *>(w->data);
auto upstream = downstream->get_upstream();
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "request header timeout stream_id="
<< downstream->get_stream_id();
}
downstream->disable_upstream_rtimer();
downstream->disable_upstream_wtimer();
upstream->on_timeout(downstream);
}
} // namespace
namespace { namespace {
void upstream_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { void upstream_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
auto downstream = static_cast<Downstream *>(w->data); auto downstream = static_cast<Downstream *>(w->data);
@@ -148,7 +165,12 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
expect_100_continue_(false), expect_100_continue_(false),
stop_reading_(false) { stop_reading_(false) {
auto &timeoutconf = get_config()->http2.timeout; auto config = get_config();
auto &httpconf = config->http;
ev_timer_init(&header_timer_, header_timeoutcb, 0., httpconf.timeout.header);
auto &timeoutconf = config->http2.timeout;
ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0., ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0.,
timeoutconf.stream_read); timeoutconf.stream_read);
@@ -159,6 +181,7 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
ev_timer_init(&downstream_wtimer_, &downstream_wtimeoutcb, 0., ev_timer_init(&downstream_wtimer_, &downstream_wtimeoutcb, 0.,
timeoutconf.stream_write); timeoutconf.stream_write);
header_timer_.data = this;
upstream_rtimer_.data = this; upstream_rtimer_.data = this;
upstream_wtimer_.data = this; upstream_wtimer_.data = this;
downstream_rtimer_.data = this; downstream_rtimer_.data = this;
@@ -183,6 +206,7 @@ Downstream::~Downstream() {
ev_timer_stop(loop, &upstream_wtimer_); ev_timer_stop(loop, &upstream_wtimer_);
ev_timer_stop(loop, &downstream_rtimer_); ev_timer_stop(loop, &downstream_rtimer_);
ev_timer_stop(loop, &downstream_wtimer_); ev_timer_stop(loop, &downstream_wtimer_);
ev_timer_stop(loop, &header_timer_);
#ifdef HAVE_MRUBY #ifdef HAVE_MRUBY
auto handler = upstream_->get_client_handler(); auto handler = upstream_->get_client_handler();
@@ -946,6 +970,18 @@ bool Downstream::expect_response_trailer() const {
(resp_.http_major == 3 || resp_.http_major == 2); (resp_.http_major == 3 || resp_.http_major == 2);
} }
void Downstream::repeat_header_timer() {
auto loop = upstream_->get_client_handler()->get_loop();
ev_timer_again(loop, &header_timer_);
}
void Downstream::stop_header_timer() {
auto loop = upstream_->get_client_handler()->get_loop();
ev_timer_stop(loop, &header_timer_);
}
namespace { namespace {
void reset_timer(struct ev_loop *loop, ev_timer *w) { ev_timer_again(loop, w); } void reset_timer(struct ev_loop *loop, ev_timer *w) { ev_timer_again(loop, w); }
} // namespace } // namespace

View File

@@ -448,6 +448,9 @@ public:
// connection. // connection.
int on_read(); int on_read();
void repeat_header_timer();
void stop_header_timer();
// Resets upstream read timer. If it is active, timeout value is // Resets upstream read timer. If it is active, timeout value is
// reset. If it is not active, timer will be started. // reset. If it is not active, timer will be started.
void reset_upstream_rtimer(); void reset_upstream_rtimer();
@@ -562,6 +565,8 @@ private:
// if frontend uses RFC 8441 WebSocket bootstrapping via HTTP/2. // if frontend uses RFC 8441 WebSocket bootstrapping via HTTP/2.
StringRef ws_key_; StringRef ws_key_;
ev_timer header_timer_;
ev_timer upstream_rtimer_; ev_timer upstream_rtimer_;
ev_timer upstream_wtimer_; ev_timer upstream_wtimer_;

View File

@@ -285,7 +285,10 @@ void Http2Upstream::on_start_request(const nghttp2_frame *frame) {
downstream->reset_upstream_rtimer(); downstream->reset_upstream_rtimer();
handler_->repeat_read_timer(); auto config = get_config();
auto &httpconf = config->http;
handler_->reset_upstream_read_timeout(httpconf.timeout.header);
auto &req = downstream->request(); auto &req = downstream->request();
@@ -298,8 +301,6 @@ void Http2Upstream::on_start_request(const nghttp2_frame *frame) {
++num_requests_; ++num_requests_;
auto config = get_config();
auto &httpconf = config->http;
if (httpconf.max_requests <= num_requests_) { if (httpconf.max_requests <= num_requests_) {
start_graceful_shutdown(); start_graceful_shutdown();
} }
@@ -1640,7 +1641,10 @@ void Http2Upstream::remove_downstream(Downstream *downstream) {
if (downstream_queue_.get_downstreams() == nullptr) { if (downstream_queue_.get_downstreams() == nullptr) {
// There is no downstream at the moment. Start idle timer now. // There is no downstream at the moment. Start idle timer now.
handler_->repeat_read_timer(); auto config = get_config();
auto &upstreamconf = config->conn.upstream;
handler_->reset_upstream_read_timeout(upstreamconf.timeout.http2_read);
} }
} }

View File

@@ -250,8 +250,9 @@ void Http3Upstream::http_begin_request_headers(int64_t stream_id) {
nghttp3_conn_set_stream_user_data(httpconn_, stream_id, downstream.get()); nghttp3_conn_set_stream_user_data(httpconn_, stream_id, downstream.get());
downstream->reset_upstream_rtimer(); downstream->reset_upstream_rtimer();
downstream->repeat_header_timer();
handler_->repeat_read_timer(); handler_->stop_read_timer();
auto &req = downstream->request(); auto &req = downstream->request();
req.http_major = 3; req.http_major = 3;
@@ -997,7 +998,18 @@ int Http3Upstream::write_streams() {
return 0; return 0;
} }
int Http3Upstream::on_timeout(Downstream *downstream) { return 0; } int Http3Upstream::on_timeout(Downstream *downstream) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "Stream timeout stream_id="
<< downstream->get_stream_id();
}
shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
handler_->signal_write();
return 0;
}
int Http3Upstream::on_downstream_abort_request(Downstream *downstream, int Http3Upstream::on_downstream_abort_request(Downstream *downstream,
unsigned int status_code) { unsigned int status_code) {
@@ -2187,7 +2199,6 @@ namespace {
int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id, int fin, int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id, int fin,
void *user_data, void *stream_user_data) { void *user_data, void *stream_user_data) {
auto upstream = static_cast<Http3Upstream *>(user_data); auto upstream = static_cast<Http3Upstream *>(user_data);
auto handler = upstream->get_client_handler();
auto downstream = static_cast<Downstream *>(stream_user_data); auto downstream = static_cast<Downstream *>(stream_user_data);
if (!downstream || downstream->get_stop_reading()) { if (!downstream || downstream->get_stop_reading()) {
@@ -2199,7 +2210,7 @@ int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id, int fin,
} }
downstream->reset_upstream_rtimer(); downstream->reset_upstream_rtimer();
handler->stop_read_timer(); downstream->stop_header_timer();
return 0; return 0;
} }

View File

@@ -115,12 +115,9 @@ void HttpsUpstream::on_start_request() {
attach_downstream(std::move(downstream)); attach_downstream(std::move(downstream));
auto conn = handler_->get_connection(); auto &httpconf = get_config()->http;
auto &upstreamconf = get_config()->conn.upstream;
conn->rt.repeat = upstreamconf.timeout.read; handler_->reset_upstream_read_timeout(httpconf.timeout.header);
handler_->repeat_read_timer();
++num_requests_; ++num_requests_;
} }
@@ -795,12 +792,9 @@ int HttpsUpstream::on_write() {
return 0; return 0;
} }
auto conn = handler_->get_connection();
auto &upstreamconf = get_config()->conn.upstream; auto &upstreamconf = get_config()->conn.upstream;
conn->rt.repeat = upstreamconf.timeout.idle_read; handler_->reset_upstream_read_timeout(upstreamconf.timeout.idle_read);
handler_->repeat_read_timer();
return resume_read(SHRPX_NO_BUFFER, nullptr, 0); return resume_read(SHRPX_NO_BUFFER, nullptr, 0);
} else { } else {