refactor: remove Option from RequestHeadType::Rc extra headers

Replace `RequestHeadType::Rc(Rc<RequestHead>, Option<HeaderMap>)` with
`RequestHeadType::Rc(Rc<RequestHead>, HeaderMap)`, using an empty
HeaderMap as the default instead of None.

This enables:
- `RequestHeadType::extra_headers_mut()` for modifying headers on both
  Owned and Rc variants
- `ConnectRequest::headers_mut()` for middleware to insert headers
  without destructuring the request

Closes #2477
This commit is contained in:
Dhinesh K 2026-06-18 21:29:53 +05:30
parent 7ded7ee478
commit 0160b00a90
9 changed files with 56 additions and 35 deletions

View File

@ -2,6 +2,7 @@
## Unreleased ## Unreleased
- Remove `Option` wrapper from `RequestHeadType::Rc` extra headers; add `RequestHeadType::extra_headers_mut()` method.
- When configured, gracefully close HTTP/1 connections after early responses to unread request bodies. [#3967] - When configured, gracefully close HTTP/1 connections after early responses to unread request bodies. [#3967]
- Wake HTTP/1 payload receivers with an incomplete-payload error when the sender is dropped before EOF. [#3100] - Wake HTTP/1 payload receivers with an incomplete-payload error when the sender is dropped before EOF. [#3100]
- Update `foldhash` dependency to `0.2`. - Update `foldhash` dependency to `0.2`.

View File

@ -648,7 +648,7 @@ mod tests {
); );
extra_headers.insert(DATE, HeaderValue::from_static("date")); extra_headers.insert(DATE, HeaderValue::from_static("date"));
let mut head = RequestHeadType::Rc(Rc::new(head), Some(extra_headers)); let mut head = RequestHeadType::Rc(Rc::new(head), extra_headers);
let _ = head.encode_headers( let _ = head.encode_headers(
&mut bytes, &mut bytes,

View File

@ -150,14 +150,32 @@ impl RequestHead {
#[derive(Debug)] #[derive(Debug)]
pub enum RequestHeadType { pub enum RequestHeadType {
Owned(RequestHead), Owned(RequestHead),
Rc(Rc<RequestHead>, Option<HeaderMap>), Rc(Rc<RequestHead>, HeaderMap),
} }
impl RequestHeadType { impl RequestHeadType {
pub fn extra_headers(&self) -> Option<&HeaderMap> { pub fn extra_headers(&self) -> Option<&HeaderMap> {
match self { match self {
RequestHeadType::Owned(_) => None, RequestHeadType::Owned(_) => None,
RequestHeadType::Rc(_, headers) => headers.as_ref(), RequestHeadType::Rc(_, headers) => {
if headers.is_empty() {
None
} else {
Some(headers)
}
}
}
}
/// Returns a mutable reference to the headers that can be modified on this request.
///
/// For `Owned` variants, this returns the main header map.
/// For `Rc` variants, this returns the extra headers map (which overrides base headers
/// during encoding).
pub fn extra_headers_mut(&mut self) -> &mut HeaderMap {
match self {
RequestHeadType::Owned(head) => &mut head.headers,
RequestHeadType::Rc(_, headers) => headers,
} }
} }
} }

View File

@ -2,6 +2,7 @@
## Unreleased ## Unreleased
- Add `ConnectRequest::headers_mut()` to allow middleware to modify request headers.
- Add camel-case header controls to `WebsocketsRequest` via `camel_case_headers()` and `set_camel_case_headers()`. [#3953] - Add camel-case header controls to `WebsocketsRequest` via `camel_case_headers()` and `set_camel_case_headers()`. [#3953]
- Update `hickory-resolver` dependency to `0.26.1`. - Update `hickory-resolver` dependency to `0.26.1`.
- Update `rand` dependency to `0.10`. - Update `rand` dependency to `0.10`.

View File

@ -9,7 +9,7 @@ use actix_http::{
body::{BodySize, MessageBody}, body::{BodySize, MessageBody},
error::PayloadError, error::PayloadError,
h1, h1,
header::{HeaderMap, TryIntoHeaderValue, EXPECT, HOST}, header::{TryIntoHeaderValue, EXPECT, HOST},
Payload, RequestHeadType, ResponseHead, StatusCode, Payload, RequestHeadType, ResponseHead, StatusCode,
}; };
use actix_utils::future::poll_fn; use actix_utils::future::poll_fn;
@ -76,15 +76,9 @@ where
}; };
match wrt.get_mut().split().freeze().try_into_value() { match wrt.get_mut().split().freeze().try_into_value() {
Ok(value) => match head { Ok(value) => {
RequestHeadType::Owned(ref mut head) => { head.extra_headers_mut().insert(HOST, value);
head.headers.insert(HOST, value);
} }
RequestHeadType::Rc(_, ref mut extra_headers) => {
let headers = extra_headers.get_or_insert(HeaderMap::new());
headers.insert(HOST, value);
}
},
Err(err) => log::error!("Can not set HOST header {err}"), Err(err) => log::error!("Can not set HOST header {err}"),
} }
} }

View File

@ -77,10 +77,9 @@ where
// Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate. // Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate.
let (head, extra_headers) = match head { let (head, extra_headers) = match head {
RequestHeadType::Owned(head) => (RequestHeadType::Owned(head), HeaderMap::new()), RequestHeadType::Owned(head) => (RequestHeadType::Owned(head), HeaderMap::new()),
RequestHeadType::Rc(head, extra_headers) => ( RequestHeadType::Rc(head, extra_headers) => {
RequestHeadType::Rc(head, None), (RequestHeadType::Rc(head, HeaderMap::new()), extra_headers)
extra_headers.unwrap_or_else(HeaderMap::new), }
),
}; };
// merging headers from head and extra headers. // merging headers from head and extra headers.

View File

@ -41,6 +41,19 @@ pub enum ConnectRequest {
Tunnel(RequestHead, Option<net::SocketAddr>), Tunnel(RequestHead, Option<net::SocketAddr>),
} }
impl ConnectRequest {
/// Returns a mutable reference to the request headers.
///
/// For `Client` requests using the `Rc` variant, this returns the extra headers map
/// (which takes priority over base headers during encoding).
pub fn headers_mut(&mut self) -> &mut actix_http::header::HeaderMap {
match self {
ConnectRequest::Client(head, ..) => head.extra_headers_mut(),
ConnectRequest::Tunnel(head, ..) => &mut head.headers,
}
}
}
/// Combined HTTP response & WebSocket tunnel type returned from connection service. /// Combined HTTP response & WebSocket tunnel type returned from connection service.
pub enum ConnectResponse { pub enum ConnectResponse {
/// Standard HTTP response. /// Standard HTTP response.

View File

@ -49,7 +49,7 @@ impl FrozenClientRequest {
where where
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
RequestSender::Rc(Rc::clone(&self.head), None).send_body( RequestSender::Rc(Rc::clone(&self.head), HeaderMap::new()).send_body(
self.addr, self.addr,
self.response_decompress, self.response_decompress,
self.timeout, self.timeout,
@ -60,7 +60,7 @@ impl FrozenClientRequest {
/// Send a json body. /// Send a json body.
pub fn send_json<T: Serialize>(&self, value: &T) -> SendClientRequest { pub fn send_json<T: Serialize>(&self, value: &T) -> SendClientRequest {
RequestSender::Rc(Rc::clone(&self.head), None).send_json( RequestSender::Rc(Rc::clone(&self.head), HeaderMap::new()).send_json(
self.addr, self.addr,
self.response_decompress, self.response_decompress,
self.timeout, self.timeout,
@ -71,7 +71,7 @@ impl FrozenClientRequest {
/// Send an urlencoded body. /// Send an urlencoded body.
pub fn send_form<T: Serialize>(&self, value: &T) -> SendClientRequest { pub fn send_form<T: Serialize>(&self, value: &T) -> SendClientRequest {
RequestSender::Rc(Rc::clone(&self.head), None).send_form( RequestSender::Rc(Rc::clone(&self.head), HeaderMap::new()).send_form(
self.addr, self.addr,
self.response_decompress, self.response_decompress,
self.timeout, self.timeout,
@ -86,7 +86,7 @@ impl FrozenClientRequest {
S: Stream<Item = Result<Bytes, E>> + 'static, S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<BoxError> + 'static, E: Into<BoxError> + 'static,
{ {
RequestSender::Rc(Rc::clone(&self.head), None).send_stream( RequestSender::Rc(Rc::clone(&self.head), HeaderMap::new()).send_stream(
self.addr, self.addr,
self.response_decompress, self.response_decompress,
self.timeout, self.timeout,
@ -97,7 +97,7 @@ impl FrozenClientRequest {
/// Send an empty body. /// Send an empty body.
pub fn send(&self) -> SendClientRequest { pub fn send(&self) -> SendClientRequest {
RequestSender::Rc(Rc::clone(&self.head), None).send( RequestSender::Rc(Rc::clone(&self.head), HeaderMap::new()).send(
self.addr, self.addr,
self.response_decompress, self.response_decompress,
self.timeout, self.timeout,
@ -151,7 +151,7 @@ impl FrozenSendBuilder {
return err.into(); return err.into();
} }
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_body( RequestSender::Rc(self.req.head, self.extra_headers).send_body(
self.req.addr, self.req.addr,
self.req.response_decompress, self.req.response_decompress,
self.req.timeout, self.req.timeout,
@ -166,7 +166,7 @@ impl FrozenSendBuilder {
return err.into(); return err.into();
} }
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_json( RequestSender::Rc(self.req.head, self.extra_headers).send_json(
self.req.addr, self.req.addr,
self.req.response_decompress, self.req.response_decompress,
self.req.timeout, self.req.timeout,
@ -181,7 +181,7 @@ impl FrozenSendBuilder {
return err.into(); return err.into();
} }
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_form( RequestSender::Rc(self.req.head, self.extra_headers).send_form(
self.req.addr, self.req.addr,
self.req.response_decompress, self.req.response_decompress,
self.req.timeout, self.req.timeout,
@ -200,7 +200,7 @@ impl FrozenSendBuilder {
return err.into(); return err.into();
} }
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_stream( RequestSender::Rc(self.req.head, self.extra_headers).send_stream(
self.req.addr, self.req.addr,
self.req.response_decompress, self.req.response_decompress,
self.req.timeout, self.req.timeout,
@ -215,7 +215,7 @@ impl FrozenSendBuilder {
return err.into(); return err.into();
} }
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send( RequestSender::Rc(self.req.head, self.extra_headers).send(
self.req.addr, self.req.addr,
self.req.response_decompress, self.req.response_decompress,
self.req.timeout, self.req.timeout,

View File

@ -176,7 +176,7 @@ impl From<PrepForSendingError> for SendClientRequest {
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum RequestSender { pub(crate) enum RequestSender {
Owned(RequestHead), Owned(RequestHead),
Rc(Rc<RequestHead>, Option<HeaderMap>), Rc(Rc<RequestHead>, HeaderMap),
} }
impl RequestSender { impl RequestSender {
@ -296,14 +296,9 @@ impl RequestSender {
} }
} }
RequestSender::Rc(head, extra_headers) => { RequestSender::Rc(head, extra_headers) => {
if !head.headers.contains_key(&key) if !head.headers.contains_key(&key) && !extra_headers.contains_key(&key) {
&& !extra_headers.iter().any(|h| h.contains_key(&key))
{
match value.try_into_value() { match value.try_into_value() {
Ok(v) => { Ok(v) => extra_headers.insert(key, v),
let h = extra_headers.get_or_insert(HeaderMap::new());
h.insert(key, v)
}
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; };
} }