1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-11-26 19:41:12 +00:00

re-work from request macro. (#2469)

This commit is contained in:
fakeshadow 2021-11-29 09:23:27 +08:00 committed by GitHub
parent 39243095b5
commit cf54388534
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 130 additions and 102 deletions

View file

@ -97,6 +97,7 @@ log = "0.4"
mime = "0.3" mime = "0.3"
paste = "1" paste = "1"
pin-project = "1.0.0" pin-project = "1.0.0"
pin-project-lite = "0.2.7"
regex = "1.4" regex = "1.4"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View file

@ -10,6 +10,7 @@ use std::{
use actix_http::http::{Method, Uri}; use actix_http::http::{Method, Uri};
use actix_utils::future::{ok, Ready}; use actix_utils::future::{ok, Ready};
use futures_core::ready; use futures_core::ready;
use pin_project_lite::pin_project;
use crate::{dev::Payload, Error, HttpRequest}; use crate::{dev::Payload, Error, HttpRequest};
@ -139,11 +140,12 @@ where
} }
} }
#[pin_project::pin_project] pin_project! {
pub struct FromRequestOptFuture<Fut> { pub struct FromRequestOptFuture<Fut> {
#[pin] #[pin]
fut: Fut, fut: Fut,
} }
}
impl<Fut, T, E> Future for FromRequestOptFuture<Fut> impl<Fut, T, E> Future for FromRequestOptFuture<Fut>
where where
@ -226,11 +228,12 @@ where
} }
} }
#[pin_project::pin_project] pin_project! {
pub struct FromRequestResFuture<Fut> { pub struct FromRequestResFuture<Fut> {
#[pin] #[pin]
fut: Fut, fut: Fut,
} }
}
impl<Fut, T, E> Future for FromRequestResFuture<Fut> impl<Fut, T, E> Future for FromRequestResFuture<Fut>
where where
@ -297,57 +300,41 @@ impl FromRequest for () {
} }
} }
macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { #[doc(hidden)]
// This module is a trick to get around the inability of
// `macro_rules!` macros to make new idents. We want to make
// a new `FutWrapper` struct for each distinct invocation of
// this macro. Ideally, we would name it something like
// `FutWrapper_$fut_type`, but this can't be done in a macro_rules
// macro.
//
// Instead, we put everything in a module named `$fut_type`, thus allowing
// us to use the name `FutWrapper` without worrying about conflicts.
// This macro only exists to generate trait impls for tuples - these
// are inherently global, so users don't have to care about this
// weird trick.
#[allow(non_snake_case)] #[allow(non_snake_case)]
mod $fut_type { mod tuple_from_req {
// Bring everything into scope, so we don't need
// redundant imports
use super::*; use super::*;
/// A helper struct to allow us to pin-project through macro_rules! tuple_from_req {
/// to individual fields ($fut: ident; $($T: ident),*) => {
#[pin_project::pin_project]
struct FutWrapper<$($T: FromRequest),+>($(#[pin] $T::Future),+);
/// FromRequest implementation for tuple /// FromRequest implementation for tuple
#[doc(hidden)]
#[allow(unused_parens)] #[allow(unused_parens)]
impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+) impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+)
{ {
type Error = Error; type Error = Error;
type Future = $fut_type<$($T),+>; type Future = $fut<$($T),+>;
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
$fut_type { $fut {
items: <($(Option<$T>,)+)>::default(), $(
futs: FutWrapper($($T::from_request(req, payload),)+), $T: ExtractFuture::Future {
fut: $T::from_request(req, payload)
},
)+
} }
} }
} }
#[doc(hidden)] pin_project! {
#[pin_project::pin_project] pub struct $fut<$($T: FromRequest),+> {
pub struct $fut_type<$($T: FromRequest),+> { $(
items: ($(Option<$T>,)+),
#[pin] #[pin]
futs: FutWrapper<$($T,)+>, $T: ExtractFuture<$T::Future, $T>,
)+
}
} }
impl<$($T: FromRequest),+> Future for $fut_type<$($T),+> impl<$($T: FromRequest),+> Future for $fut<$($T),+>
{ {
type Output = Result<($($T,)+), Error>; type Output = Result<($($T,)+), Error>;
@ -356,43 +343,61 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
let mut ready = true; let mut ready = true;
$( $(
if this.items.$n.is_none() { match this.$T.as_mut().project() {
match this.futs.as_mut().project().$n.poll(cx) { ExtractProj::Future { fut } => match fut.poll(cx) {
Poll::Ready(Ok(item)) => { Poll::Ready(Ok(output)) => {
this.items.$n = Some(item); let _ = this.$T.as_mut().project_replace(ExtractFuture::Done { output });
} },
Poll::Pending => ready = false,
Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())), Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())),
} Poll::Pending => ready = false,
},
ExtractProj::Done { .. } => {},
ExtractProj::Empty => unreachable!("FromRequest polled after finished"),
} }
)+ )+
if ready { if ready {
Poll::Ready(Ok( Poll::Ready(Ok(
($(this.items.$n.take().unwrap(),)+) ($(
match this.$T.project_replace(ExtractFuture::Empty) {
ExtractReplaceProj::Done { output } => output,
_ => unreachable!("FromRequest polled after finished"),
},
)+)
)) ))
} else { } else {
Poll::Pending Poll::Pending
} }
} }
} }
};
} }
});
#[rustfmt::skip] pin_project! {
mod m { #[project = ExtractProj]
use super::*; #[project_replace = ExtractReplaceProj]
enum ExtractFuture<Fut, Res> {
Future {
#[pin]
fut: Fut
},
Done {
output: Res,
},
Empty
}
}
tuple_from_req!(TupleFromRequest1, (0, A)); tuple_from_req! { TupleFromRequest1; A }
tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); tuple_from_req! { TupleFromRequest2; A, B }
tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); tuple_from_req! { TupleFromRequest3; A, B, C }
tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D)); tuple_from_req! { TupleFromRequest4; A, B, C, D }
tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E)); tuple_from_req! { TupleFromRequest5; A, B, C, D, E }
tuple_from_req!(TupleFromRequest6, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); tuple_from_req! { TupleFromRequest6; A, B, C, D, E, F }
tuple_from_req!(TupleFromRequest7, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); tuple_from_req! { TupleFromRequest7; A, B, C, D, E, F, G }
tuple_from_req!(TupleFromRequest8, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); tuple_from_req! { TupleFromRequest8; A, B, C, D, E, F, G, H }
tuple_from_req!(TupleFromRequest9, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); tuple_from_req! { TupleFromRequest9; A, B, C, D, E, F, G, H, I }
tuple_from_req!(TupleFromRequest10, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); tuple_from_req! { TupleFromRequest10; A, B, C, D, E, F, G, H, I, J }
} }
#[cfg(test)] #[cfg(test)]
@ -494,4 +499,26 @@ mod tests {
let method = Method::extract(&req).await.unwrap(); let method = Method::extract(&req).await.unwrap();
assert_eq!(method, Method::GET); assert_eq!(method, Method::GET);
} }
#[actix_rt::test]
async fn test_concurrent() {
let (req, mut pl) = TestRequest::default()
.uri("/foo/bar")
.method(Method::GET)
.insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
.insert_header((header::CONTENT_LENGTH, "11"))
.set_payload(Bytes::from_static(b"hello=world"))
.to_http_parts();
let (method, uri, form) = <(Method, Uri, Form<Info>)>::from_request(&req, &mut pl)
.await
.unwrap();
assert_eq!(method, Method::GET);
assert_eq!(uri.path(), "/foo/bar");
assert_eq!(
form,
Form(Info {
hello: "world".into()
})
);
}
} }