Wire upstream_connect_timeout_secs to enforce separate connect timeout
Implement two-phase timeout in proxy_handler: - Inner timeout uses per-site upstream_connect_timeout_secs (default 5s) for the connect + first-byte phase - Outer timeout uses upstream_request_timeout_secs (default 60s) for the full request/response cycle - Set connect_timeout on HttpConnector for both HTTP and HTTPS clients (default 5s) to enforce TCP-level connect timeouts - Use wrap_connector for HTTPS client to apply connect_timeout on the underlying HttpConnector - Add Ok(Err(_)) handler for connect timeout returning 504 Gateway Timeout
This commit is contained in:
@@ -80,23 +80,23 @@ async fn proxy_handler(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let connect_timeout = Duration::from_secs(site.upstream_connect_timeout_secs);
|
||||||
let request_timeout = Duration::from_secs(site.upstream_request_timeout_secs);
|
let request_timeout = Duration::from_secs(site.upstream_request_timeout_secs);
|
||||||
// The timeout covers the entire HTTP round-trip including response body
|
|
||||||
// streaming. For large file downloads or slow upstreams, this means the
|
|
||||||
// timeout kills the response even if the upstream is actively sending data.
|
|
||||||
// A more precise timeout would apply only to connect + first-byte, then
|
|
||||||
// stream the body without a timeout. The `upstream_connect_timeout_secs`
|
|
||||||
// field in SiteConfig exists for a separate connect timeout (see W4).
|
|
||||||
// For Phase 1, this full-request timeout is acceptable.
|
|
||||||
|
|
||||||
let result = if upstream_scheme == "https" {
|
let result = if upstream_scheme == "https" {
|
||||||
tokio::time::timeout(request_timeout, state.https_client.request(upstream_req)).await
|
tokio::time::timeout(request_timeout, async {
|
||||||
|
tokio::time::timeout(connect_timeout, state.https_client.request(upstream_req)).await
|
||||||
|
})
|
||||||
|
.await
|
||||||
} else {
|
} else {
|
||||||
tokio::time::timeout(request_timeout, state.http_client.request(upstream_req)).await
|
tokio::time::timeout(request_timeout, async {
|
||||||
|
tokio::time::timeout(connect_timeout, state.http_client.request(upstream_req)).await
|
||||||
|
})
|
||||||
|
.await
|
||||||
};
|
};
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(Ok(upstream_resp)) => {
|
Ok(Ok(Ok(upstream_resp))) => {
|
||||||
let status = upstream_resp.status().as_u16();
|
let status = upstream_resp.status().as_u16();
|
||||||
let duration_ms = start.elapsed().as_millis() as u64;
|
let duration_ms = start.elapsed().as_millis() as u64;
|
||||||
log_request!(
|
log_request!(
|
||||||
@@ -114,7 +114,7 @@ async fn proxy_handler(
|
|||||||
let body = Body::new(body);
|
let body = Body::new(body);
|
||||||
Response::from_parts(parts, body)
|
Response::from_parts(parts, body)
|
||||||
}
|
}
|
||||||
Ok(Err(e)) => {
|
Ok(Ok(Err(e))) => {
|
||||||
let duration_ms = start.elapsed().as_millis() as u64;
|
let duration_ms = start.elapsed().as_millis() as u64;
|
||||||
if e.is_connect() {
|
if e.is_connect() {
|
||||||
log_upstream_error!(&host_owned, &upstream_addr, &format!("{}", e));
|
log_upstream_error!(&host_owned, &upstream_addr, &format!("{}", e));
|
||||||
@@ -148,6 +148,21 @@ async fn proxy_handler(
|
|||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(Err(_)) => {
|
||||||
|
let duration_ms = start.elapsed().as_millis() as u64;
|
||||||
|
log_upstream_error!(&host_owned, &upstream_addr, "upstream connect timeout");
|
||||||
|
let resp = ProxyError::UpstreamTimeout.into_response();
|
||||||
|
log_request!(
|
||||||
|
&client_ip,
|
||||||
|
&host_owned,
|
||||||
|
&method,
|
||||||
|
&path,
|
||||||
|
504u16,
|
||||||
|
&upstream,
|
||||||
|
duration_ms
|
||||||
|
);
|
||||||
|
resp
|
||||||
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let duration_ms = start.elapsed().as_millis() as u64;
|
let duration_ms = start.elapsed().as_millis() as u64;
|
||||||
log_upstream_error!(&host_owned, &upstream_addr, "upstream timeout");
|
log_upstream_error!(&host_owned, &upstream_addr, "upstream timeout");
|
||||||
@@ -192,13 +207,21 @@ fn build_upstream_request(req: Request<Body>, upstream_uri: &Uri) -> anyhow::Res
|
|||||||
builder.body(req.into_body()).map_err(Into::into)
|
builder.body(req.into_body()).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CONNECT_TIMEOUT_SECS: u64 = 5;
|
||||||
|
|
||||||
pub fn create_http_client() -> Client<HttpConnector, Body> {
|
pub fn create_http_client() -> Client<HttpConnector, Body> {
|
||||||
|
let mut connector = HttpConnector::new();
|
||||||
|
connector.set_connect_timeout(Some(Duration::from_secs(DEFAULT_CONNECT_TIMEOUT_SECS)));
|
||||||
Client::builder(TokioExecutor::new())
|
Client::builder(TokioExecutor::new())
|
||||||
.pool_idle_timeout(Duration::from_secs(90))
|
.pool_idle_timeout(Duration::from_secs(90))
|
||||||
.build_http()
|
.build(connector)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_https_client() -> Client<hyper_rustls::HttpsConnector<HttpConnector>, Body> {
|
pub fn create_https_client() -> Client<hyper_rustls::HttpsConnector<HttpConnector>, Body> {
|
||||||
|
let mut http_connector = HttpConnector::new();
|
||||||
|
http_connector.set_connect_timeout(Some(Duration::from_secs(DEFAULT_CONNECT_TIMEOUT_SECS)));
|
||||||
|
http_connector.enforce_http(false);
|
||||||
|
|
||||||
let tls_config = rustls::ClientConfig::builder()
|
let tls_config = rustls::ClientConfig::builder()
|
||||||
.with_root_certificates(root_certs())
|
.with_root_certificates(root_certs())
|
||||||
.with_no_client_auth();
|
.with_no_client_auth();
|
||||||
@@ -207,7 +230,7 @@ pub fn create_https_client() -> Client<hyper_rustls::HttpsConnector<HttpConnecto
|
|||||||
.with_tls_config(tls_config)
|
.with_tls_config(tls_config)
|
||||||
.https_or_http()
|
.https_or_http()
|
||||||
.enable_http1()
|
.enable_http1()
|
||||||
.build();
|
.wrap_connector(http_connector);
|
||||||
|
|
||||||
Client::builder(TokioExecutor::new())
|
Client::builder(TokioExecutor::new())
|
||||||
.pool_idle_timeout(Duration::from_secs(90))
|
.pool_idle_timeout(Duration::from_secs(90))
|
||||||
|
|||||||
Reference in New Issue
Block a user