From 7874d607d8ff0d042524680ccb4abdbd51dc357b Mon Sep 17 00:00:00 2001 From: Greg Wardwell Date: Mon, 14 Apr 2025 16:46:44 -0400 Subject: [PATCH 01/10] feat: add server configuration (cherry picked from commit f008bf38274698b915f14ccc0cc34c9b56dd0d6e) --- apollo-router/src/configuration/server.rs | 140 ++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 apollo-router/src/configuration/server.rs diff --git a/apollo-router/src/configuration/server.rs b/apollo-router/src/configuration/server.rs new file mode 100644 index 0000000000..010e85ec01 --- /dev/null +++ b/apollo-router/src/configuration/server.rs @@ -0,0 +1,140 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +const DEFAULT_HEADER_READ_TIMEOUT: Duration = Duration::from_secs(10); + +fn default_header_read_timeout() -> Duration { + DEFAULT_HEADER_READ_TIMEOUT +} + +/// Configuration for HTTP +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +#[serde(deny_unknown_fields, default)] +pub(crate) struct ServerHttpConfig { + /// Header read timeout in human-readable format; defaults to 10s + #[serde( + deserialize_with = "humantime_serde::deserialize", + default = "default_header_read_timeout" + )] + #[schemars(with = "String", default = "default_header_read_timeout")] + pub(crate) header_read_timeout: Duration, +} + +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +#[serde(deny_unknown_fields, default)] +pub(crate) struct Server { + /// The server http configuration + pub(crate) http: ServerHttpConfig, +} + +impl Default for ServerHttpConfig { + fn default() -> Self { + Self { + header_read_timeout: Duration::from_secs(10), + } + } +} + +#[buildstructor::buildstructor] +impl ServerHttpConfig { + #[builder] + pub(crate) fn new(header_read_timeout: Option) -> Self { + Self { + header_read_timeout: header_read_timeout.unwrap_or_default(), + } + } +} + +#[buildstructor::buildstructor] +impl Server { + #[builder] + pub(crate) fn new(http: Option) -> Self { + Self { + http: http.unwrap_or_default(), + } + } +} + +impl Default for Server { + fn default() -> Self { + Self::builder().build() + } +} + +#[cfg(test)] +mod tests { + use serde_json::json; + use super::*; + + #[test] + fn it_builds_default_server_configuration() { + let default_duration_seconds = Duration::from_secs(10); + let server_config = Server::builder().build(); + assert_eq!( + server_config.http.header_read_timeout, + default_duration_seconds + ); + } + + #[test] + fn it_builds_custom_server_configuration() { + let duration_seconds = Duration::from_secs(30); + let http_config = ServerHttpConfig::builder() + .header_read_timeout(duration_seconds) + .build(); + let server_config = Server::builder() + .http(http_config) + .build(); + assert_eq!( + server_config.http.header_read_timeout, + duration_seconds + ); + } + + #[test] + fn it_json_parses_default_header_read_timeout_when_server_http_config_omitted() { + let json_server = json!({}); + + let config: Server = serde_json::from_value(json_server).unwrap(); + + assert_eq!(config.http.header_read_timeout, Duration::from_secs(10)); + } + + #[test] + fn it_json_parses_default_header_read_timeout_when_omitted() { + let json_config = json!({ + "http": {} + }); + + let config: Server = serde_json::from_value(json_config).unwrap(); + + assert_eq!(config.http.header_read_timeout, Duration::from_secs(10)); + } + + #[test] + fn it_json_parses_specified_server_config_seconds_correctly() { + let json_config = json!({ + "http": { + "header_read_timeout": "30s" + } + }); + + let config: Server = serde_json::from_value(json_config).unwrap(); + + assert_eq!(config.http.header_read_timeout, Duration::from_secs(30)); + } + + #[test] + fn it_json_parses_specified_server_config_minutes_correctly() { + let json_config = json!({ + "http": { + "header_read_timeout": "1m" + } + }); + + let config: Server = serde_json::from_value(json_config).unwrap(); + + assert_eq!(config.http.header_read_timeout, Duration::from_secs(60)); + } +} From d7443a1a9ca1c8f064a015b2de2cd045c274f623 Mon Sep 17 00:00:00 2001 From: Greg Wardwell Date: Mon, 14 Apr 2025 16:47:16 -0400 Subject: [PATCH 02/10] feat: add server configuration to router config (cherry picked from commit 8683f3a674b06bf1ddfb1210927fcdb5f271749a) # Conflicts: # apollo-router/src/configuration/mod.rs --- apollo-router/src/configuration/mod.rs | 20 ++++++++++++++++++++ apollo-router/src/configuration/tests.rs | 24 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/apollo-router/src/configuration/mod.rs b/apollo-router/src/configuration/mod.rs index 7844409859..ce7ed1273b 100644 --- a/apollo-router/src/configuration/mod.rs +++ b/apollo-router/src/configuration/mod.rs @@ -45,6 +45,11 @@ use self::expansion::Expansion; pub(crate) use self::experimental::Discussed; pub(crate) use self::schema::generate_config_schema; pub(crate) use self::schema::generate_upgrade; +<<<<<<< HEAD +======= +pub(crate) use self::schema::validate_yaml_configuration; +use self::server::Server; +>>>>>>> 8683f3a6 (feat: add server configuration to router config) use self::subgraph::SubgraphConfiguration; use crate::ApolloRouterError; use crate::cache::DEFAULT_CACHE_CAPACITY; @@ -63,7 +68,12 @@ pub(crate) mod expansion; mod experimental; pub(crate) mod metrics; mod persisted_queries; +<<<<<<< HEAD mod schema; +======= +pub(crate) mod schema; +pub(crate) mod server; +>>>>>>> 8683f3a6 (feat: add server configuration to router config) pub(crate) mod shared; pub(crate) mod subgraph; #[cfg(test)] @@ -136,6 +146,10 @@ pub struct Configuration { #[serde(default)] pub(crate) homepage: Homepage, + /// Configuration for the server + #[serde(default)] + pub(crate) server: Server, + /// Configuration for the supergraph #[serde(default)] pub(crate) supergraph: Supergraph, @@ -208,6 +222,7 @@ impl<'de> serde::Deserialize<'de> for Configuration { health_check: HealthCheck, sandbox: Sandbox, homepage: Homepage, + server: Server, supergraph: Supergraph, cors: Cors, plugins: UserPlugins, @@ -238,6 +253,7 @@ impl<'de> serde::Deserialize<'de> for Configuration { health_check: ad_hoc.health_check, sandbox: ad_hoc.sandbox, homepage: ad_hoc.homepage, + server: ad_hoc.server, supergraph: ad_hoc.supergraph, cors: ad_hoc.cors, tls: ad_hoc.tls, @@ -291,12 +307,14 @@ impl Configuration { uplink: Option, experimental_type_conditioned_fetching: Option, batching: Option, + server: Option, ) -> Result { let notify = Self::notify(&apollo_plugins)?; let conf = Self { validated_yaml: Default::default(), supergraph: supergraph.unwrap_or_default(), + server: server.unwrap_or_default(), health_check: health_check.unwrap_or_default(), sandbox: sandbox.unwrap_or_default(), homepage: homepage.unwrap_or_default(), @@ -426,9 +444,11 @@ impl Configuration { uplink: Option, batching: Option, experimental_type_conditioned_fetching: Option, + server: Option, ) -> Result { let configuration = Self { validated_yaml: Default::default(), + server: server.unwrap_or_default(), supergraph: supergraph.unwrap_or_else(|| Supergraph::fake_builder().build()), health_check: health_check.unwrap_or_else(|| HealthCheck::fake_builder().build()), sandbox: sandbox.unwrap_or_else(|| Sandbox::fake_builder().build()), diff --git a/apollo-router/src/configuration/tests.rs b/apollo-router/src/configuration/tests.rs index a86a374041..c43d3daa0f 100644 --- a/apollo-router/src/configuration/tests.rs +++ b/apollo-router/src/configuration/tests.rs @@ -1116,6 +1116,30 @@ fn it_processes_specified_maximum_batch_limit_correctly() { assert_eq!(config.maximum_size, Some(10)); } +#[test] +fn it_includes_default_header_read_timeout_when_server_config_omitted() { + let json_config = json!({}); + + let config: Configuration = serde_json::from_value(json_config).unwrap(); + + assert_eq!(config.server.http.header_read_timeout, Duration::from_secs(10)); +} + +#[test] +fn it_processes_specified_server_config_correctly() { + let json_config = json!({ + "server": { + "http": { + "header_read_timeout": "30s" + } + } + }); + + let config: Configuration = serde_json::from_value(json_config).unwrap(); + + assert_eq!(config.server.http.header_read_timeout, Duration::from_secs(30)); +} + fn has_field_level_serde_defaults(lines: &[&str], line_number: usize) -> bool { let serde_field_default = Regex::new( r#"^\s*#[\s\n]*\[serde\s*\((.*,)?\s*default\s*=\s*"[a-zA-Z0-9_:]+"\s*(,.*)?\)\s*\]\s*$"#, From 50e1677f9a3d2bcfb4a6d3a5d4f322a001227883 Mon Sep 17 00:00:00 2001 From: Greg Wardwell Date: Mon, 14 Apr 2025 16:47:46 -0400 Subject: [PATCH 03/10] feat: use server header_read_timeout when creating http_connection (cherry picked from commit bca998b2edf37a54cef51d7af07bdadc9af0456c) # Conflicts: # apollo-router/src/axum_factory/axum_http_server_factory.rs # apollo-router/src/axum_factory/listeners.rs --- .../axum_factory/axum_http_server_factory.rs | 16 +++++ apollo-router/src/axum_factory/listeners.rs | 63 +++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/apollo-router/src/axum_factory/axum_http_server_factory.rs b/apollo-router/src/axum_factory/axum_http_server_factory.rs index 18ad8fe8ae..d18af99019 100644 --- a/apollo-router/src/axum_factory/axum_http_server_factory.rs +++ b/apollo-router/src/axum_factory/axum_http_server_factory.rs @@ -5,8 +5,12 @@ use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; +<<<<<<< HEAD use std::time::Duration; use std::time::Instant; +======= +use std::time::{Duration, Instant}; +>>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) use axum::Router; use axum::error_handling::HandleErrorLayer; @@ -333,8 +337,14 @@ impl HttpServerFactory for AxumHttpServerFactory { configuration.supergraph.connection_shutdown_timeout, actual_main_listen_address.clone(), all_routers.main.1, +<<<<<<< HEAD true, http_config.clone(), +======= + configuration.limits.http1_max_request_headers, + configuration.limits.http1_max_request_buf_size, + configuration.server.http.header_read_timeout, +>>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) all_connections_stopped_sender.clone(), ); @@ -375,8 +385,14 @@ impl HttpServerFactory for AxumHttpServerFactory { configuration.supergraph.connection_shutdown_timeout, listen_addr.clone(), router, +<<<<<<< HEAD false, http_config.clone(), +======= + configuration.limits.http1_max_request_headers, + configuration.limits.http1_max_request_buf_size, + configuration.server.http.header_read_timeout, +>>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) all_connections_stopped_sender.clone(), ); ( diff --git a/apollo-router/src/axum_factory/listeners.rs b/apollo-router/src/axum_factory/listeners.rs index 22d584b9f7..b3cdf1667d 100644 --- a/apollo-router/src/axum_factory/listeners.rs +++ b/apollo-router/src/axum_factory/listeners.rs @@ -268,8 +268,14 @@ pub(super) fn serve_router_on_listen_addr( connection_shutdown_timeout: Duration, address: ListenAddr, router: axum::Router, +<<<<<<< HEAD main_graphql_port: bool, http_config: Http, +======= + opt_max_headers: Option, + opt_max_buf_size: Option, + opt_http_read_timeout: Duration, +>>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) all_connections_stopped_sender: mpsc::Sender<()>, ) -> (impl Future, oneshot::Sender<()>) { let (shutdown_sender, shutdown_receiver) = oneshot::channel::<()>(); @@ -354,7 +360,24 @@ pub(super) fn serve_router_on_listen_addr( "this should not fail unless the socket is invalid", ); +<<<<<<< HEAD let connection = http_config.serve_connection(stream, app); +======= + let mut builder = Builder::new(TokioExecutor::new()); + let mut http_connection = builder.http1(); + let http_config = http_connection + .keep_alive(true) + .timer(TokioTimer::new()) + .header_read_timeout(opt_http_read_timeout); + if let Some(max_headers) = opt_max_headers { + http_config.max_headers(max_headers); + } + + if let Some(max_buf_size) = opt_max_buf_size { + http_config.max_buf_size(max_buf_size.as_u64() as usize); + } + let connection = http_config.serve_connection_with_upgrades(tokio_stream, hyper_service); +>>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) handle_connection!(connection, connection_handle, connection_shutdown, connection_shutdown_timeout, received_first_request); } @@ -362,7 +385,28 @@ pub(super) fn serve_router_on_listen_addr( NetworkStream::Unix(stream) => { let received_first_request = Arc::new(AtomicBool::new(false)); let app = IdleConnectionChecker::new(received_first_request.clone(), app); +<<<<<<< HEAD let connection = http_config.serve_connection(stream, app); +======= + let tokio_stream = TokioIo::new(stream); + let hyper_service = hyper::service::service_fn(move |request| { + app.clone().call(request) + }); + let mut builder = Builder::new(TokioExecutor::new()); + let mut http_connection = builder.http1(); + let http_config = http_connection + .keep_alive(true) + .timer(TokioTimer::new()) + .header_read_timeout(opt_http_read_timeout); + if let Some(max_headers) = opt_max_headers { + http_config.max_headers(max_headers); + } + + if let Some(max_buf_size) = opt_max_buf_size { + http_config.max_buf_size(max_buf_size.as_u64() as usize); + } + let connection = http_config.serve_connection_with_upgrades(tokio_stream, hyper_service); +>>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) handle_connection!(connection, connection_handle, connection_shutdown, connection_shutdown_timeout, received_first_request); }, NetworkStream::Tls(stream) => { @@ -378,6 +422,25 @@ pub(super) fn serve_router_on_listen_addr( let protocol = stream.get_ref().1.alpn_protocol(); let http2 = protocol == Some(&b"h2"[..]); +<<<<<<< HEAD +======= + let tokio_stream = TokioIo::new(stream); + let hyper_service = hyper::service::service_fn(move |request| { + app.clone().call(request) + }); + let mut http_connection = builder.http1(); + let http_config = http_connection + .keep_alive(true) + .timer(TokioTimer::new()) + .header_read_timeout(opt_http_read_timeout); + if let Some(max_headers) = opt_max_headers { + http_config.max_headers(max_headers); + } + + if let Some(max_buf_size) = opt_max_buf_size { + http_config.max_buf_size(max_buf_size.as_u64() as usize); + } +>>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) let connection = http_config .http2_only(http2) .serve_connection(stream, app); From 4bcb6cef6a7bb13c0183f21f45f0a6c176d79192 Mon Sep 17 00:00:00 2001 From: Greg Wardwell Date: Mon, 14 Apr 2025 17:10:43 -0400 Subject: [PATCH 04/10] style: fix lint errors (cherry picked from commit de2127f56d1cab4d780d4d9b44449ffe68710bfb) # Conflicts: # apollo-router/src/axum_factory/axum_http_server_factory.rs # apollo-router/src/axum_factory/listeners.rs --- .../axum_factory/axum_http_server_factory.rs | 12 ++++++ apollo-router/src/axum_factory/listeners.rs | 10 +++-- apollo-router/src/configuration/server.rs | 40 +++++-------------- apollo-router/src/configuration/tests.rs | 10 ++++- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/apollo-router/src/axum_factory/axum_http_server_factory.rs b/apollo-router/src/axum_factory/axum_http_server_factory.rs index d18af99019..0df269bcbb 100644 --- a/apollo-router/src/axum_factory/axum_http_server_factory.rs +++ b/apollo-router/src/axum_factory/axum_http_server_factory.rs @@ -6,11 +6,15 @@ use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; <<<<<<< HEAD +<<<<<<< HEAD use std::time::Duration; use std::time::Instant; ======= use std::time::{Duration, Instant}; >>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) +======= +use std::time::Instant; +>>>>>>> de2127f5 (style: fix lint errors) use axum::Router; use axum::error_handling::HandleErrorLayer; @@ -343,8 +347,12 @@ impl HttpServerFactory for AxumHttpServerFactory { ======= configuration.limits.http1_max_request_headers, configuration.limits.http1_max_request_buf_size, +<<<<<<< HEAD configuration.server.http.header_read_timeout, >>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) +======= + configuration.server.http.header_read_timeout, +>>>>>>> de2127f5 (style: fix lint errors) all_connections_stopped_sender.clone(), ); @@ -391,8 +399,12 @@ impl HttpServerFactory for AxumHttpServerFactory { ======= configuration.limits.http1_max_request_headers, configuration.limits.http1_max_request_buf_size, +<<<<<<< HEAD configuration.server.http.header_read_timeout, >>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) +======= + configuration.server.http.header_read_timeout, +>>>>>>> de2127f5 (style: fix lint errors) all_connections_stopped_sender.clone(), ); ( diff --git a/apollo-router/src/axum_factory/listeners.rs b/apollo-router/src/axum_factory/listeners.rs index b3cdf1667d..a6af26990f 100644 --- a/apollo-router/src/axum_factory/listeners.rs +++ b/apollo-router/src/axum_factory/listeners.rs @@ -274,8 +274,12 @@ pub(super) fn serve_router_on_listen_addr( ======= opt_max_headers: Option, opt_max_buf_size: Option, +<<<<<<< HEAD opt_http_read_timeout: Duration, >>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) +======= + http_read_timeout: Duration, +>>>>>>> de2127f5 (style: fix lint errors) all_connections_stopped_sender: mpsc::Sender<()>, ) -> (impl Future, oneshot::Sender<()>) { let (shutdown_sender, shutdown_receiver) = oneshot::channel::<()>(); @@ -368,7 +372,7 @@ pub(super) fn serve_router_on_listen_addr( let http_config = http_connection .keep_alive(true) .timer(TokioTimer::new()) - .header_read_timeout(opt_http_read_timeout); + .header_read_timeout(http_read_timeout); if let Some(max_headers) = opt_max_headers { http_config.max_headers(max_headers); } @@ -397,7 +401,7 @@ pub(super) fn serve_router_on_listen_addr( let http_config = http_connection .keep_alive(true) .timer(TokioTimer::new()) - .header_read_timeout(opt_http_read_timeout); + .header_read_timeout(http_read_timeout); if let Some(max_headers) = opt_max_headers { http_config.max_headers(max_headers); } @@ -432,7 +436,7 @@ pub(super) fn serve_router_on_listen_addr( let http_config = http_connection .keep_alive(true) .timer(TokioTimer::new()) - .header_read_timeout(opt_http_read_timeout); + .header_read_timeout(http_read_timeout); if let Some(max_headers) = opt_max_headers { http_config.max_headers(max_headers); } diff --git a/apollo-router/src/configuration/server.rs b/apollo-router/src/configuration/server.rs index 010e85ec01..a2ed812671 100644 --- a/apollo-router/src/configuration/server.rs +++ b/apollo-router/src/configuration/server.rs @@ -1,7 +1,9 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use std::time::Duration; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; + const DEFAULT_HEADER_READ_TIMEOUT: Duration = Duration::from_secs(10); fn default_header_read_timeout() -> Duration { @@ -36,16 +38,6 @@ impl Default for ServerHttpConfig { } } -#[buildstructor::buildstructor] -impl ServerHttpConfig { - #[builder] - pub(crate) fn new(header_read_timeout: Option) -> Self { - Self { - header_read_timeout: header_read_timeout.unwrap_or_default(), - } - } -} - #[buildstructor::buildstructor] impl Server { #[builder] @@ -65,6 +57,7 @@ impl Default for Server { #[cfg(test)] mod tests { use serde_json::json; + use super::*; #[test] @@ -77,21 +70,6 @@ mod tests { ); } - #[test] - fn it_builds_custom_server_configuration() { - let duration_seconds = Duration::from_secs(30); - let http_config = ServerHttpConfig::builder() - .header_read_timeout(duration_seconds) - .build(); - let server_config = Server::builder() - .http(http_config) - .build(); - assert_eq!( - server_config.http.header_read_timeout, - duration_seconds - ); - } - #[test] fn it_json_parses_default_header_read_timeout_when_server_http_config_omitted() { let json_server = json!({}); @@ -115,10 +93,10 @@ mod tests { #[test] fn it_json_parses_specified_server_config_seconds_correctly() { let json_config = json!({ - "http": { - "header_read_timeout": "30s" - } - }); + "http": { + "header_read_timeout": "30s" + } + }); let config: Server = serde_json::from_value(json_config).unwrap(); diff --git a/apollo-router/src/configuration/tests.rs b/apollo-router/src/configuration/tests.rs index c43d3daa0f..6dd96588cd 100644 --- a/apollo-router/src/configuration/tests.rs +++ b/apollo-router/src/configuration/tests.rs @@ -1122,7 +1122,10 @@ fn it_includes_default_header_read_timeout_when_server_config_omitted() { let config: Configuration = serde_json::from_value(json_config).unwrap(); - assert_eq!(config.server.http.header_read_timeout, Duration::from_secs(10)); + assert_eq!( + config.server.http.header_read_timeout, + Duration::from_secs(10) + ); } #[test] @@ -1137,7 +1140,10 @@ fn it_processes_specified_server_config_correctly() { let config: Configuration = serde_json::from_value(json_config).unwrap(); - assert_eq!(config.server.http.header_read_timeout, Duration::from_secs(30)); + assert_eq!( + config.server.http.header_read_timeout, + Duration::from_secs(30) + ); } fn has_field_level_serde_defaults(lines: &[&str], line_number: usize) -> bool { From 686cd4879217aea52143f311f3f1a7f1a934a980 Mon Sep 17 00:00:00 2001 From: Greg Wardwell Date: Mon, 14 Apr 2025 21:28:38 -0400 Subject: [PATCH 05/10] test: update configuration test snapshot (cherry picked from commit 1b195743f3aa750f570895f712d85bbba71df23e) # Conflicts: # apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap --- ...nfiguration__tests__schema_generation.snap | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 014b8148c8..e8db0c87dc 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -5326,8 +5326,35 @@ expression: "&schema" } ] }, +<<<<<<< HEAD "SocketEndpoint": { "type": "string" +======= + "Server": { + "additionalProperties": false, + "properties": { + "http": { + "$ref": "#/definitions/ServerHttpConfig", + "description": "#/definitions/ServerHttpConfig" + } + }, + "type": "object" + }, + "ServerHttpConfig": { + "additionalProperties": false, + "description": "Configuration for HTTP", + "properties": { + "header_read_timeout": { + "default": { + "nanos": 0, + "secs": 10 + }, + "description": "Header read timeout in human-readable format; defaults to 10s", + "type": "string" + } + }, + "type": "object" +>>>>>>> 1b195743 (test: update configuration test snapshot) }, "Source": { "oneOf": [ @@ -8359,6 +8386,10 @@ expression: "&schema" "$ref": "#/definitions/Sandbox", "description": "#/definitions/Sandbox" }, + "server": { + "$ref": "#/definitions/Server", + "description": "#/definitions/Server" + }, "subscription": { "$ref": "#/definitions/SubscriptionConfig", "description": "#/definitions/SubscriptionConfig" From 03ea6e56ec61be2dd33edf78a30723794bf5a3ac Mon Sep 17 00:00:00 2001 From: Greg Wardwell Date: Thu, 17 Apr 2025 08:51:29 -0400 Subject: [PATCH 06/10] docs: add header_read_timeout to configuration docs (cherry picked from commit b9099ffad72d5568ff0b6f55d4ef66ae214a2b63) --- docs/source/routing/configuration.mdx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/source/routing/configuration.mdx b/docs/source/routing/configuration.mdx index 2454f5c041..68a7a1758d 100644 --- a/docs/source/routing/configuration.mdx +++ b/docs/source/routing/configuration.mdx @@ -1211,6 +1211,18 @@ traffic_shaping: timeout: 60s ``` +### Header Read Timeout + +The header read timeout is the amount of time the Router will wait to receive the complete request headers from a client before timing out. It applies both when the connection is fully idle and when a request has been started but sending the headers has not been completed. + +By default, the header read timeout is set to 10 seconds. A longer timeout can be configured using the `server.http.header_read_timeout` configuration option. + +```yaml title="router.yaml" +server: + http: + header_read_timeout: 30s +``` + ### Plugins You can customize the router's behavior with [plugins](/router/customizations/overview). Each plugin can have its own section in the configuration file with arbitrary values: @@ -1324,4 +1336,4 @@ You can also view a diff of exactly which changes are necessary to upgrade your ## Related topics -- [Checklist for configuring the router for production](/technotes/TN0008-production-readiness-checklist/#apollo-router) +- [Checklist for configuring the router for production](/technotes/TN0008-production-readiness-checklist/#apollo-router) \ No newline at end of file From 3c7d488549e9657fcc046a0661f97dadba07e457 Mon Sep 17 00:00:00 2001 From: Greg Wardwell Date: Thu, 17 Apr 2025 09:10:53 -0400 Subject: [PATCH 07/10] chore: add changeset for header read timeout (cherry picked from commit 8e2289623111f8463543fbf16a58aac947792b60) --- .changesets/config_header_read_timeout.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changesets/config_header_read_timeout.md diff --git a/.changesets/config_header_read_timeout.md b/.changesets/config_header_read_timeout.md new file mode 100644 index 0000000000..fa006ae661 --- /dev/null +++ b/.changesets/config_header_read_timeout.md @@ -0,0 +1,13 @@ +### Add configurable server header read timeout ([PR #7262](https://github.com/apollographql/router/pull/7262)) + +This change exposes the server's header read timeout as the `server.http.header_read_timeout` configuration option. + +By default, the `server.http.header_read_timeout` is set to previously hard-coded 10 seconds. A longer timeout can be configured using the `server.http.header_read_timeout` option. + +```yaml title="router.yaml" +server: + http: + header_read_timeout: 30s +``` + +By [@gwardwell ](https://github.com/gwardwell) in https://github.com/apollographql/router/pull/7262 From aa354feb815e98e28de9a5cf555276124f1bd1ee Mon Sep 17 00:00:00 2001 From: Greg Wardwell Date: Thu, 17 Apr 2025 09:19:10 -0400 Subject: [PATCH 08/10] refactor: fix header_read_timeout variable name (cherry picked from commit f0aa17d841b643f040c055f85fec4d6a53e6570d) # Conflicts: # apollo-router/src/axum_factory/listeners.rs --- apollo-router/src/axum_factory/listeners.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apollo-router/src/axum_factory/listeners.rs b/apollo-router/src/axum_factory/listeners.rs index a6af26990f..9b5294b1bb 100644 --- a/apollo-router/src/axum_factory/listeners.rs +++ b/apollo-router/src/axum_factory/listeners.rs @@ -274,12 +274,16 @@ pub(super) fn serve_router_on_listen_addr( ======= opt_max_headers: Option, opt_max_buf_size: Option, +<<<<<<< HEAD <<<<<<< HEAD opt_http_read_timeout: Duration, >>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) ======= http_read_timeout: Duration, >>>>>>> de2127f5 (style: fix lint errors) +======= + header_read_timeout: Duration, +>>>>>>> f0aa17d8 (refactor: fix header_read_timeout variable name) all_connections_stopped_sender: mpsc::Sender<()>, ) -> (impl Future, oneshot::Sender<()>) { let (shutdown_sender, shutdown_receiver) = oneshot::channel::<()>(); @@ -372,7 +376,7 @@ pub(super) fn serve_router_on_listen_addr( let http_config = http_connection .keep_alive(true) .timer(TokioTimer::new()) - .header_read_timeout(http_read_timeout); + .header_read_timeout(header_read_timeout); if let Some(max_headers) = opt_max_headers { http_config.max_headers(max_headers); } @@ -401,7 +405,7 @@ pub(super) fn serve_router_on_listen_addr( let http_config = http_connection .keep_alive(true) .timer(TokioTimer::new()) - .header_read_timeout(http_read_timeout); + .header_read_timeout(header_read_timeout); if let Some(max_headers) = opt_max_headers { http_config.max_headers(max_headers); } @@ -436,7 +440,7 @@ pub(super) fn serve_router_on_listen_addr( let http_config = http_connection .keep_alive(true) .timer(TokioTimer::new()) - .header_read_timeout(http_read_timeout); + .header_read_timeout(header_read_timeout); if let Some(max_headers) = opt_max_headers { http_config.max_headers(max_headers); } From c4eae7bd31f136be061678b0ac38c414fcbbace7 Mon Sep 17 00:00:00 2001 From: Greg Wardwell Date: Mon, 28 Apr 2025 12:34:02 -0400 Subject: [PATCH 09/10] fix: fix merge conflicts --- .../axum_factory/axum_http_server_factory.rs | 31 +------- apollo-router/src/axum_factory/listeners.rs | 71 ------------------- apollo-router/src/configuration/mod.rs | 8 --- 3 files changed, 1 insertion(+), 109 deletions(-) diff --git a/apollo-router/src/axum_factory/axum_http_server_factory.rs b/apollo-router/src/axum_factory/axum_http_server_factory.rs index 0df269bcbb..216824e293 100644 --- a/apollo-router/src/axum_factory/axum_http_server_factory.rs +++ b/apollo-router/src/axum_factory/axum_http_server_factory.rs @@ -5,16 +5,7 @@ use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; -<<<<<<< HEAD -<<<<<<< HEAD -use std::time::Duration; use std::time::Instant; -======= -use std::time::{Duration, Instant}; ->>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) -======= -use std::time::Instant; ->>>>>>> de2127f5 (style: fix lint errors) use axum::Router; use axum::error_handling::HandleErrorLayer; @@ -324,7 +315,7 @@ impl HttpServerFactory for AxumHttpServerFactory { .map_err(ApolloRouterError::ServerCreationError)?; let mut http_config = Http::new(); http_config.http1_keep_alive(true); - http_config.http1_header_read_timeout(Duration::from_secs(10)); + http_config.http1_header_read_timeout(configuration.server.http.header_read_timeout); #[cfg(feature = "hyper_header_limits")] if let Some(max_headers) = configuration.limits.http1_max_request_headers { @@ -341,18 +332,8 @@ impl HttpServerFactory for AxumHttpServerFactory { configuration.supergraph.connection_shutdown_timeout, actual_main_listen_address.clone(), all_routers.main.1, -<<<<<<< HEAD true, http_config.clone(), -======= - configuration.limits.http1_max_request_headers, - configuration.limits.http1_max_request_buf_size, -<<<<<<< HEAD - configuration.server.http.header_read_timeout, ->>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) -======= - configuration.server.http.header_read_timeout, ->>>>>>> de2127f5 (style: fix lint errors) all_connections_stopped_sender.clone(), ); @@ -393,18 +374,8 @@ impl HttpServerFactory for AxumHttpServerFactory { configuration.supergraph.connection_shutdown_timeout, listen_addr.clone(), router, -<<<<<<< HEAD false, http_config.clone(), -======= - configuration.limits.http1_max_request_headers, - configuration.limits.http1_max_request_buf_size, -<<<<<<< HEAD - configuration.server.http.header_read_timeout, ->>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) -======= - configuration.server.http.header_read_timeout, ->>>>>>> de2127f5 (style: fix lint errors) all_connections_stopped_sender.clone(), ); ( diff --git a/apollo-router/src/axum_factory/listeners.rs b/apollo-router/src/axum_factory/listeners.rs index 9b5294b1bb..22d584b9f7 100644 --- a/apollo-router/src/axum_factory/listeners.rs +++ b/apollo-router/src/axum_factory/listeners.rs @@ -268,22 +268,8 @@ pub(super) fn serve_router_on_listen_addr( connection_shutdown_timeout: Duration, address: ListenAddr, router: axum::Router, -<<<<<<< HEAD main_graphql_port: bool, http_config: Http, -======= - opt_max_headers: Option, - opt_max_buf_size: Option, -<<<<<<< HEAD -<<<<<<< HEAD - opt_http_read_timeout: Duration, ->>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) -======= - http_read_timeout: Duration, ->>>>>>> de2127f5 (style: fix lint errors) -======= - header_read_timeout: Duration, ->>>>>>> f0aa17d8 (refactor: fix header_read_timeout variable name) all_connections_stopped_sender: mpsc::Sender<()>, ) -> (impl Future, oneshot::Sender<()>) { let (shutdown_sender, shutdown_receiver) = oneshot::channel::<()>(); @@ -368,24 +354,7 @@ pub(super) fn serve_router_on_listen_addr( "this should not fail unless the socket is invalid", ); -<<<<<<< HEAD let connection = http_config.serve_connection(stream, app); -======= - let mut builder = Builder::new(TokioExecutor::new()); - let mut http_connection = builder.http1(); - let http_config = http_connection - .keep_alive(true) - .timer(TokioTimer::new()) - .header_read_timeout(header_read_timeout); - if let Some(max_headers) = opt_max_headers { - http_config.max_headers(max_headers); - } - - if let Some(max_buf_size) = opt_max_buf_size { - http_config.max_buf_size(max_buf_size.as_u64() as usize); - } - let connection = http_config.serve_connection_with_upgrades(tokio_stream, hyper_service); ->>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) handle_connection!(connection, connection_handle, connection_shutdown, connection_shutdown_timeout, received_first_request); } @@ -393,28 +362,7 @@ pub(super) fn serve_router_on_listen_addr( NetworkStream::Unix(stream) => { let received_first_request = Arc::new(AtomicBool::new(false)); let app = IdleConnectionChecker::new(received_first_request.clone(), app); -<<<<<<< HEAD let connection = http_config.serve_connection(stream, app); -======= - let tokio_stream = TokioIo::new(stream); - let hyper_service = hyper::service::service_fn(move |request| { - app.clone().call(request) - }); - let mut builder = Builder::new(TokioExecutor::new()); - let mut http_connection = builder.http1(); - let http_config = http_connection - .keep_alive(true) - .timer(TokioTimer::new()) - .header_read_timeout(header_read_timeout); - if let Some(max_headers) = opt_max_headers { - http_config.max_headers(max_headers); - } - - if let Some(max_buf_size) = opt_max_buf_size { - http_config.max_buf_size(max_buf_size.as_u64() as usize); - } - let connection = http_config.serve_connection_with_upgrades(tokio_stream, hyper_service); ->>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) handle_connection!(connection, connection_handle, connection_shutdown, connection_shutdown_timeout, received_first_request); }, NetworkStream::Tls(stream) => { @@ -430,25 +378,6 @@ pub(super) fn serve_router_on_listen_addr( let protocol = stream.get_ref().1.alpn_protocol(); let http2 = protocol == Some(&b"h2"[..]); -<<<<<<< HEAD -======= - let tokio_stream = TokioIo::new(stream); - let hyper_service = hyper::service::service_fn(move |request| { - app.clone().call(request) - }); - let mut http_connection = builder.http1(); - let http_config = http_connection - .keep_alive(true) - .timer(TokioTimer::new()) - .header_read_timeout(header_read_timeout); - if let Some(max_headers) = opt_max_headers { - http_config.max_headers(max_headers); - } - - if let Some(max_buf_size) = opt_max_buf_size { - http_config.max_buf_size(max_buf_size.as_u64() as usize); - } ->>>>>>> bca998b2 (feat: use server header_read_timeout when creating http_connection) let connection = http_config .http2_only(http2) .serve_connection(stream, app); diff --git a/apollo-router/src/configuration/mod.rs b/apollo-router/src/configuration/mod.rs index ce7ed1273b..3efc7db885 100644 --- a/apollo-router/src/configuration/mod.rs +++ b/apollo-router/src/configuration/mod.rs @@ -45,11 +45,7 @@ use self::expansion::Expansion; pub(crate) use self::experimental::Discussed; pub(crate) use self::schema::generate_config_schema; pub(crate) use self::schema::generate_upgrade; -<<<<<<< HEAD -======= -pub(crate) use self::schema::validate_yaml_configuration; use self::server::Server; ->>>>>>> 8683f3a6 (feat: add server configuration to router config) use self::subgraph::SubgraphConfiguration; use crate::ApolloRouterError; use crate::cache::DEFAULT_CACHE_CAPACITY; @@ -68,12 +64,8 @@ pub(crate) mod expansion; mod experimental; pub(crate) mod metrics; mod persisted_queries; -<<<<<<< HEAD mod schema; -======= -pub(crate) mod schema; pub(crate) mod server; ->>>>>>> 8683f3a6 (feat: add server configuration to router config) pub(crate) mod shared; pub(crate) mod subgraph; #[cfg(test)] From 2dc3ec1767141420b2c5031b7dccd968ddad9ef6 Mon Sep 17 00:00:00 2001 From: Greg Wardwell Date: Mon, 28 Apr 2025 13:36:55 -0400 Subject: [PATCH 10/10] fix: fix merge conflicts in test fixture --- ...o_router__configuration__tests__schema_generation.snap | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index e8db0c87dc..37f70fc2d8 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -5326,10 +5326,6 @@ expression: "&schema" } ] }, -<<<<<<< HEAD - "SocketEndpoint": { - "type": "string" -======= "Server": { "additionalProperties": false, "properties": { @@ -5354,7 +5350,9 @@ expression: "&schema" } }, "type": "object" ->>>>>>> 1b195743 (test: update configuration test snapshot) + }, + "SocketEndpoint": { + "type": "string" }, "Source": { "oneOf": [