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
- 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]
- Wake HTTP/1 payload receivers with an incomplete-payload error when the sender is dropped before EOF. [#3100]
- Update `foldhash` dependency to `0.2`.

View File

@ -648,7 +648,7 @@ mod tests {
);
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(
&mut bytes,

View File

@ -150,14 +150,32 @@ impl RequestHead {
#[derive(Debug)]
pub enum RequestHeadType {
Owned(RequestHead),
Rc(Rc<RequestHead>, Option<HeaderMap>),
Rc(Rc<RequestHead>, HeaderMap),
}
impl RequestHeadType {
pub fn extra_headers(&self) -> Option<&HeaderMap> {
match self {
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
- 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]
- Update `hickory-resolver` dependency to `0.26.1`.
- Update `rand` dependency to `0.10`.

View File

@ -9,7 +9,7 @@ use actix_http::{
body::{BodySize, MessageBody},
error::PayloadError,
h1,
header::{HeaderMap, TryIntoHeaderValue, EXPECT, HOST},
header::{TryIntoHeaderValue, EXPECT, HOST},
Payload, RequestHeadType, ResponseHead, StatusCode,
};
use actix_utils::future::poll_fn;
@ -76,15 +76,9 @@ where
};
match wrt.get_mut().split().freeze().try_into_value() {
Ok(value) => match head {
RequestHeadType::Owned(ref mut head) => {
head.headers.insert(HOST, value);
Ok(value) => {
head.extra_headers_mut().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}"),
}
}

View File

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

View File

@ -41,6 +41,19 @@ pub enum ConnectRequest {
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.
pub enum ConnectResponse {
/// Standard HTTP response.

View File

@ -49,7 +49,7 @@ impl FrozenClientRequest {
where
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.response_decompress,
self.timeout,
@ -60,7 +60,7 @@ impl FrozenClientRequest {
/// Send a json body.
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.response_decompress,
self.timeout,
@ -71,7 +71,7 @@ impl FrozenClientRequest {
/// Send an urlencoded body.
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.response_decompress,
self.timeout,
@ -86,7 +86,7 @@ impl FrozenClientRequest {
S: Stream<Item = Result<Bytes, E>> + '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.response_decompress,
self.timeout,
@ -97,7 +97,7 @@ impl FrozenClientRequest {
/// Send an empty body.
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.response_decompress,
self.timeout,
@ -151,7 +151,7 @@ impl FrozenSendBuilder {
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.response_decompress,
self.req.timeout,
@ -166,7 +166,7 @@ impl FrozenSendBuilder {
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.response_decompress,
self.req.timeout,
@ -181,7 +181,7 @@ impl FrozenSendBuilder {
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.response_decompress,
self.req.timeout,
@ -200,7 +200,7 @@ impl FrozenSendBuilder {
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.response_decompress,
self.req.timeout,
@ -215,7 +215,7 @@ impl FrozenSendBuilder {
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.response_decompress,
self.req.timeout,

View File

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