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
This commit is contained in:
Gregory Schier
2026-04-28 07:49:47 -07:00
parent 75d94da578
commit b0740770df
3 changed files with 38 additions and 14 deletions
+29 -4
View File
@@ -1,11 +1,36 @@
use crate::dns::LocalhostResolver; use crate::dns::LocalhostResolver;
use crate::error::Result; use crate::error::Result;
use log::{debug, info, warn}; use log::{debug, info, warn};
use reqwest::{Client, Proxy, redirect}; use reqwest::{Client, ClientBuilder, Proxy, redirect};
use std::sync::Arc; use std::sync::Arc;
use yaak_models::models::DnsOverride; use yaak_models::models::DnsOverride;
use yaak_tls::{ClientCertificateConfig, get_tls_config}; 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<Self> {
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)] #[derive(Clone)]
pub struct HttpConnectionProxySettingAuth { pub struct HttpConnectionProxySettingAuth {
pub user: String, pub user: String,
@@ -37,8 +62,8 @@ impl HttpConnectionOptions {
/// Build a reqwest Client and return it along with the DNS resolver. /// Build a reqwest Client and return it along with the DNS resolver.
/// The resolver is returned separately so it can be configured per-request /// The resolver is returned separately so it can be configured per-request
/// to emit DNS timing events to the appropriate channel. /// to emit DNS timing events to the appropriate channel.
pub(crate) fn build_client(&self) -> Result<(Client, Arc<LocalhostResolver>)> { pub(crate) fn build_client(&self) -> Result<(ConfiguredClient, Arc<LocalhostResolver>)> {
let mut client = Client::builder() let mut client = client_builder()
.connection_verbose(true) .connection_verbose(true)
.redirect(redirect::Policy::none()) .redirect(redirect::Policy::none())
// Decompression is handled by HttpTransaction, not reqwest // Decompression is handled by HttpTransaction, not reqwest
@@ -79,7 +104,7 @@ impl HttpConnectionOptions {
self.client_certificate.is_some() self.client_certificate.is_some()
); );
Ok((client.build()?, resolver)) Ok((ConfiguredClient::from_inner(client.build()?), resolver))
} }
} }
+2 -3
View File
@@ -1,7 +1,6 @@
use crate::client::HttpConnectionOptions; use crate::client::{ConfiguredClient, HttpConnectionOptions};
use crate::dns::LocalhostResolver; use crate::dns::LocalhostResolver;
use crate::error::Result; use crate::error::Result;
use reqwest::Client;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -10,7 +9,7 @@ use tokio::sync::RwLock;
/// A cached HTTP client along with its DNS resolver. /// A cached HTTP client along with its DNS resolver.
/// The resolver is needed to set the event sender per-request. /// The resolver is needed to set the event sender per-request.
pub struct CachedClient { pub struct CachedClient {
pub client: Client, pub client: ConfiguredClient,
pub resolver: Arc<LocalhostResolver>, pub resolver: Arc<LocalhostResolver>,
} }
+7 -7
View File
@@ -5,7 +5,7 @@ use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use futures_util::StreamExt; use futures_util::StreamExt;
use http_body::{Body as HttpBody, Frame, SizeHint}; use http_body::{Body as HttpBody, Frame, SizeHint};
use reqwest::{Client, Method, Version}; use reqwest::{Method, Version};
use std::fmt::Display; use std::fmt::Display;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
@@ -411,18 +411,18 @@ pub trait HttpSender: Send + Sync {
/// Reqwest-based implementation of HttpSender /// Reqwest-based implementation of HttpSender
pub struct ReqwestSender { pub struct ReqwestSender {
client: Client, client: crate::client::ConfiguredClient,
} }
impl ReqwestSender { impl ReqwestSender {
/// Create a new ReqwestSender with a default client /// Create a new ReqwestSender with a default client
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let client = Client::builder().build().map_err(Error::Client)?; let client = crate::client::ConfiguredClient::build_default()?;
Ok(Self { client }) Ok(Self { client })
} }
/// Create a new ReqwestSender with a custom client /// Create a new ReqwestSender with a configured client
pub fn with_client(client: Client) -> Self { pub fn with_client(client: crate::client::ConfiguredClient) -> Self {
Self { client } Self { client }
} }
} }
@@ -444,7 +444,7 @@ impl HttpSender for ReqwestSender {
.map_err(|e| Error::RequestError(format!("Invalid HTTP method: {}", e)))?; .map_err(|e| Error::RequestError(format!("Invalid HTTP method: {}", e)))?;
// Build the request // 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 // Add headers
for header in request.headers { for header in request.headers {
@@ -513,7 +513,7 @@ impl HttpSender for ReqwestSender {
send_event(HttpResponseEvent::Info("Sending request to server".to_string())); send_event(HttpResponseEvent::Info("Sending request to server".to_string()));
// Map some errors to our own, so they look nicer // 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) { if reqwest::Error::is_timeout(&e) {
Error::RequestTimeout( Error::RequestTimeout(
request.options.timeout.unwrap_or(Duration::from_secs(0)).clone(), request.options.timeout.unwrap_or(Duration::from_secs(0)).clone(),