1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-23 16:21:06 +01:00

add connection level data container (#2491)

This commit is contained in:
Rob Ede 2021-12-07 17:23:34 +00:00 committed by GitHub
parent 069cf2da07
commit d35b7644dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 152 additions and 125 deletions

View File

@ -7,6 +7,7 @@
* `Range` typed header. [#2485]
* `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468]
* `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468]
* Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491]
### Changed
* Rename `Accept::{mime_precedence => ranked}`. [#2480]
@ -27,6 +28,7 @@
[#2482]: https://github.com/actix/actix-web/pull/2482
[#2484]: https://github.com/actix/actix-web/pull/2484
[#2485]: https://github.com/actix/actix-web/pull/2485
[#2491]: https://github.com/actix/actix-web/pull/2491
[#2492]: https://github.com/actix/actix-web/pull/2492
[#2493]: https://github.com/actix/actix-web/pull/2493

View File

@ -14,6 +14,8 @@
* `header::QualityItem::{max, min}`. [#2486]
* `header::Quality::{MAX, MIN}`. [#2486]
* `impl Display` for `header::Quality`. [#2486]
* Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491]
* `Request::take_conn_data()`. [#2491]
### Changed
* Rename `body::BoxBody::{from_body => new}`. [#2468]
@ -39,6 +41,7 @@
[#1920]: https://github.com/actix/actix-web/pull/1920
[#2486]: https://github.com/actix/actix-web/pull/2486
[#2488]: https://github.com/actix/actix-web/pull/2488
[#2491]: https://github.com/actix/actix-web/pull/2491
## 3.0.0-beta.14 - 2021-11-30

View File

@ -1,6 +1,6 @@
use std::{
any::{Any, TypeId},
fmt, mem,
fmt,
};
use ahash::AHashMap;
@ -10,8 +10,7 @@ use ahash::AHashMap;
/// All entries into this map must be owned types (or static references).
#[derive(Default)]
pub struct Extensions {
/// Use FxHasher with a std HashMap with for faster
/// lookups on the small `TypeId` (u64 equivalent) keys.
/// Use AHasher with a std HashMap with for faster lookups on the small `TypeId` keys.
map: AHashMap<TypeId, Box<dyn Any>>,
}
@ -123,11 +122,6 @@ impl Extensions {
pub fn extend(&mut self, other: Extensions) {
self.map.extend(other.map);
}
/// Sets (or overrides) items from `other` into this map.
pub(crate) fn drain_from(&mut self, other: &mut Self) {
self.map.extend(mem::take(&mut other.map));
}
}
impl fmt::Debug for Extensions {
@ -179,6 +173,8 @@ mod tests {
#[test]
fn test_integers() {
static A: u32 = 8;
let mut map = Extensions::new();
map.insert::<i8>(8);
@ -191,6 +187,7 @@ mod tests {
map.insert::<u32>(32);
map.insert::<u64>(64);
map.insert::<u128>(128);
map.insert::<&'static u32>(&A);
assert!(map.get::<i8>().is_some());
assert!(map.get::<i16>().is_some());
assert!(map.get::<i32>().is_some());
@ -201,6 +198,7 @@ mod tests {
assert!(map.get::<u32>().is_some());
assert!(map.get::<u64>().is_some());
assert!(map.get::<u128>().is_some());
assert!(map.get::<&'static u32>().is_some());
}
#[test]
@ -279,27 +277,4 @@ mod tests {
assert_eq!(extensions.get(), Some(&20u8));
assert_eq!(extensions.get_mut(), Some(&mut 20u8));
}
#[test]
fn test_drain_from() {
let mut ext = Extensions::new();
ext.insert(2isize);
let mut more_ext = Extensions::new();
more_ext.insert(5isize);
more_ext.insert(5usize);
assert_eq!(ext.get::<isize>(), Some(&2isize));
assert_eq!(ext.get::<usize>(), None);
assert_eq!(more_ext.get::<isize>(), Some(&5isize));
assert_eq!(more_ext.get::<usize>(), Some(&5usize));
ext.drain_from(&mut more_ext);
assert_eq!(ext.get::<isize>(), Some(&5isize));
assert_eq!(ext.get::<usize>(), Some(&5usize));
assert_eq!(more_ext.get::<isize>(), None);
assert_eq!(more_ext.get::<usize>(), None);
}
}

View File

@ -22,7 +22,7 @@ use crate::{
config::ServiceConfig,
error::{DispatchError, ParseError, PayloadError},
service::HttpFlow,
OnConnectData, Request, Response, StatusCode,
Extensions, OnConnectData, Request, Response, StatusCode,
};
use super::{
@ -100,9 +100,9 @@ where
U::Error: fmt::Display,
{
flow: Rc<HttpFlow<S, X, U>>,
on_connect_data: OnConnectData,
flags: Flags,
peer_addr: Option<net::SocketAddr>,
conn_data: Option<Rc<Extensions>>,
error: Option<DispatchError>,
#[pin]
@ -179,10 +179,10 @@ where
/// Create HTTP/1 dispatcher.
pub(crate) fn new(
io: T,
config: ServiceConfig,
flow: Rc<HttpFlow<S, X, U>>,
on_connect_data: OnConnectData,
config: ServiceConfig,
peer_addr: Option<net::SocketAddr>,
conn_data: OnConnectData,
) -> Self {
let flags = if config.keep_alive_enabled() {
Flags::KEEPALIVE
@ -198,20 +198,23 @@ where
Dispatcher {
inner: DispatcherState::Normal(InnerDispatcher {
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
payload: None,
state: State::None,
error: None,
messages: VecDeque::new(),
io: Some(io),
codec: Codec::new(config),
flow,
on_connect_data,
flags,
peer_addr,
conn_data: conn_data.0.map(Rc::new),
error: None,
state: State::None,
payload: None,
messages: VecDeque::new(),
ka_expire,
ka_timer,
io: Some(io),
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
codec: Codec::new(config),
}),
#[cfg(test)]
@ -593,8 +596,7 @@ where
Message::Item(mut req) => {
req.head_mut().peer_addr = *this.peer_addr;
// merge on_connect_ext data into request extensions
this.on_connect_data.merge_into(&mut req);
req.conn_data = this.conn_data.as_ref().map(Rc::clone);
match this.codec.message_type() {
// Request is upgradable. add upgrade message and break.
@ -1100,10 +1102,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf,
ServiceConfig::default(),
services,
OnConnectData::default(),
ServiceConfig::default(),
None,
OnConnectData::default(),
);
actix_rt::pin!(h1);
@ -1140,10 +1142,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf,
cfg,
services,
OnConnectData::default(),
cfg,
None,
OnConnectData::default(),
);
actix_rt::pin!(h1);
@ -1194,10 +1196,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf,
cfg,
services,
OnConnectData::default(),
cfg,
None,
OnConnectData::default(),
);
actix_rt::pin!(h1);
@ -1244,10 +1246,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
cfg,
services,
OnConnectData::default(),
cfg,
None,
OnConnectData::default(),
);
buf.extend_read_buf(
@ -1316,10 +1318,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
cfg,
services,
OnConnectData::default(),
cfg,
None,
OnConnectData::default(),
);
buf.extend_read_buf(
@ -1393,10 +1395,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new(
buf.clone(),
cfg,
services,
OnConnectData::default(),
cfg,
None,
OnConnectData::default(),
);
buf.extend_read_buf(

View File

@ -365,15 +365,7 @@ where
}
fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
let on_connect_data =
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
Dispatcher::new(
io,
self.cfg.clone(),
self.flow.clone(),
on_connect_data,
addr,
)
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
Dispatcher::new(io, self.flow.clone(), self.cfg.clone(), addr, conn_data)
}
}

View File

@ -27,7 +27,7 @@ use crate::{
body::{BodySize, BoxBody, MessageBody},
config::ServiceConfig,
service::HttpFlow,
OnConnectData, Payload, Request, Response, ResponseHead,
Extensions, OnConnectData, Payload, Request, Response, ResponseHead,
};
const CHUNK_SIZE: usize = 16_384;
@ -37,7 +37,7 @@ pin_project! {
pub struct Dispatcher<T, S, B, X, U> {
flow: Rc<HttpFlow<S, X, U>>,
connection: Connection<T, Bytes>,
on_connect_data: OnConnectData,
conn_data: Option<Rc<Extensions>>,
config: ServiceConfig,
peer_addr: Option<net::SocketAddr>,
ping_pong: Option<H2PingPong>,
@ -50,11 +50,11 @@ where
T: AsyncRead + AsyncWrite + Unpin,
{
pub(crate) fn new(
flow: Rc<HttpFlow<S, X, U>>,
mut conn: Connection<T, Bytes>,
on_connect_data: OnConnectData,
flow: Rc<HttpFlow<S, X, U>>,
config: ServiceConfig,
peer_addr: Option<net::SocketAddr>,
conn_data: OnConnectData,
timer: Option<Pin<Box<Sleep>>>,
) -> Self {
let ping_pong = config.keep_alive().map(|dur| H2PingPong {
@ -74,7 +74,7 @@ where
config,
peer_addr,
connection: conn,
on_connect_data,
conn_data: conn_data.0.map(Rc::new),
ping_pong,
_phantom: PhantomData,
}
@ -119,8 +119,7 @@ where
head.headers = parts.headers.into();
head.peer_addr = this.peer_addr;
// merge on_connect_ext data into request extensions
this.on_connect_data.merge_into(&mut req);
req.conn_data = this.conn_data.as_ref().map(Rc::clone);
let fut = this.flow.service.call(req);
let config = this.config.clone();

View File

@ -1,7 +1,7 @@
use std::{
future::Future,
marker::PhantomData,
net,
mem, net,
pin::Pin,
rc::Rc,
task::{Context, Poll},
@ -339,21 +339,24 @@ where
ref mut srv,
ref mut config,
ref peer_addr,
ref mut on_connect_data,
ref mut conn_data,
ref mut handshake,
) => match ready!(Pin::new(handshake).poll(cx)) {
Ok((conn, timer)) => {
let on_connect_data = std::mem::take(on_connect_data);
let on_connect_data = mem::take(conn_data);
self.state = State::Incoming(Dispatcher::new(
srv.take().unwrap(),
conn,
on_connect_data,
srv.take().unwrap(),
config.take().unwrap(),
*peer_addr,
on_connect_data,
timer,
));
self.poll(cx)
}
Err(err) => {
trace!("H2 handshake error: {}", err);
Poll::Ready(Err(err))

View File

@ -92,19 +92,11 @@ impl OnConnectData {
on_connect_ext: Option<&ConnectCallback<T>>,
) -> Self {
let ext = on_connect_ext.map(|handler| {
let mut extensions = Extensions::new();
let mut extensions = Extensions::default();
handler(io, &mut extensions);
extensions
});
Self(ext)
}
/// Merge self into given request's extensions.
#[inline]
pub(crate) fn merge_into(&mut self, req: &mut Request) {
if let Some(ref mut ext) = self.0 {
req.head.extensions.get_mut().drain_from(ext);
}
}
}

View File

@ -2,7 +2,9 @@
use std::{
cell::{Ref, RefMut},
fmt, net, str,
fmt, net,
rc::Rc,
str,
};
use http::{header, Method, Uri, Version};
@ -19,6 +21,7 @@ use crate::{
pub struct Request<P = PayloadStream> {
pub(crate) payload: Payload<P>,
pub(crate) head: Message<RequestHead>,
pub(crate) conn_data: Option<Rc<Extensions>>,
}
impl<P> HttpMessage for Request<P> {
@ -51,6 +54,7 @@ impl From<Message<RequestHead>> for Request<PayloadStream> {
Request {
head,
payload: Payload::None,
conn_data: None,
}
}
}
@ -61,6 +65,7 @@ impl Request<PayloadStream> {
Request {
head: Message::new(),
payload: Payload::None,
conn_data: None,
}
}
}
@ -71,16 +76,19 @@ impl<P> Request<P> {
Request {
payload,
head: Message::new(),
conn_data: None,
}
}
/// Create new Request instance
pub fn replace_payload<P1>(self, payload: Payload<P1>) -> (Request<P1>, Payload<P>) {
let pl = self.payload;
(
Request {
payload,
head: self.head,
conn_data: self.conn_data,
},
pl,
)
@ -170,6 +178,26 @@ impl<P> Request<P> {
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
self.head().peer_addr
}
/// Returns a reference a piece of connection data set in an [on-connect] callback.
///
/// ```ignore
/// let opt_t = req.conn_data::<PeerCertificate>();
/// ```
///
/// [on-connect]: crate::HttpServiceBuilder::on_connect_ext
pub fn conn_data<T: 'static>(&self) -> Option<&T> {
self.conn_data
.as_deref()
.and_then(|container| container.get::<T>())
}
/// Returns the connection data container if an [on-connect] callback was registered.
///
/// [on-connect]: crate::HttpServiceBuilder::on_connect_ext
pub fn take_conn_data(&mut self) -> Option<Rc<Extensions>> {
self.conn_data.take()
}
}
impl<P> fmt::Debug for Request<P> {

View File

@ -507,8 +507,7 @@ where
&self,
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
) -> Self::Future {
let on_connect_data =
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
match proto {
Protocol::Http2 => HttpServiceHandlerResponse {
@ -517,7 +516,7 @@ where
h2::handshake_with_timeout(io, &self.cfg),
self.cfg.clone(),
self.flow.clone(),
on_connect_data,
conn_data,
peer_addr,
)),
},
@ -527,10 +526,10 @@ where
state: State::H1 {
dispatcher: h1::Dispatcher::new(
io,
self.cfg.clone(),
self.flow.clone(),
on_connect_data,
self.cfg.clone(),
peer_addr,
conn_data,
),
},
},
@ -627,17 +626,12 @@ where
StateProj::H2Handshake { handshake: data } => {
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
Ok((conn, timer)) => {
let (_, config, flow, on_connect_data, peer_addr) =
let (_, config, flow, conn_data, peer_addr) =
data.take().unwrap();
self.as_mut().project().state.set(State::H2 {
dispatcher: h2::Dispatcher::new(
flow,
conn,
on_connect_data,
config,
peer_addr,
timer,
conn, flow, config, peer_addr, conn_data, timer,
),
});
self.poll(cx)

View File

@ -8,7 +8,7 @@ use actix_http::{
body::{BodyStream, BoxBody, SizedStream},
error::PayloadError,
header::{self, HeaderValue},
Error, HttpMessage, HttpService, Method, Request, Response, StatusCode, Version,
Error, HttpService, Method, Request, Response, StatusCode, Version,
};
use actix_http_test::test_server;
use actix_service::{fn_service, ServiceFactoryExt};
@ -430,7 +430,7 @@ async fn test_h2_on_connect() {
data.insert(20isize);
})
.h2(|req: Request| {
assert!(req.extensions().contains::<isize>());
assert!(req.conn_data::<isize>().is_some());
ok::<_, Infallible>(Response::ok())
})
.openssl(tls_config())

View File

@ -7,7 +7,7 @@ use std::{
use actix_http::{
body::{self, BodyStream, BoxBody, SizedStream},
header, Error, HttpMessage, HttpService, KeepAlive, Request, Response, StatusCode,
header, Error, HttpService, KeepAlive, Request, Response, StatusCode,
};
use actix_http_test::test_server;
use actix_rt::time::sleep;
@ -748,7 +748,7 @@ async fn test_h1_on_connect() {
data.insert(20isize);
})
.h1(|req: Request| {
assert!(req.extensions().contains::<isize>());
assert!(req.conn_data::<isize>().is_some());
ok::<_, Infallible>(Response::ok())
})
.tcp()

View File

@ -6,7 +6,10 @@
use std::{any::Any, io, net::SocketAddr};
use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer};
use actix_web::{
dev::Extensions, rt::net::TcpStream, web, App, HttpRequest, HttpResponse, HttpServer,
Responder,
};
#[allow(dead_code)]
#[derive(Debug, Clone)]
@ -16,11 +19,16 @@ struct ConnectionInfo {
ttl: Option<u32>,
}
async fn route_whoami(conn_info: web::ReqData<ConnectionInfo>) -> String {
format!(
async fn route_whoami(req: HttpRequest) -> impl Responder {
match req.conn_data::<ConnectionInfo>() {
Some(info) => HttpResponse::Ok().body(format!(
"Here is some info about your connection:\n\n{:#?}",
conn_info
)
info
)),
None => {
HttpResponse::InternalServerError().body("Missing expected request extension data")
}
}
}
fn get_conn_info(connection: &dyn Any, data: &mut Extensions) {
@ -39,9 +47,12 @@ fn get_conn_info(connection: &dyn Any, data: &mut Extensions) {
async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
let bind = ("127.0.0.1", 8080);
log::info!("staring server at http://{}:{}", &bind.0, &bind.1);
HttpServer::new(|| App::new().default_service(web::to(route_whoami)))
.on_connect(get_conn_info)
.bind(("127.0.0.1", 8080))?
.bind(bind)?
.workers(1)
.run()
.await

View File

@ -197,7 +197,8 @@ where
actix_service::forward_ready!(service);
fn call(&self, req: Request) -> Self::Future {
fn call(&self, mut req: Request) -> Self::Future {
let conn_data = req.take_conn_data();
let (head, payload) = req.into_parts();
let req = if let Some(mut req) = self.app_state.pool().pop() {
@ -205,6 +206,7 @@ where
inner.path.get_mut().update(&head.uri);
inner.path.reset();
inner.head = head;
inner.conn_data = conn_data;
req
} else {
HttpRequest::new(
@ -212,6 +214,7 @@ where
head,
self.app_state.clone(),
self.app_data.clone(),
conn_data,
)
};
self.service.call(ServiceRequest::new(req, payload))

View File

@ -37,6 +37,7 @@ pub(crate) struct HttpRequestInner {
pub(crate) head: Message<RequestHead>,
pub(crate) path: Path<Url>,
pub(crate) app_data: SmallVec<[Rc<Extensions>; 4]>,
pub(crate) conn_data: Option<Rc<Extensions>>,
app_state: Rc<AppInitServiceState>,
}
@ -47,6 +48,7 @@ impl HttpRequest {
head: Message<RequestHead>,
app_state: Rc<AppInitServiceState>,
app_data: Rc<Extensions>,
conn_data: Option<Rc<Extensions>>,
) -> HttpRequest {
let mut data = SmallVec::<[Rc<Extensions>; 4]>::new();
data.push(app_data);
@ -57,6 +59,7 @@ impl HttpRequest {
path,
app_state,
app_data: data,
conn_data,
}),
}
}
@ -165,6 +168,20 @@ impl HttpRequest {
self.head().extensions_mut()
}
/// Returns a reference a piece of connection data set in an [on-connect] callback.
///
/// ```ignore
/// let opt_t = req.conn_data::<PeerCertificate>();
/// ```
///
/// [on-connect]: crate::HttpServiceBuilder::on_connect_ext
pub fn conn_data<T: 'static>(&self) -> Option<&T> {
self.inner
.conn_data
.as_deref()
.and_then(|container| container.get::<T>())
}
/// Generates URL for a named resource.
///
/// This substitutes in sequence all URL parameters that appear in the resource itself and in

View File

@ -101,9 +101,9 @@ where
/// Sets function that will be called once before each connection is handled.
/// It will receive a `&std::any::Any`, which contains underlying connection type and an
/// [Extensions] container so that request-local data can be passed to middleware and handlers.
/// [Extensions] container so that connection data can be accessed in middleware and handlers.
///
/// For example:
/// # Connection Types
/// - `actix_tls::accept::openssl::TlsStream<actix_web::rt::net::TcpStream>` when using openssl.
/// - `actix_tls::accept::rustls::TlsStream<actix_web::rt::net::TcpStream>` when using rustls.
/// - `actix_web::rt::net::TcpStream` when no encryption is used.

View File

@ -172,12 +172,10 @@ impl ServiceRequest {
self.head().uri.path()
}
/// The query string in the URL.
///
/// E.g., id=10
/// Counterpart to [`HttpRequest::query_string`](super::HttpRequest::query_string()).
#[inline]
pub fn query_string(&self) -> &str {
self.uri().query().unwrap_or_default()
self.req.query_string()
}
/// Peer socket address.
@ -241,6 +239,7 @@ impl ServiceRequest {
}
/// Counterpart to [`HttpRequest::app_data`](super::HttpRequest::app_data()).
#[inline]
pub fn app_data<T: 'static>(&self) -> Option<&T> {
for container in self.req.inner.app_data.iter().rev() {
if let Some(data) = container.get::<T>() {
@ -251,6 +250,12 @@ impl ServiceRequest {
None
}
/// Counterpart to [`HttpRequest::conn_data`](super::HttpRequest::conn_data()).
#[inline]
pub fn conn_data<T: 'static>(&self) -> Option<&T> {
self.req.conn_data()
}
#[cfg(feature = "cookies")]
pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
self.req.cookies()
@ -263,6 +268,7 @@ impl ServiceRequest {
}
/// Set request payload.
#[inline]
pub fn set_payload(&mut self, payload: Payload) {
self.payload = payload;
}
@ -280,6 +286,7 @@ impl ServiceRequest {
}
impl Resource<Url> for ServiceRequest {
#[inline]
fn resource_path(&mut self) -> &mut Path<Url> {
self.match_info_mut()
}
@ -404,12 +411,11 @@ impl<B> ServiceResponse<B> {
}
/// Extract response body
#[inline]
pub fn into_body(self) -> B {
self.response.into_body()
}
}
impl<B> ServiceResponse<B> {
/// Set a new body
#[inline]
pub fn map_body<F, B2>(self, f: F) -> ServiceResponse<B2>

View File

@ -581,7 +581,7 @@ impl TestRequest {
let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone());
ServiceRequest::new(
HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data)),
HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data), None),
payload,
)
}
@ -599,7 +599,7 @@ impl TestRequest {
let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone());
HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data))
HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data), None)
}
/// Complete request creation and generate `HttpRequest` and `Payload` instances
@ -610,7 +610,7 @@ impl TestRequest {
let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone());
let req = HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data));
let req = HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data), None);
(req, payload)
}