1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-27 17:52:56 +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] * `Range` typed header. [#2485]
* `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] * `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] * `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 ### Changed
* Rename `Accept::{mime_precedence => ranked}`. [#2480] * Rename `Accept::{mime_precedence => ranked}`. [#2480]
@ -27,6 +28,7 @@
[#2482]: https://github.com/actix/actix-web/pull/2482 [#2482]: https://github.com/actix/actix-web/pull/2482
[#2484]: https://github.com/actix/actix-web/pull/2484 [#2484]: https://github.com/actix/actix-web/pull/2484
[#2485]: https://github.com/actix/actix-web/pull/2485 [#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 [#2492]: https://github.com/actix/actix-web/pull/2492
[#2493]: https://github.com/actix/actix-web/pull/2493 [#2493]: https://github.com/actix/actix-web/pull/2493

View File

@ -14,6 +14,8 @@
* `header::QualityItem::{max, min}`. [#2486] * `header::QualityItem::{max, min}`. [#2486]
* `header::Quality::{MAX, MIN}`. [#2486] * `header::Quality::{MAX, MIN}`. [#2486]
* `impl Display` for `header::Quality`. [#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 ### Changed
* Rename `body::BoxBody::{from_body => new}`. [#2468] * Rename `body::BoxBody::{from_body => new}`. [#2468]
@ -39,6 +41,7 @@
[#1920]: https://github.com/actix/actix-web/pull/1920 [#1920]: https://github.com/actix/actix-web/pull/1920
[#2486]: https://github.com/actix/actix-web/pull/2486 [#2486]: https://github.com/actix/actix-web/pull/2486
[#2488]: https://github.com/actix/actix-web/pull/2488 [#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 ## 3.0.0-beta.14 - 2021-11-30

View File

@ -1,6 +1,6 @@
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
fmt, mem, fmt,
}; };
use ahash::AHashMap; use ahash::AHashMap;
@ -10,8 +10,7 @@ use ahash::AHashMap;
/// All entries into this map must be owned types (or static references). /// All entries into this map must be owned types (or static references).
#[derive(Default)] #[derive(Default)]
pub struct Extensions { pub struct Extensions {
/// Use FxHasher with a std HashMap with for faster /// Use AHasher with a std HashMap with for faster lookups on the small `TypeId` keys.
/// lookups on the small `TypeId` (u64 equivalent) keys.
map: AHashMap<TypeId, Box<dyn Any>>, map: AHashMap<TypeId, Box<dyn Any>>,
} }
@ -123,11 +122,6 @@ impl Extensions {
pub fn extend(&mut self, other: Extensions) { pub fn extend(&mut self, other: Extensions) {
self.map.extend(other.map); 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 { impl fmt::Debug for Extensions {
@ -179,6 +173,8 @@ mod tests {
#[test] #[test]
fn test_integers() { fn test_integers() {
static A: u32 = 8;
let mut map = Extensions::new(); let mut map = Extensions::new();
map.insert::<i8>(8); map.insert::<i8>(8);
@ -191,6 +187,7 @@ mod tests {
map.insert::<u32>(32); map.insert::<u32>(32);
map.insert::<u64>(64); map.insert::<u64>(64);
map.insert::<u128>(128); map.insert::<u128>(128);
map.insert::<&'static u32>(&A);
assert!(map.get::<i8>().is_some()); assert!(map.get::<i8>().is_some());
assert!(map.get::<i16>().is_some()); assert!(map.get::<i16>().is_some());
assert!(map.get::<i32>().is_some()); assert!(map.get::<i32>().is_some());
@ -201,6 +198,7 @@ mod tests {
assert!(map.get::<u32>().is_some()); assert!(map.get::<u32>().is_some());
assert!(map.get::<u64>().is_some()); assert!(map.get::<u64>().is_some());
assert!(map.get::<u128>().is_some()); assert!(map.get::<u128>().is_some());
assert!(map.get::<&'static u32>().is_some());
} }
#[test] #[test]
@ -279,27 +277,4 @@ mod tests {
assert_eq!(extensions.get(), Some(&20u8)); assert_eq!(extensions.get(), Some(&20u8));
assert_eq!(extensions.get_mut(), Some(&mut 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, config::ServiceConfig,
error::{DispatchError, ParseError, PayloadError}, error::{DispatchError, ParseError, PayloadError},
service::HttpFlow, service::HttpFlow,
OnConnectData, Request, Response, StatusCode, Extensions, OnConnectData, Request, Response, StatusCode,
}; };
use super::{ use super::{
@ -100,9 +100,9 @@ where
U::Error: fmt::Display, U::Error: fmt::Display,
{ {
flow: Rc<HttpFlow<S, X, U>>, flow: Rc<HttpFlow<S, X, U>>,
on_connect_data: OnConnectData,
flags: Flags, flags: Flags,
peer_addr: Option<net::SocketAddr>, peer_addr: Option<net::SocketAddr>,
conn_data: Option<Rc<Extensions>>,
error: Option<DispatchError>, error: Option<DispatchError>,
#[pin] #[pin]
@ -179,10 +179,10 @@ where
/// Create HTTP/1 dispatcher. /// Create HTTP/1 dispatcher.
pub(crate) fn new( pub(crate) fn new(
io: T, io: T,
config: ServiceConfig,
flow: Rc<HttpFlow<S, X, U>>, flow: Rc<HttpFlow<S, X, U>>,
on_connect_data: OnConnectData, config: ServiceConfig,
peer_addr: Option<net::SocketAddr>, peer_addr: Option<net::SocketAddr>,
conn_data: OnConnectData,
) -> Self { ) -> Self {
let flags = if config.keep_alive_enabled() { let flags = if config.keep_alive_enabled() {
Flags::KEEPALIVE Flags::KEEPALIVE
@ -198,20 +198,23 @@ where
Dispatcher { Dispatcher {
inner: DispatcherState::Normal(InnerDispatcher { 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, flow,
on_connect_data,
flags, flags,
peer_addr, peer_addr,
conn_data: conn_data.0.map(Rc::new),
error: None,
state: State::None,
payload: None,
messages: VecDeque::new(),
ka_expire, ka_expire,
ka_timer, 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)] #[cfg(test)]
@ -593,8 +596,7 @@ where
Message::Item(mut req) => { Message::Item(mut req) => {
req.head_mut().peer_addr = *this.peer_addr; req.head_mut().peer_addr = *this.peer_addr;
// merge on_connect_ext data into request extensions req.conn_data = this.conn_data.as_ref().map(Rc::clone);
this.on_connect_data.merge_into(&mut req);
match this.codec.message_type() { match this.codec.message_type() {
// Request is upgradable. add upgrade message and break. // Request is upgradable. add upgrade message and break.
@ -1100,10 +1102,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf, buf,
ServiceConfig::default(),
services, services,
OnConnectData::default(), ServiceConfig::default(),
None, None,
OnConnectData::default(),
); );
actix_rt::pin!(h1); actix_rt::pin!(h1);
@ -1140,10 +1142,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf, buf,
cfg,
services, services,
OnConnectData::default(), cfg,
None, None,
OnConnectData::default(),
); );
actix_rt::pin!(h1); actix_rt::pin!(h1);
@ -1194,10 +1196,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf, buf,
cfg,
services, services,
OnConnectData::default(), cfg,
None, None,
OnConnectData::default(),
); );
actix_rt::pin!(h1); actix_rt::pin!(h1);
@ -1244,10 +1246,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(), buf.clone(),
cfg,
services, services,
OnConnectData::default(), cfg,
None, None,
OnConnectData::default(),
); );
buf.extend_read_buf( buf.extend_read_buf(
@ -1316,10 +1318,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(), buf.clone(),
cfg,
services, services,
OnConnectData::default(), cfg,
None, None,
OnConnectData::default(),
); );
buf.extend_read_buf( buf.extend_read_buf(
@ -1393,10 +1395,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new( let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new(
buf.clone(), buf.clone(),
cfg,
services, services,
OnConnectData::default(), cfg,
None, None,
OnConnectData::default(),
); );
buf.extend_read_buf( buf.extend_read_buf(

View File

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

View File

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

View File

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

View File

@ -92,19 +92,11 @@ impl OnConnectData {
on_connect_ext: Option<&ConnectCallback<T>>, on_connect_ext: Option<&ConnectCallback<T>>,
) -> Self { ) -> Self {
let ext = on_connect_ext.map(|handler| { let ext = on_connect_ext.map(|handler| {
let mut extensions = Extensions::new(); let mut extensions = Extensions::default();
handler(io, &mut extensions); handler(io, &mut extensions);
extensions extensions
}); });
Self(ext) 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::{ use std::{
cell::{Ref, RefMut}, cell::{Ref, RefMut},
fmt, net, str, fmt, net,
rc::Rc,
str,
}; };
use http::{header, Method, Uri, Version}; use http::{header, Method, Uri, Version};
@ -19,6 +21,7 @@ use crate::{
pub struct Request<P = PayloadStream> { pub struct Request<P = PayloadStream> {
pub(crate) payload: Payload<P>, pub(crate) payload: Payload<P>,
pub(crate) head: Message<RequestHead>, pub(crate) head: Message<RequestHead>,
pub(crate) conn_data: Option<Rc<Extensions>>,
} }
impl<P> HttpMessage for Request<P> { impl<P> HttpMessage for Request<P> {
@ -51,6 +54,7 @@ impl From<Message<RequestHead>> for Request<PayloadStream> {
Request { Request {
head, head,
payload: Payload::None, payload: Payload::None,
conn_data: None,
} }
} }
} }
@ -61,6 +65,7 @@ impl Request<PayloadStream> {
Request { Request {
head: Message::new(), head: Message::new(),
payload: Payload::None, payload: Payload::None,
conn_data: None,
} }
} }
} }
@ -71,16 +76,19 @@ impl<P> Request<P> {
Request { Request {
payload, payload,
head: Message::new(), head: Message::new(),
conn_data: None,
} }
} }
/// Create new Request instance /// Create new Request instance
pub fn replace_payload<P1>(self, payload: Payload<P1>) -> (Request<P1>, Payload<P>) { pub fn replace_payload<P1>(self, payload: Payload<P1>) -> (Request<P1>, Payload<P>) {
let pl = self.payload; let pl = self.payload;
( (
Request { Request {
payload, payload,
head: self.head, head: self.head,
conn_data: self.conn_data,
}, },
pl, pl,
) )
@ -170,6 +178,26 @@ impl<P> Request<P> {
pub fn peer_addr(&self) -> Option<net::SocketAddr> { pub fn peer_addr(&self) -> Option<net::SocketAddr> {
self.head().peer_addr 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> { impl<P> fmt::Debug for Request<P> {

View File

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

View File

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

View File

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

View File

@ -6,7 +6,10 @@
use std::{any::Any, io, net::SocketAddr}; 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)] #[allow(dead_code)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -16,11 +19,16 @@ struct ConnectionInfo {
ttl: Option<u32>, ttl: Option<u32>,
} }
async fn route_whoami(conn_info: web::ReqData<ConnectionInfo>) -> String { async fn route_whoami(req: HttpRequest) -> impl Responder {
format!( match req.conn_data::<ConnectionInfo>() {
Some(info) => HttpResponse::Ok().body(format!(
"Here is some info about your connection:\n\n{:#?}", "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) { 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<()> { async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 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))) HttpServer::new(|| App::new().default_service(web::to(route_whoami)))
.on_connect(get_conn_info) .on_connect(get_conn_info)
.bind(("127.0.0.1", 8080))? .bind(bind)?
.workers(1) .workers(1)
.run() .run()
.await .await

View File

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

View File

@ -37,6 +37,7 @@ pub(crate) struct HttpRequestInner {
pub(crate) head: Message<RequestHead>, pub(crate) head: Message<RequestHead>,
pub(crate) path: Path<Url>, pub(crate) path: Path<Url>,
pub(crate) app_data: SmallVec<[Rc<Extensions>; 4]>, pub(crate) app_data: SmallVec<[Rc<Extensions>; 4]>,
pub(crate) conn_data: Option<Rc<Extensions>>,
app_state: Rc<AppInitServiceState>, app_state: Rc<AppInitServiceState>,
} }
@ -47,6 +48,7 @@ impl HttpRequest {
head: Message<RequestHead>, head: Message<RequestHead>,
app_state: Rc<AppInitServiceState>, app_state: Rc<AppInitServiceState>,
app_data: Rc<Extensions>, app_data: Rc<Extensions>,
conn_data: Option<Rc<Extensions>>,
) -> HttpRequest { ) -> HttpRequest {
let mut data = SmallVec::<[Rc<Extensions>; 4]>::new(); let mut data = SmallVec::<[Rc<Extensions>; 4]>::new();
data.push(app_data); data.push(app_data);
@ -57,6 +59,7 @@ impl HttpRequest {
path, path,
app_state, app_state,
app_data: data, app_data: data,
conn_data,
}), }),
} }
} }
@ -165,6 +168,20 @@ impl HttpRequest {
self.head().extensions_mut() 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. /// Generates URL for a named resource.
/// ///
/// This substitutes in sequence all URL parameters that appear in the resource itself and in /// 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. /// 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 /// 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::openssl::TlsStream<actix_web::rt::net::TcpStream>` when using openssl.
/// - `actix_tls::accept::rustls::TlsStream<actix_web::rt::net::TcpStream>` when using rustls. /// - `actix_tls::accept::rustls::TlsStream<actix_web::rt::net::TcpStream>` when using rustls.
/// - `actix_web::rt::net::TcpStream` when no encryption is used. /// - `actix_web::rt::net::TcpStream` when no encryption is used.

View File

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