From b0740770df8f645c50f9a6c783fe061c9d5b9b8f Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Tue, 28 Apr 2026 07:49:47 -0700 Subject: [PATCH] Increase HTTP/2 response header limit Set Yaak's reqwest request clients to accept HTTP/2 response header lists up to 1 MiB and wrap configured clients so the sender path cannot accidentally bypass the shared builder. Feedback: https://yaak.app/feedback/posts/when-response-headers-exceed-a-certain-size-hyper-throws-error --- crates/yaak-http/src/client.rs | 33 +++++++++++++++++++++++++++++---- crates/yaak-http/src/manager.rs | 5 ++--- crates/yaak-http/src/sender.rs | 14 +++++++------- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/crates/yaak-http/src/client.rs b/crates/yaak-http/src/client.rs index 26be82c9..f9f7cbb5 100644 --- a/crates/yaak-http/src/client.rs +++ b/crates/yaak-http/src/client.rs @@ -1,11 +1,36 @@ use crate::dns::LocalhostResolver; use crate::error::Result; use log::{debug, info, warn}; -use reqwest::{Client, Proxy, redirect}; +use reqwest::{Client, ClientBuilder, Proxy, redirect}; use std::sync::Arc; use yaak_models::models::DnsOverride; use yaak_tls::{ClientCertificateConfig, get_tls_config}; +pub const HTTP2_MAX_RESPONSE_HEADER_LIST_SIZE: u32 = 1024 * 1024; + +fn client_builder() -> ClientBuilder { + Client::builder().http2_max_header_list_size(HTTP2_MAX_RESPONSE_HEADER_LIST_SIZE) +} + +#[derive(Clone)] +pub struct ConfiguredClient { + inner: Client, +} + +impl ConfiguredClient { + pub(crate) fn build_default() -> Result { + Ok(Self { inner: client_builder().build()? }) + } + + pub(crate) fn from_inner(inner: Client) -> Self { + Self { inner } + } + + pub(crate) fn inner(&self) -> &Client { + &self.inner + } +} + #[derive(Clone)] pub struct HttpConnectionProxySettingAuth { pub user: String, @@ -37,8 +62,8 @@ impl HttpConnectionOptions { /// Build a reqwest Client and return it along with the DNS resolver. /// The resolver is returned separately so it can be configured per-request /// to emit DNS timing events to the appropriate channel. - pub(crate) fn build_client(&self) -> Result<(Client, Arc)> { - let mut client = Client::builder() + pub(crate) fn build_client(&self) -> Result<(ConfiguredClient, Arc)> { + let mut client = client_builder() .connection_verbose(true) .redirect(redirect::Policy::none()) // Decompression is handled by HttpTransaction, not reqwest @@ -79,7 +104,7 @@ impl HttpConnectionOptions { self.client_certificate.is_some() ); - Ok((client.build()?, resolver)) + Ok((ConfiguredClient::from_inner(client.build()?), resolver)) } } diff --git a/crates/yaak-http/src/manager.rs b/crates/yaak-http/src/manager.rs index 2034f124..cb9555d7 100644 --- a/crates/yaak-http/src/manager.rs +++ b/crates/yaak-http/src/manager.rs @@ -1,7 +1,6 @@ -use crate::client::HttpConnectionOptions; +use crate::client::{ConfiguredClient, HttpConnectionOptions}; use crate::dns::LocalhostResolver; use crate::error::Result; -use reqwest::Client; use std::collections::BTreeMap; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -10,7 +9,7 @@ use tokio::sync::RwLock; /// A cached HTTP client along with its DNS resolver. /// The resolver is needed to set the event sender per-request. pub struct CachedClient { - pub client: Client, + pub client: ConfiguredClient, pub resolver: Arc, } diff --git a/crates/yaak-http/src/sender.rs b/crates/yaak-http/src/sender.rs index 32059a68..063f0a47 100644 --- a/crates/yaak-http/src/sender.rs +++ b/crates/yaak-http/src/sender.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use bytes::Bytes; use futures_util::StreamExt; use http_body::{Body as HttpBody, Frame, SizeHint}; -use reqwest::{Client, Method, Version}; +use reqwest::{Method, Version}; use std::fmt::Display; use std::pin::Pin; use std::task::{Context, Poll}; @@ -411,18 +411,18 @@ pub trait HttpSender: Send + Sync { /// Reqwest-based implementation of HttpSender pub struct ReqwestSender { - client: Client, + client: crate::client::ConfiguredClient, } impl ReqwestSender { /// Create a new ReqwestSender with a default client pub fn new() -> Result { - let client = Client::builder().build().map_err(Error::Client)?; + let client = crate::client::ConfiguredClient::build_default()?; Ok(Self { client }) } - /// Create a new ReqwestSender with a custom client - pub fn with_client(client: Client) -> Self { + /// Create a new ReqwestSender with a configured client + pub fn with_client(client: crate::client::ConfiguredClient) -> Self { Self { client } } } @@ -444,7 +444,7 @@ impl HttpSender for ReqwestSender { .map_err(|e| Error::RequestError(format!("Invalid HTTP method: {}", e)))?; // Build the request - let mut req_builder = self.client.request(method, &request.url); + let mut req_builder = self.client.inner().request(method, &request.url); // Add headers for header in request.headers { @@ -513,7 +513,7 @@ impl HttpSender for ReqwestSender { send_event(HttpResponseEvent::Info("Sending request to server".to_string())); // Map some errors to our own, so they look nicer - let response = self.client.execute(sendable_req).await.map_err(|e| { + let response = self.client.inner().execute(sendable_req).await.map_err(|e| { if reqwest::Error::is_timeout(&e) { Error::RequestTimeout( request.options.timeout.unwrap_or(Duration::from_secs(0)).clone(),