1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-11-25 11:01:14 +00:00

migrate actix-web to std::future

This commit is contained in:
Nikolay Kim 2019-11-20 23:33:22 +06:00
parent d081e57316
commit 3127dd4db6
46 changed files with 4134 additions and 3720 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "1.0.9"
version = "2.0.0-alpha.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
@ -16,7 +16,7 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018"
[package.metadata.docs.rs]
features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls", "uds"]
features = ["openssl", "rustls", "brotli", "flate2-zlib", "secure-cookies", "client"]
[badges]
travis-ci = { repository = "actix/actix-web", branch = "master" }
@ -28,20 +28,19 @@ path = "src/lib.rs"
[workspace]
members = [
# ".",
# "awc",
# #"actix-http",
# "actix-cors",
# "actix-files",
# "actix-framed",
# "actix-session",
# "actix-identity",
# "actix-multipart",
# "actix-web-actors",
# "actix-web-codegen",
# "test-server",
".",
"awc",
"actix-http",
"actix-cors",
"actix-files",
"actix-framed",
"actix-session",
"actix-identity",
"actix-multipart",
"actix-web-actors",
"actix-web-codegen",
"test-server",
]
exclude = ["awc", "actix-http", "test-server"]
[features]
default = ["brotli", "flate2-zlib", "client", "fail"]
@ -64,37 +63,35 @@ secure-cookies = ["actix-http/secure-cookies"]
fail = ["actix-http/fail"]
# openssl
ssl = ["openssl", "actix-server/ssl", "awc/ssl"]
openssl = ["open-ssl", "actix-server/openssl", "awc/openssl"]
# rustls
rust-tls = ["rustls", "actix-server/rust-tls", "awc/rust-tls"]
# unix domain sockets support
uds = ["actix-server/uds"]
rustls = ["rust-tls", "actix-server/rustls", "awc/rustls"]
[dependencies]
actix-codec = "0.1.2"
actix-service = "0.4.1"
actix-utils = "0.4.4"
actix-codec = "0.2.0-alpha.1"
actix-service = "1.0.0-alpha.1"
actix-utils = "0.5.0-alpha.1"
actix-router = "0.1.5"
actix-rt = "0.2.4"
actix-rt = "1.0.0-alpha.1"
actix-web-codegen = "0.1.2"
actix-http = "0.2.11"
actix-server = "0.6.1"
actix-server-config = "0.1.2"
actix-testing = "0.1.0"
actix-threadpool = "0.1.1"
awc = { version = "0.2.7", optional = true }
actix-http = "0.3.0-alpha.1"
actix-server = "0.8.0-alpha.1"
actix-server-config = "0.3.0-alpha.1"
actix-testing = "0.3.0-alpha.1"
actix-threadpool = "0.2.0-alpha.1"
awc = { version = "0.3.0-alpha.1", optional = true }
bytes = "0.4"
derive_more = "0.15.0"
encoding_rs = "0.8"
futures = "0.1.25"
futures = "0.3.1"
hashbrown = "0.6.3"
log = "0.4"
mime = "0.3"
net2 = "0.2.33"
parking_lot = "0.9"
pin-project = "0.4.5"
regex = "1.0"
serde = { version = "1.0", features=["derive"] }
serde_json = "1.0"
@ -103,17 +100,17 @@ time = "0.1.42"
url = "2.1"
# ssl support
openssl = { version="0.10", optional = true }
rustls = { version = "0.15", optional = true }
open-ssl = { version="0.10", package="openssl", optional = true }
rust-tls = { version = "0.16", package="rustls", optional = true }
[dev-dependencies]
actix = "0.8.3"
actix-connect = "0.2.2"
actix-http-test = "0.2.4"
# actix = "0.8.3"
actix-connect = "0.3.0-alpha.1"
actix-http-test = "0.3.0-alpha.1"
rand = "0.7"
env_logger = "0.6"
serde_derive = "1.0"
tokio-timer = "0.2.8"
tokio-timer = "0.3.0-alpha.6"
brotli2 = "0.3.2"
flate2 = "1.0.2"
@ -123,19 +120,18 @@ opt-level = 3
codegen-units = 1
[patch.crates-io]
# actix-web = { path = "." }
# actix-http = { path = "actix-http" }
# actix-http-test = { path = "test-server" }
# actix-web-codegen = { path = "actix-web-codegen" }
actix-web = { path = "." }
actix-http = { path = "actix-http" }
actix-http-test = { path = "test-server" }
actix-web-codegen = { path = "actix-web-codegen" }
# actix-web-actors = { path = "actix-web-actors" }
# actix-session = { path = "actix-session" }
# actix-files = { path = "actix-files" }
# actix-multipart = { path = "actix-multipart" }
# awc = { path = "awc" }
actix-session = { path = "actix-session" }
actix-files = { path = "actix-files" }
actix-multipart = { path = "actix-multipart" }
awc = { path = "awc" }
actix-codec = { path = "../actix-net/actix-codec" }
actix-connect = { path = "../actix-net/actix-connect" }
actix-ioframe = { path = "../actix-net/actix-ioframe" }
actix-rt = { path = "../actix-net/actix-rt" }
actix-server = { path = "../actix-net/actix-server" }
actix-server-config = { path = "../actix-net/actix-server-config" }

View file

@ -1,6 +1,6 @@
[package]
name = "actix-cors"
version = "0.1.0"
version = "0.2.0-alpha.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Cross-origin resource sharing (CORS) for Actix applications."
readme = "README.md"
@ -10,14 +10,14 @@ repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-cors/"
license = "MIT/Apache-2.0"
edition = "2018"
#workspace = ".."
workspace = ".."
[lib]
name = "actix_cors"
path = "src/lib.rs"
[dependencies]
actix-web = "1.0.0"
actix-service = "0.4.0"
actix-web = "2.0.0-alpha.1"
actix-service = "1.0.0-alpha.1"
derive_more = "0.15.0"
futures = "0.1.25"
futures = "0.3.1"

View file

@ -1,6 +1,6 @@
[package]
name = "actix-files"
version = "0.1.7"
version = "0.2.0-alpha.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web."
readme = "README.md"
@ -18,12 +18,12 @@ name = "actix_files"
path = "src/lib.rs"
[dependencies]
actix-web = { version = "1.0.8", default-features = false }
actix-http = "0.2.11"
actix-service = "0.4.1"
actix-web = { version = "2.0.0-alpha.1", default-features = false }
actix-http = "0.3.0-alpha.1"
actix-service = "1.0.0-alpha.1"
bitflags = "1"
bytes = "0.4"
futures = "0.1.25"
futures = "0.3.1"
derive_more = "0.15.0"
log = "0.4"
mime = "0.3"
@ -32,4 +32,4 @@ percent-encoding = "2.1"
v_htmlescape = "0.4"
[dev-dependencies]
actix-web = { version = "1.0.8", features=["ssl"] }
actix-web = { version = "2.0.0-alpha.1", features=["openssl"] }

View file

@ -303,9 +303,8 @@ impl Files {
/// Set custom directory renderer
pub fn files_listing_renderer<F>(mut self, f: F) -> Self
where
for<'r, 's> F:
Fn(&'r Directory, &'s HttpRequest) -> Result<ServiceResponse, io::Error>
+ 'static,
for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) -> Result<ServiceResponse, io::Error>
+ 'static,
{
self.renderer = Rc::new(f);
self

View file

@ -1,6 +1,6 @@
[package]
name = "actix-framed"
version = "0.2.1"
version = "0.3.0-alpha.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix framed app server"
readme = "README.md"
@ -20,19 +20,19 @@ name = "actix_framed"
path = "src/lib.rs"
[dependencies]
actix-codec = "0.1.2"
actix-service = "0.4.1"
actix-codec = "0.2.0-alpha.1"
actix-service = "1.0.0-alpha.1"
actix-router = "0.1.2"
actix-rt = "0.2.2"
actix-http = "0.2.7"
actix-server-config = "0.1.2"
actix-rt = "1.0.0-alpha.1"
actix-http = "0.3.0-alpha.1"
actix-server-config = "0.2.0-alpha.1"
bytes = "0.4"
futures = "0.1.25"
log = "0.4"
[dev-dependencies]
actix-server = { version = "0.6.0", features=["ssl"] }
actix-connect = { version = "0.2.0", features=["ssl"] }
actix-http-test = { version = "0.2.4", features=["ssl"] }
actix-utils = "0.4.4"
actix-server = { version = "0.8.0-alpha.1", features=["openssl"] }
actix-connect = { version = "0.3.0-alpha.1", features=["openssl"] }
actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] }
actix-utils = "0.5.0-alpha.1"

View file

@ -13,8 +13,7 @@ categories = ["network-programming", "asynchronous",
"web-programming::websocket"]
license = "MIT/Apache-2.0"
edition = "2018"
# workspace = ".."
workspace = ".."
[package.metadata.docs.rs]
features = ["openssl", "fail", "brotli", "flate2-zlib", "secure-cookies"]
@ -114,18 +113,3 @@ actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] }
env_logger = "0.6"
serde_derive = "1.0"
open-ssl = { version="0.10", package="openssl" }
[patch.crates-io]
awc = { path = "../awc" }
actix-http = { path = "." }
actix-http-test = { path = "../test-server" }
actix-codec = { path = "../../actix-net/actix-codec" }
actix-connect = { path = "../../actix-net/actix-connect" }
actix-rt = { path = "../../actix-net/actix-rt" }
actix-server = { path = "../../actix-net/actix-server" }
actix-server-config = { path = "../../actix-net/actix-server-config" }
actix-service = { path = "../../actix-net/actix-service" }
actix-testing = { path = "../../actix-net/actix-testing" }
actix-threadpool = { path = "../../actix-net/actix-threadpool" }
actix-utils = { path = "../../actix-net/actix-utils" }

View file

@ -172,12 +172,11 @@ where
/// Finish service configuration and create *http service* for HTTP/1 protocol.
pub fn h1<F, P, B>(self, service: F) -> H1Service<T, P, S, B, X, U>
where
B: MessageBody + 'static,
B: MessageBody,
F: IntoServiceFactory<S>,
S::Error: Into<Error> + 'static,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
S::Response: Into<Response<B>>,
{
let cfg = ServiceConfig::new(
self.keep_alive,

View file

@ -51,10 +51,10 @@ impl<T, S, B> Dispatcher<T, S, B>
where
T: IoStream,
S: Service<Request = Request>,
S::Error: Into<Error> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
S::Error: Into<Error>,
// S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody,
{
pub(crate) fn new(
service: CloneableService<S>,
@ -176,9 +176,9 @@ enum ServiceResponseState<F, B> {
impl<F, I, E, B> ServiceResponse<F, I, E, B>
where
F: Future<Output = Result<I, E>>,
E: Into<Error> + 'static,
I: Into<Response<B>> + 'static,
B: MessageBody + 'static,
E: Into<Error>,
I: Into<Response<B>>,
B: MessageBody,
{
fn prepare_response(
&self,
@ -244,9 +244,9 @@ where
impl<F, I, E, B> Future for ServiceResponse<F, I, E, B>
where
F: Future<Output = Result<I, E>>,
E: Into<Error> + 'static,
I: Into<Response<B>> + 'static,
B: MessageBody + 'static,
E: Into<Error>,
I: Into<Response<B>>,
B: MessageBody,
{
type Output = ();

View file

@ -1,6 +1,6 @@
[package]
name = "actix-identity"
version = "0.1.0"
version = "0.2.0-alpha.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Identity service for actix web framework."
readme = "README.md"
@ -17,14 +17,14 @@ name = "actix_identity"
path = "src/lib.rs"
[dependencies]
actix-web = { version = "1.0.0", default-features = false, features = ["secure-cookies"] }
actix-service = "0.4.0"
futures = "0.1.25"
actix-web = { version = "2.0.0-alpha.1", default-features = false, features = ["secure-cookies"] }
actix-service = "1.0.0-alpha.1"
futures = "0.3.1"
serde = "1.0"
serde_json = "1.0"
time = "0.1.42"
[dev-dependencies]
actix-rt = "0.2.2"
actix-http = "0.2.3"
actix-rt = "1.0.0-alpha.1"
actix-http = "0.3.0-alpha.1"
bytes = "0.4"

View file

@ -1,6 +1,6 @@
[package]
name = "actix-multipart"
version = "0.1.4"
version = "0.2.0-alpha.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Multipart support for actix web framework."
readme = "README.md"
@ -18,17 +18,17 @@ name = "actix_multipart"
path = "src/lib.rs"
[dependencies]
actix-web = { version = "1.0.0", default-features = false }
actix-service = "0.4.1"
actix-web = { version = "2.0.0-alpha.1", default-features = false }
actix-service = "1.0.0-alpha.1"
bytes = "0.4"
derive_more = "0.15.0"
httparse = "1.3"
futures = "0.1.25"
futures = "0.3.1"
log = "0.4"
mime = "0.3"
time = "0.1"
twoway = "0.2"
[dev-dependencies]
actix-rt = "0.2.2"
actix-http = "0.2.4"
actix-rt = "1.0.0-alpha.1"
actix-http = "0.3.0-alpha.1"

View file

@ -1,6 +1,6 @@
[package]
name = "actix-session"
version = "0.2.0"
version = "0.3.0-alpha.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Session for actix web framework."
readme = "README.md"
@ -24,15 +24,15 @@ default = ["cookie-session"]
cookie-session = ["actix-web/secure-cookies"]
[dependencies]
actix-web = "1.0.0"
actix-service = "0.4.1"
actix-web = "2.0.0-alpha.1"
actix-service = "1.0.0-alpha.1"
bytes = "0.4"
derive_more = "0.15.0"
futures = "0.1.25"
hashbrown = "0.5.0"
futures = "0.3.1"
hashbrown = "0.6.3"
serde = "1.0"
serde_json = "1.0"
time = "0.1.42"
[dev-dependencies]
actix-rt = "0.2.2"
actix-rt = "1.0.0-alpha.1"

View file

@ -63,7 +63,7 @@ rust-tls = { version = "0.16.0", package="rustls", optional = true }
[dev-dependencies]
actix-rt = "1.0.0-alpha.1"
#actix-web = { version = "1.0.8", features=["ssl"] }
actix-web = { version = "2.0.0-alpha.1", features=["openssl"] }
actix-http = { version = "0.3.0-alpha.1", features=["openssl"] }
actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] }
actix-utils = "0.5.0-alpha.1"
@ -75,17 +75,3 @@ rand = "0.7"
tokio-tcp = "0.1"
webpki = { version = "0.21" }
rus-tls = { version = "0.16.0", package="rustls", features = ["dangerous_configuration"] }
[patch.crates-io]
awc = { path = "." }
actix-http = { path = "../actix-http" }
actix-http-test = { path = "../test-server" }
actix-codec = { path = "../../actix-net/actix-codec" }
actix-connect = { path = "../../actix-net/actix-connect" }
actix-rt = { path = "../../actix-net/actix-rt" }
actix-server = { path = "../../actix-net/actix-server" }
actix-server-config = { path = "../../actix-net/actix-server-config" }
actix-service = { path = "../../actix-net/actix-service" }
actix-threadpool = { path = "../../actix-net/actix-threadpool" }
actix-utils = { path = "../../actix-net/actix-utils" }

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,3 @@
use futures::IntoFuture;
use actix_web::{
get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer,
};
@ -10,7 +8,7 @@ fn index(req: HttpRequest, name: web::Path<String>) -> String {
format!("Hello: {}!\r\n", name)
}
fn index_async(req: HttpRequest) -> impl IntoFuture<Item = &'static str, Error = Error> {
async fn index_async(req: HttpRequest) -> Result<&'static str, Error> {
println!("REQ: {:?}", req);
Ok("Hello world!\r\n")
}
@ -28,7 +26,7 @@ fn main() -> std::io::Result<()> {
App::new()
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
.wrap(middleware::Compress::default())
.wrap(middleware::Logger::default())
// .wrap(middleware::Logger::default())
.service(index)
.service(no_params)
.service(

View file

@ -1,26 +1,27 @@
use actix_http::Error;
use actix_rt::System;
use futures::{future::lazy, Future};
fn main() -> Result<(), Error> {
std::env::set_var("RUST_LOG", "actix_http=trace");
env_logger::init();
System::new("test").block_on(lazy(|| {
awc::Client::new()
.get("https://www.rust-lang.org/") // <- Create request builder
.header("User-Agent", "Actix-web")
.send() // <- Send http request
.from_err()
.and_then(|mut response| {
// <- server http response
println!("Response: {:?}", response);
System::new("test").block_on(async {
let client = awc::Client::new();
// read response body
response
.body()
.from_err()
.map(|body| println!("Downloaded: {:?} bytes", body.len()))
})
}))
// Create request builder, configure request and send
let mut response = client
.get("https://www.rust-lang.org/")
.header("User-Agent", "Actix-web")
.send()
.await?;
// server http response
println!("Response: {:?}", response);
// read response body
let body = response.body().await?;
println!("Downloaded: {:?} bytes", body.len());
Ok(())
})
}

View file

@ -1,5 +1,3 @@
use futures::IntoFuture;
use actix_web::{
get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer,
};
@ -10,7 +8,7 @@ fn index(req: HttpRequest, name: web::Path<String>) -> String {
format!("Hello: {}!\r\n", name)
}
fn index_async(req: HttpRequest) -> impl IntoFuture<Item = &'static str, Error = Error> {
async fn index_async(req: HttpRequest) -> Result<&'static str, Error> {
println!("REQ: {:?}", req);
Ok("Hello world!\r\n")
}
@ -29,7 +27,7 @@ fn main() -> std::io::Result<()> {
App::new()
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
.wrap(middleware::Compress::default())
.wrap(middleware::Logger::default())
// .wrap(middleware::Logger::default())
.service(index)
.service(no_params)
.service(
@ -38,7 +36,7 @@ fn main() -> std::io::Result<()> {
middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"),
)
.default_service(
web::route().to(|| HttpResponse::MethodNotAllowed()),
web::route().to(|| ok(HttpResponse::MethodNotAllowed())),
)
.route(web::get().to_async(index_async)),
)

View file

@ -1,14 +1,17 @@
use std::cell::RefCell;
use std::fmt;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_http::body::{Body, MessageBody};
use actix_service::boxed::{self, BoxedNewService};
use actix_service::{
apply_transform, IntoNewService, IntoTransform, NewService, Transform,
apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, Transform,
};
use futures::{Future, IntoFuture};
use futures::future::{FutureExt, LocalBoxFuture};
use crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
use crate::config::{AppConfig, AppConfigInner, ServiceConfig};
@ -18,19 +21,19 @@ use crate::error::Error;
use crate::resource::Resource;
use crate::route::Route;
use crate::service::{
HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest,
AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest,
ServiceResponse,
};
type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
type FnDataFactory =
Box<dyn Fn() -> Box<dyn Future<Item = Box<dyn DataFactory>, Error = ()>>>;
Box<dyn Fn() -> LocalBoxFuture<'static, Result<Box<dyn DataFactory>, ()>>>;
/// Application builder - structure that follows the builder pattern
/// for building application instances.
pub struct App<T, B> {
endpoint: T,
services: Vec<Box<dyn ServiceFactory>>,
services: Vec<Box<dyn AppServiceFactory>>,
default: Option<Rc<HttpNewService>>,
factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
data: Vec<Box<dyn DataFactory>>,
@ -61,7 +64,7 @@ impl App<AppEntry, Body> {
impl<T, B> App<T, B>
where
B: MessageBody,
T: NewService<
T: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse<B>,
@ -107,24 +110,30 @@ where
/// Set application data factory. This function is
/// similar to `.data()` but it accepts data factory. Data object get
/// constructed asynchronously during application initialization.
pub fn data_factory<F, Out>(mut self, data: F) -> Self
pub fn data_factory<F, Out, D, E>(mut self, data: F) -> Self
where
F: Fn() -> Out + 'static,
Out: IntoFuture + 'static,
Out::Error: std::fmt::Debug,
Out: Future<Output = Result<D, E>> + 'static,
D: 'static,
E: std::fmt::Debug,
{
self.data_factories.push(Box::new(move || {
Box::new(
data()
.into_future()
.map_err(|e| {
log::error!("Can not construct data instance: {:?}", e);
})
.map(|data| {
let data: Box<dyn DataFactory> = Box::new(Data::new(data));
data
}),
)
{
let fut = data();
async move {
match fut.await {
Err(e) => {
log::error!("Can not construct data instance: {:?}", e);
Err(())
}
Ok(data) => {
let data: Box<dyn DataFactory> = Box::new(Data::new(data));
Ok(data)
}
}
}
}
.boxed_local()
}));
self
}
@ -267,8 +276,8 @@ where
/// ```
pub fn default_service<F, U>(mut self, f: F) -> Self
where
F: IntoNewService<U>,
U: NewService<
F: IntoServiceFactory<U>,
U: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse,
@ -277,11 +286,9 @@ where
U::InitError: fmt::Debug,
{
// create and configure default resource
self.default = Some(Rc::new(boxed::new_service(
f.into_new_service().map_init_err(|e| {
log::error!("Can not construct default service: {:?}", e)
}),
)));
self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err(
|e| log::error!("Can not construct default service: {:?}", e),
))));
self
}
@ -350,11 +357,11 @@ where
/// .route("/index.html", web::get().to(index));
/// }
/// ```
pub fn wrap<M, B1, F>(
pub fn wrap<M, B1>(
self,
mw: F,
mw: M,
) -> App<
impl NewService<
impl ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse<B1>,
@ -372,11 +379,9 @@ where
InitError = (),
>,
B1: MessageBody,
F: IntoTransform<M, T::Service>,
{
let endpoint = apply_transform(mw, self.endpoint);
App {
endpoint,
endpoint: apply(mw, self.endpoint),
data: self.data,
data_factories: self.data_factories,
services: self.services,
@ -407,13 +412,16 @@ where
///
/// fn main() {
/// let app = App::new()
/// .wrap_fn(|req, srv|
/// srv.call(req).map(|mut res| {
/// .wrap_fn(|req, srv| {
/// let fut = srv.call(req);
/// async {
/// let mut res = fut.await?;
/// res.headers_mut().insert(
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
/// );
/// res
/// }))
/// Ok(res)
/// }
/// })
/// .route("/index.html", web::get().to(index));
/// }
/// ```
@ -421,7 +429,7 @@ where
self,
mw: F,
) -> App<
impl NewService<
impl ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse<B1>,
@ -433,16 +441,26 @@ where
where
B1: MessageBody,
F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone,
R: IntoFuture<Item = ServiceResponse<B1>, Error = Error>,
R: Future<Output = Result<ServiceResponse<B1>, Error>>,
{
self.wrap(mw)
App {
endpoint: apply_fn_factory(self.endpoint, mw),
data: self.data,
data_factories: self.data_factories,
services: self.services,
default: self.default,
factory_ref: self.factory_ref,
config: self.config,
external: self.external,
_t: PhantomData,
}
}
}
impl<T, B> IntoNewService<AppInit<T, B>> for App<T, B>
impl<T, B> IntoServiceFactory<AppInit<T, B>> for App<T, B>
where
B: MessageBody,
T: NewService<
T: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse<B>,
@ -450,7 +468,7 @@ where
InitError = (),
>,
{
fn into_new_service(self) -> AppInit<T, B> {
fn into_factory(self) -> AppInit<T, B> {
AppInit {
data: Rc::new(self.data),
data_factories: Rc::new(self.data_factories),
@ -468,82 +486,89 @@ where
mod tests {
use actix_service::Service;
use bytes::Bytes;
use futures::{Future, IntoFuture};
use futures::future::{ok, Future};
use super::*;
use crate::http::{header, HeaderValue, Method, StatusCode};
use crate::middleware::DefaultHeaders;
use crate::service::{ServiceRequest, ServiceResponse};
use crate::test::{
block_fn, block_on, call_service, init_service, read_body, TestRequest,
};
use crate::test::{block_on, call_service, init_service, read_body, TestRequest};
use crate::{web, Error, HttpRequest, HttpResponse};
#[test]
fn test_default_resource() {
let mut srv = init_service(
App::new().service(web::resource("/test").to(|| HttpResponse::Ok())),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = block_fn(|| srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
block_on(async {
let mut srv = init_service(
App::new().service(web::resource("/test").to(|| HttpResponse::Ok())),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/blah").to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/blah").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut srv = init_service(
App::new()
.service(web::resource("/test").to(|| HttpResponse::Ok()))
.service(
web::resource("/test2")
.default_service(|r: ServiceRequest| {
r.into_response(HttpResponse::Created())
})
.route(web::get().to(|| HttpResponse::Ok())),
)
.default_service(|r: ServiceRequest| {
r.into_response(HttpResponse::MethodNotAllowed())
}),
);
let mut srv = init_service(
App::new()
.service(web::resource("/test").to(|| HttpResponse::Ok()))
.service(
web::resource("/test2")
.default_service(|r: ServiceRequest| {
ok(r.into_response(HttpResponse::Created()))
})
.route(web::get().to(|| HttpResponse::Ok())),
)
.default_service(|r: ServiceRequest| {
ok(r.into_response(HttpResponse::MethodNotAllowed()))
}),
)
.await;
let req = TestRequest::with_uri("/blah").to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let req = TestRequest::with_uri("/blah").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let req = TestRequest::with_uri("/test2").to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test2").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test2")
.method(Method::POST)
.to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test2")
.method(Method::POST)
.to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::CREATED);
})
}
#[test]
fn test_data_factory() {
let mut srv =
init_service(App::new().data_factory(|| Ok::<_, ()>(10usize)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
block_on(async {
let mut srv =
init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
))
.await;
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let mut srv =
init_service(App::new().data_factory(|| Ok::<_, ()>(10u32)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
let mut srv =
init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
))
.await;
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
})
}
fn md<S, B>(
req: ServiceRequest,
srv: &mut S,
) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error>
) -> impl Future<Output = Result<ServiceResponse<B>, Error>>
where
S: Service<
Request = ServiceRequest,
@ -551,112 +576,141 @@ mod tests {
Error = Error,
>,
{
srv.call(req).map(|mut res| {
let fut = srv.call(req);
async move {
let mut res = fut.await?;
res.headers_mut()
.insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
res
})
Ok(res)
}
}
#[test]
fn test_wrap() {
let mut srv = init_service(
App::new()
.wrap(md)
.route("/test", web::get().to(|| HttpResponse::Ok())),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
block_on(async {
let mut srv =
init_service(
App::new()
.wrap(DefaultHeaders::new().header(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
))
.route("/test", web::get().to(|| HttpResponse::Ok())),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
})
}
#[test]
fn test_router_wrap() {
let mut srv = init_service(
App::new()
.route("/test", web::get().to(|| HttpResponse::Ok()))
.wrap(md),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
block_on(async {
let mut srv =
init_service(
App::new()
.route("/test", web::get().to(|| HttpResponse::Ok()))
.wrap(DefaultHeaders::new().header(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
)),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
})
}
#[test]
fn test_wrap_fn() {
let mut srv = init_service(
App::new()
.wrap_fn(|req, srv| {
srv.call(req).map(|mut res| {
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
);
res
block_on(async {
let mut srv = init_service(
App::new()
.wrap_fn(|req, srv| {
let fut = srv.call(req);
async move {
let mut res = fut.await?;
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
);
Ok(res)
}
})
})
.service(web::resource("/test").to(|| HttpResponse::Ok())),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
.service(web::resource("/test").to(|| HttpResponse::Ok())),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
})
}
#[test]
fn test_router_wrap_fn() {
let mut srv = init_service(
App::new()
.route("/test", web::get().to(|| HttpResponse::Ok()))
.wrap_fn(|req, srv| {
srv.call(req).map(|mut res| {
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
);
res
})
}),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
block_on(async {
let mut srv = init_service(
App::new()
.route("/test", web::get().to(|| HttpResponse::Ok()))
.wrap_fn(|req, srv| {
let fut = srv.call(req);
async {
let mut res = fut.await?;
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
);
Ok(res)
}
}),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
})
}
#[test]
fn test_external_resource() {
let mut srv = init_service(
App::new()
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
.route(
"/test",
web::get().to(|req: HttpRequest| {
HttpResponse::Ok().body(format!(
"{}",
req.url_for("youtube", &["12345"]).unwrap()
))
}),
),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let body = read_body(resp);
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
block_on(async {
let mut srv = init_service(
App::new()
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
.route(
"/test",
web::get().to(|req: HttpRequest| {
HttpResponse::Ok().body(format!(
"{}",
req.url_for("youtube", &["12345"]).unwrap()
))
}),
),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let body = read_body(resp).await;
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
})
}
}

View file

@ -1,14 +1,16 @@
use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_http::{Extensions, Request, Response};
use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url};
use actix_server_config::ServerConfig;
use actix_service::boxed::{self, BoxedNewService, BoxedService};
use actix_service::{service_fn, NewService, Service};
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, Poll};
use actix_service::{service_fn, Service, ServiceFactory};
use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready};
use crate::config::{AppConfig, AppService};
use crate::data::DataFactory;
@ -16,23 +18,20 @@ use crate::error::Error;
use crate::guard::Guard;
use crate::request::{HttpRequest, HttpRequestPool};
use crate::rmap::ResourceMap;
use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse};
use crate::service::{AppServiceFactory, ServiceRequest, ServiceResponse};
type Guards = Vec<Box<dyn Guard>>;
type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
type BoxedResponse = Either<
FutureResult<ServiceResponse, Error>,
Box<dyn Future<Item = ServiceResponse, Error = Error>>,
>;
type BoxedResponse = LocalBoxFuture<'static, Result<ServiceResponse, Error>>;
type FnDataFactory =
Box<dyn Fn() -> Box<dyn Future<Item = Box<dyn DataFactory>, Error = ()>>>;
Box<dyn Fn() -> LocalBoxFuture<'static, Result<Box<dyn DataFactory>, ()>>>;
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
/// It also executes data factories.
pub struct AppInit<T, B>
where
T: NewService<
T: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse<B>,
@ -44,15 +43,15 @@ where
pub(crate) data: Rc<Vec<Box<dyn DataFactory>>>,
pub(crate) data_factories: Rc<Vec<FnDataFactory>>,
pub(crate) config: RefCell<AppConfig>,
pub(crate) services: Rc<RefCell<Vec<Box<dyn ServiceFactory>>>>,
pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
pub(crate) default: Option<Rc<HttpNewService>>,
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
pub(crate) external: RefCell<Vec<ResourceDef>>,
}
impl<T, B> NewService for AppInit<T, B>
impl<T, B> ServiceFactory for AppInit<T, B>
where
T: NewService<
T: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse<B>,
@ -71,8 +70,8 @@ where
fn new_service(&self, cfg: &ServerConfig) -> Self::Future {
// update resource default service
let default = self.default.clone().unwrap_or_else(|| {
Rc::new(boxed::new_service(service_fn(|req: ServiceRequest| {
Ok(req.into_response(Response::NotFound().finish()))
Rc::new(boxed::factory(service_fn(|req: ServiceRequest| {
ok(req.into_response(Response::NotFound().finish()))
})))
});
@ -135,23 +134,25 @@ where
}
}
#[pin_project::pin_project]
pub struct AppInitResult<T, B>
where
T: NewService,
T: ServiceFactory,
{
endpoint: Option<T::Service>,
#[pin]
endpoint_fut: T::Future,
rmap: Rc<ResourceMap>,
config: AppConfig,
data: Rc<Vec<Box<dyn DataFactory>>>,
data_factories: Vec<Box<dyn DataFactory>>,
data_factories_fut: Vec<Box<dyn Future<Item = Box<dyn DataFactory>, Error = ()>>>,
data_factories_fut: Vec<LocalBoxFuture<'static, Result<Box<dyn DataFactory>, ()>>>,
_t: PhantomData<B>,
}
impl<T, B> Future for AppInitResult<T, B>
where
T: NewService<
T: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse<B>,
@ -159,48 +160,49 @@ where
InitError = (),
>,
{
type Item = AppInitService<T::Service, B>;
type Error = ();
type Output = Result<AppInitService<T::Service, B>, ()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let this = self.project();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
// async data factories
let mut idx = 0;
while idx < self.data_factories_fut.len() {
match self.data_factories_fut[idx].poll()? {
Async::Ready(f) => {
self.data_factories.push(f);
let _ = self.data_factories_fut.remove(idx);
while idx < this.data_factories_fut.len() {
match Pin::new(&mut this.data_factories_fut[idx]).poll(cx)? {
Poll::Ready(f) => {
this.data_factories.push(f);
let _ = this.data_factories_fut.remove(idx);
}
Async::NotReady => idx += 1,
Poll::Pending => idx += 1,
}
}
if self.endpoint.is_none() {
if let Async::Ready(srv) = self.endpoint_fut.poll()? {
self.endpoint = Some(srv);
if this.endpoint.is_none() {
if let Poll::Ready(srv) = this.endpoint_fut.poll(cx)? {
*this.endpoint = Some(srv);
}
}
if self.endpoint.is_some() && self.data_factories_fut.is_empty() {
if this.endpoint.is_some() && this.data_factories_fut.is_empty() {
// create app data container
let mut data = Extensions::new();
for f in self.data.iter() {
for f in this.data.iter() {
f.create(&mut data);
}
for f in &self.data_factories {
for f in this.data_factories.iter() {
f.create(&mut data);
}
Ok(Async::Ready(AppInitService {
service: self.endpoint.take().unwrap(),
rmap: self.rmap.clone(),
config: self.config.clone(),
Poll::Ready(Ok(AppInitService {
service: this.endpoint.take().unwrap(),
rmap: this.rmap.clone(),
config: this.config.clone(),
data: Rc::new(data),
pool: HttpRequestPool::create(),
}))
} else {
Ok(Async::NotReady)
Poll::Pending
}
}
}
@ -226,8 +228,8 @@ where
type Error = T::Error;
type Future = T::Future;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future {
@ -270,7 +272,7 @@ pub struct AppRoutingFactory {
default: Rc<HttpNewService>,
}
impl NewService for AppRoutingFactory {
impl ServiceFactory for AppRoutingFactory {
type Config = ();
type Request = ServiceRequest;
type Response = ServiceResponse;
@ -288,7 +290,7 @@ impl NewService for AppRoutingFactory {
CreateAppRoutingItem::Future(
Some(path.clone()),
guards.borrow_mut().take(),
service.new_service(&()),
service.new_service(&()).boxed_local(),
)
})
.collect(),
@ -298,14 +300,14 @@ impl NewService for AppRoutingFactory {
}
}
type HttpServiceFut = Box<dyn Future<Item = HttpService, Error = ()>>;
type HttpServiceFut = LocalBoxFuture<'static, Result<HttpService, ()>>;
/// Create app service
#[doc(hidden)]
pub struct AppRoutingFactoryResponse {
fut: Vec<CreateAppRoutingItem>,
default: Option<HttpService>,
default_fut: Option<Box<dyn Future<Item = HttpService, Error = ()>>>,
default_fut: Option<LocalBoxFuture<'static, Result<HttpService, ()>>>,
}
enum CreateAppRoutingItem {
@ -314,16 +316,15 @@ enum CreateAppRoutingItem {
}
impl Future for AppRoutingFactoryResponse {
type Item = AppRouting;
type Error = ();
type Output = Result<AppRouting, ()>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let mut done = true;
if let Some(ref mut fut) = self.default_fut {
match fut.poll()? {
Async::Ready(default) => self.default = Some(default),
Async::NotReady => done = false,
match Pin::new(fut).poll(cx)? {
Poll::Ready(default) => self.default = Some(default),
Poll::Pending => done = false,
}
}
@ -334,11 +335,12 @@ impl Future for AppRoutingFactoryResponse {
ref mut path,
ref mut guards,
ref mut fut,
) => match fut.poll()? {
Async::Ready(service) => {
) => match Pin::new(fut).poll(cx) {
Poll::Ready(Ok(service)) => {
Some((path.take().unwrap(), guards.take(), service))
}
Async::NotReady => {
Poll::Ready(Err(_)) => return Poll::Ready(Err(())),
Poll::Pending => {
done = false;
None
}
@ -364,13 +366,13 @@ impl Future for AppRoutingFactoryResponse {
}
router
});
Ok(Async::Ready(AppRouting {
Poll::Ready(Ok(AppRouting {
ready: None,
router: router.finish(),
default: self.default.take(),
}))
} else {
Ok(Async::NotReady)
Poll::Pending
}
}
}
@ -387,11 +389,11 @@ impl Service for AppRouting {
type Error = Error;
type Future = BoxedResponse;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
if self.ready.is_none() {
Ok(Async::Ready(()))
Poll::Ready(Ok(()))
} else {
Ok(Async::NotReady)
Poll::Pending
}
}
@ -413,7 +415,7 @@ impl Service for AppRouting {
default.call(req)
} else {
let req = req.into_parts().0;
Either::A(ok(ServiceResponse::new(req, Response::NotFound().finish())))
ok(ServiceResponse::new(req, Response::NotFound().finish())).boxed_local()
}
}
}
@ -429,7 +431,7 @@ impl AppEntry {
}
}
impl NewService for AppEntry {
impl ServiceFactory for AppEntry {
type Config = ();
type Request = ServiceRequest;
type Response = ServiceResponse;
@ -464,15 +466,16 @@ mod tests {
#[test]
fn drop_data() {
let data = Arc::new(AtomicBool::new(false));
{
test::block_on(async {
let mut app = test::init_service(
App::new()
.data(DropData(data.clone()))
.service(web::resource("/test").to(|| HttpResponse::Ok())),
);
)
.await;
let req = test::TestRequest::with_uri("/test").to_request();
let _ = test::block_on(app.call(req)).unwrap();
}
let _ = app.call(req).await.unwrap();
});
assert!(data.load(Ordering::Relaxed));
}
}

View file

@ -3,7 +3,7 @@ use std::rc::Rc;
use actix_http::Extensions;
use actix_router::ResourceDef;
use actix_service::{boxed, IntoNewService, NewService};
use actix_service::{boxed, IntoServiceFactory, ServiceFactory};
use crate::data::{Data, DataFactory};
use crate::error::Error;
@ -12,7 +12,7 @@ use crate::resource::Resource;
use crate::rmap::ResourceMap;
use crate::route::Route;
use crate::service::{
HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest,
AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest,
ServiceResponse,
};
@ -102,11 +102,11 @@ impl AppService {
&mut self,
rdef: ResourceDef,
guards: Option<Vec<Box<dyn Guard>>>,
service: F,
factory: F,
nested: Option<Rc<ResourceMap>>,
) where
F: IntoNewService<S>,
S: NewService<
F: IntoServiceFactory<S>,
S: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse,
@ -116,7 +116,7 @@ impl AppService {
{
self.services.push((
rdef,
boxed::new_service(service.into_new_service()),
boxed::factory(factory.into_factory()),
guards,
nested,
));
@ -174,7 +174,7 @@ impl Default for AppConfigInner {
/// to set of external methods. This could help with
/// modularization of big application configuration.
pub struct ServiceConfig {
pub(crate) services: Vec<Box<dyn ServiceFactory>>,
pub(crate) services: Vec<Box<dyn AppServiceFactory>>,
pub(crate) data: Vec<Box<dyn DataFactory>>,
pub(crate) external: Vec<ResourceDef>,
}
@ -251,17 +251,19 @@ mod tests {
#[test]
fn test_data() {
let cfg = |cfg: &mut ServiceConfig| {
cfg.data(10usize);
};
block_on(async {
let cfg = |cfg: &mut ServiceConfig| {
cfg.data(10usize);
};
let mut srv =
init_service(App::new().configure(cfg).service(
let mut srv = init_service(App::new().configure(cfg).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
))
.await;
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
})
}
// #[test]
@ -298,50 +300,57 @@ mod tests {
#[test]
fn test_external_resource() {
let mut srv = init_service(
App::new()
.configure(|cfg| {
cfg.external_resource(
"youtube",
"https://youtube.com/watch/{video_id}",
);
})
.route(
"/test",
web::get().to(|req: HttpRequest| {
HttpResponse::Ok().body(format!(
"{}",
req.url_for("youtube", &["12345"]).unwrap()
))
}),
),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let body = read_body(resp);
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
block_on(async {
let mut srv = init_service(
App::new()
.configure(|cfg| {
cfg.external_resource(
"youtube",
"https://youtube.com/watch/{video_id}",
);
})
.route(
"/test",
web::get().to(|req: HttpRequest| {
HttpResponse::Ok().body(format!(
"{}",
req.url_for("youtube", &["12345"]).unwrap()
))
}),
),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let body = read_body(resp).await;
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
})
}
#[test]
fn test_service() {
let mut srv = init_service(App::new().configure(|cfg| {
cfg.service(
web::resource("/test").route(web::get().to(|| HttpResponse::Created())),
)
.route("/index.html", web::get().to(|| HttpResponse::Ok()));
}));
block_on(async {
let mut srv = init_service(App::new().configure(|cfg| {
cfg.service(
web::resource("/test")
.route(web::get().to(|| HttpResponse::Created())),
)
.route("/index.html", web::get().to(|| HttpResponse::Ok()));
}))
.await;
let req = TestRequest::with_uri("/test")
.method(Method::GET)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test")
.method(Method::GET)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/index.html")
.method(Method::GET)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/index.html")
.method(Method::GET)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
})
}
}

View file

@ -3,6 +3,7 @@ use std::sync::Arc;
use actix_http::error::{Error, ErrorInternalServerError};
use actix_http::Extensions;
use futures::future::{err, ok, Ready};
use crate::dev::Payload;
use crate::extract::FromRequest;
@ -101,19 +102,19 @@ impl<T> Clone for Data<T> {
impl<T: 'static> FromRequest for Data<T> {
type Config = ();
type Error = Error;
type Future = Result<Self, Error>;
type Future = Ready<Result<Self, Error>>;
#[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
if let Some(st) = req.get_app_data::<T>() {
Ok(st)
ok(st)
} else {
log::debug!(
"Failed to construct App-level Data extractor. \
Request path: {:?}",
req.path()
);
Err(ErrorInternalServerError(
err(ErrorInternalServerError(
"App data is not configured, to configure use App::data()",
))
}
@ -142,85 +143,99 @@ mod tests {
#[test]
fn test_data_extractor() {
let mut srv =
init_service(App::new().data(10usize).service(
block_on(async {
let mut srv = init_service(App::new().data(10usize).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
))
.await;
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let mut srv =
init_service(App::new().data(10u32).service(
let mut srv = init_service(App::new().data(10u32).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
))
.await;
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
})
}
#[test]
fn test_register_data_extractor() {
let mut srv =
init_service(App::new().register_data(Data::new(10usize)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
block_on(async {
let mut srv =
init_service(App::new().register_data(Data::new(10usize)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
))
.await;
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let mut srv =
init_service(App::new().register_data(Data::new(10u32)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
let mut srv =
init_service(App::new().register_data(Data::new(10u32)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
))
.await;
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
})
}
#[test]
fn test_route_data_extractor() {
let mut srv =
init_service(App::new().service(web::resource("/").data(10usize).route(
web::get().to(|data: web::Data<usize>| {
let _ = data.clone();
HttpResponse::Ok()
}),
)));
block_on(async {
let mut srv = init_service(App::new().service(
web::resource("/").data(10usize).route(web::get().to(
|data: web::Data<usize>| {
let _ = data.clone();
HttpResponse::Ok()
},
)),
))
.await;
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
// different type
let mut srv = init_service(
App::new().service(
web::resource("/")
.data(10u32)
.route(web::get().to(|_: web::Data<usize>| HttpResponse::Ok())),
),
);
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
// different type
let mut srv = init_service(
App::new().service(
web::resource("/")
.data(10u32)
.route(web::get().to(|_: web::Data<usize>| HttpResponse::Ok())),
),
)
.await;
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
})
}
#[test]
fn test_override_data() {
let mut srv = init_service(App::new().data(1usize).service(
web::resource("/").data(10usize).route(web::get().to(
|data: web::Data<usize>| {
assert_eq!(*data, 10);
let _ = data.clone();
HttpResponse::Ok()
},
)),
));
block_on(async {
let mut srv = init_service(App::new().data(1usize).service(
web::resource("/").data(10usize).route(web::get().to(
|data: web::Data<usize>| {
assert_eq!(*data, 10);
let _ = data.clone();
HttpResponse::Ok()
},
)),
))
.await;
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
})
}
}

View file

@ -1,8 +1,10 @@
//! Request extractors
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_http::error::Error;
use futures::future::ok;
use futures::{future, Async, Future, IntoFuture, Poll};
use futures::future::{ok, FutureExt, LocalBoxFuture, Ready};
use crate::dev::Payload;
use crate::request::HttpRequest;
@ -15,7 +17,7 @@ pub trait FromRequest: Sized {
type Error: Into<Error>;
/// Future that resolves to a Self
type Future: IntoFuture<Item = Self, Error = Self::Error>;
type Future: Future<Output = Result<Self, Self::Error>>;
/// Configuration for this extractor
type Config: Default + 'static;
@ -48,6 +50,7 @@ pub trait FromRequest: Sized {
/// ```rust
/// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest};
/// use actix_web::error::ErrorBadRequest;
/// use futures::future::{ok, err, Ready};
/// use serde_derive::Deserialize;
/// use rand;
///
@ -58,14 +61,14 @@ pub trait FromRequest: Sized {
///
/// impl FromRequest for Thing {
/// type Error = Error;
/// type Future = Result<Self, Self::Error>;
/// type Future = Ready<Result<Self, Self::Error>>;
/// type Config = ();
///
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// ok(Thing { name: "thingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// err(ErrorBadRequest("no luck"))
/// }
///
/// }
@ -94,21 +97,19 @@ where
{
type Config = T::Config;
type Error = Error;
type Future = Box<dyn Future<Item = Option<T>, Error = Error>>;
type Future = LocalBoxFuture<'static, Result<Option<T>, Error>>;
#[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
Box::new(
T::from_request(req, payload)
.into_future()
.then(|r| match r {
Ok(v) => future::ok(Some(v)),
Err(e) => {
log::debug!("Error for Option<T> extractor: {}", e.into());
future::ok(None)
}
}),
)
T::from_request(req, payload)
.then(|r| match r {
Ok(v) => ok(Some(v)),
Err(e) => {
log::debug!("Error for Option<T> extractor: {}", e.into());
ok(None)
}
})
.boxed_local()
}
}
@ -121,6 +122,7 @@ where
/// ```rust
/// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest};
/// use actix_web::error::ErrorBadRequest;
/// use futures::future::{ok, err, Ready};
/// use serde_derive::Deserialize;
/// use rand;
///
@ -131,14 +133,14 @@ where
///
/// impl FromRequest for Thing {
/// type Error = Error;
/// type Future = Result<Thing, Error>;
/// type Future = Ready<Result<Thing, Error>>;
/// type Config = ();
///
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// ok(Thing { name: "thingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// err(ErrorBadRequest("no luck"))
/// }
/// }
/// }
@ -157,26 +159,24 @@ where
/// );
/// }
/// ```
impl<T: 'static> FromRequest for Result<T, T::Error>
impl<T> FromRequest for Result<T, T::Error>
where
T: FromRequest,
T::Future: 'static,
T: FromRequest + 'static,
T::Error: 'static,
T::Future: 'static,
{
type Config = T::Config;
type Error = Error;
type Future = Box<dyn Future<Item = Result<T, T::Error>, Error = Error>>;
type Future = LocalBoxFuture<'static, Result<Result<T, T::Error>, Error>>;
#[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
Box::new(
T::from_request(req, payload)
.into_future()
.then(|res| match res {
Ok(v) => ok(Ok(v)),
Err(e) => ok(Err(e)),
}),
)
T::from_request(req, payload)
.then(|res| match res {
Ok(v) => ok(Ok(v)),
Err(e) => ok(Err(e)),
})
.boxed_local()
}
}
@ -184,10 +184,10 @@ where
impl FromRequest for () {
type Config = ();
type Error = Error;
type Future = Result<(), Error>;
type Future = Ready<Result<(), Error>>;
fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future {
Ok(())
ok(())
}
}
@ -204,43 +204,44 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
$fut_type {
items: <($(Option<$T>,)+)>::default(),
futs: ($($T::from_request(req, payload).into_future(),)+),
futs: ($($T::from_request(req, payload),)+),
}
}
}
#[doc(hidden)]
#[pin_project::pin_project]
pub struct $fut_type<$($T: FromRequest),+> {
items: ($(Option<$T>,)+),
futs: ($(<$T::Future as futures::IntoFuture>::Future,)+),
futs: ($($T::Future,)+),
}
impl<$($T: FromRequest),+> Future for $fut_type<$($T),+>
{
type Item = ($($T,)+);
type Error = Error;
type Output = Result<($($T,)+), Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let this = self.project();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut ready = true;
$(
if self.items.$n.is_none() {
match self.futs.$n.poll() {
Ok(Async::Ready(item)) => {
self.items.$n = Some(item);
if this.items.$n.is_none() {
match unsafe { Pin::new_unchecked(&mut this.futs.$n) }.poll(cx) {
Poll::Ready(Ok(item)) => {
this.items.$n = Some(item);
}
Ok(Async::NotReady) => ready = false,
Err(e) => return Err(e.into()),
Poll::Pending => ready = false,
Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())),
}
}
)+
if ready {
Ok(Async::Ready(
($(self.items.$n.take().unwrap(),)+)
Poll::Ready(Ok(
($(this.items.$n.take().unwrap(),)+)
))
} else {
Ok(Async::NotReady)
Poll::Pending
}
}
}

View file

@ -1,10 +1,14 @@
use std::convert::Infallible;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_http::{Error, Response};
use actix_service::{NewService, Service};
use futures::future::{ok, FutureResult};
use futures::{try_ready, Async, Future, IntoFuture, Poll};
use actix_service::{Service, ServiceFactory};
use futures::future::{ok, Ready};
use futures::ready;
use pin_project::pin_project;
use crate::extract::FromRequest;
use crate::request::HttpRequest;
@ -73,14 +77,14 @@ where
type Request = (T, HttpRequest);
type Response = ServiceResponse;
type Error = Infallible;
type Future = HandlerServiceResponse<<R::Future as IntoFuture>::Future>;
type Future = HandlerServiceResponse<R>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future {
let fut = self.hnd.call(param).respond_to(&req).into_future();
let fut = self.hnd.call(param).respond_to(&req);
HandlerServiceResponse {
fut,
req: Some(req),
@ -88,53 +92,48 @@ where
}
}
pub struct HandlerServiceResponse<T> {
fut: T,
#[pin_project]
pub struct HandlerServiceResponse<T: Responder> {
#[pin]
fut: T::Future,
req: Option<HttpRequest>,
}
impl<T> Future for HandlerServiceResponse<T>
where
T: Future<Item = Response>,
T::Error: Into<Error>,
{
type Item = ServiceResponse;
type Error = Infallible;
impl<T: Responder> Future for HandlerServiceResponse<T> {
type Output = Result<ServiceResponse, Infallible>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.fut.poll() {
Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new(
self.req.take().unwrap(),
res,
))),
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => {
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let this = self.project();
match this.fut.poll(cx) {
Poll::Ready(Ok(res)) => {
Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res)))
}
Poll::Pending => Poll::Pending,
Poll::Ready(Err(e)) => {
let res: Response = e.into().into();
Ok(Async::Ready(ServiceResponse::new(
self.req.take().unwrap(),
res,
)))
Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res)))
}
}
}
}
/// Async handler converter factory
pub trait AsyncFactory<T, R>: Clone + 'static
pub trait AsyncFactory<T, R, O, E>: Clone + 'static
where
R: IntoFuture,
R::Item: Responder,
R::Error: Into<Error>,
R: Future<Output = Result<O, E>>,
O: Responder,
E: Into<Error>,
{
fn call(&self, param: T) -> R;
}
impl<F, R> AsyncFactory<(), R> for F
impl<F, R, O, E> AsyncFactory<(), R, O, E> for F
where
F: Fn() -> R + Clone + 'static,
R: IntoFuture,
R::Item: Responder,
R::Error: Into<Error>,
R: Future<Output = Result<O, E>>,
O: Responder,
E: Into<Error>,
{
fn call(&self, _: ()) -> R {
(self)()
@ -142,23 +141,23 @@ where
}
#[doc(hidden)]
pub struct AsyncHandler<F, T, R>
pub struct AsyncHandler<F, T, R, O, E>
where
F: AsyncFactory<T, R>,
R: IntoFuture,
R::Item: Responder,
R::Error: Into<Error>,
F: AsyncFactory<T, R, O, E>,
R: Future<Output = Result<O, E>>,
O: Responder,
E: Into<Error>,
{
hnd: F,
_t: PhantomData<(T, R)>,
_t: PhantomData<(T, R, O, E)>,
}
impl<F, T, R> AsyncHandler<F, T, R>
impl<F, T, R, O, E> AsyncHandler<F, T, R, O, E>
where
F: AsyncFactory<T, R>,
R: IntoFuture,
R::Item: Responder,
R::Error: Into<Error>,
F: AsyncFactory<T, R, O, E>,
R: Future<Output = Result<O, E>>,
O: Responder,
E: Into<Error>,
{
pub fn new(hnd: F) -> Self {
AsyncHandler {
@ -168,12 +167,12 @@ where
}
}
impl<F, T, R> Clone for AsyncHandler<F, T, R>
impl<F, T, R, O, E> Clone for AsyncHandler<F, T, R, O, E>
where
F: AsyncFactory<T, R>,
R: IntoFuture,
R::Item: Responder,
R::Error: Into<Error>,
F: AsyncFactory<T, R, O, E>,
R: Future<Output = Result<O, E>>,
O: Responder,
E: Into<Error>,
{
fn clone(&self) -> Self {
AsyncHandler {
@ -183,25 +182,25 @@ where
}
}
impl<F, T, R> Service for AsyncHandler<F, T, R>
impl<F, T, R, O, E> Service for AsyncHandler<F, T, R, O, E>
where
F: AsyncFactory<T, R>,
R: IntoFuture,
R::Item: Responder,
R::Error: Into<Error>,
F: AsyncFactory<T, R, O, E>,
R: Future<Output = Result<O, E>>,
O: Responder,
E: Into<Error>,
{
type Request = (T, HttpRequest);
type Response = ServiceResponse;
type Error = Infallible;
type Future = AsyncHandlerServiceResponse<R::Future>;
type Future = AsyncHandlerServiceResponse<R, O, E>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future {
AsyncHandlerServiceResponse {
fut: self.hnd.call(param).into_future(),
fut: self.hnd.call(param),
fut2: None,
req: Some(req),
}
@ -209,56 +208,54 @@ where
}
#[doc(hidden)]
pub struct AsyncHandlerServiceResponse<T>
#[pin_project]
pub struct AsyncHandlerServiceResponse<T, R, E>
where
T: Future,
T::Item: Responder,
T: Future<Output = Result<R, E>>,
R: Responder,
E: Into<Error>,
{
#[pin]
fut: T,
fut2: Option<<<T::Item as Responder>::Future as IntoFuture>::Future>,
#[pin]
fut2: Option<R::Future>,
req: Option<HttpRequest>,
}
impl<T> Future for AsyncHandlerServiceResponse<T>
impl<T, R, E> Future for AsyncHandlerServiceResponse<T, R, E>
where
T: Future,
T::Item: Responder,
T::Error: Into<Error>,
T: Future<Output = Result<R, E>>,
R: Responder,
E: Into<Error>,
{
type Item = ServiceResponse;
type Error = Infallible;
type Output = Result<ServiceResponse, Infallible>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut2 {
return match fut.poll() {
Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new(
self.req.take().unwrap(),
res,
))),
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let this = self.as_mut().project();
if let Some(fut) = this.fut2.as_pin_mut() {
return match fut.poll(cx) {
Poll::Ready(Ok(res)) => {
Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res)))
}
Poll::Pending => Poll::Pending,
Poll::Ready(Err(e)) => {
let res: Response = e.into().into();
Ok(Async::Ready(ServiceResponse::new(
self.req.take().unwrap(),
res,
)))
Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res)))
}
};
}
match self.fut.poll() {
Ok(Async::Ready(res)) => {
self.fut2 =
Some(res.respond_to(self.req.as_ref().unwrap()).into_future());
self.poll()
match this.fut.poll(cx) {
Poll::Ready(Ok(res)) => {
let fut = res.respond_to(this.req.as_ref().unwrap());
self.as_mut().project().fut2.set(Some(fut));
self.poll(cx)
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => {
Poll::Pending => Poll::Pending,
Poll::Ready(Err(e)) => {
let res: Response = e.into().into();
Ok(Async::Ready(ServiceResponse::new(
self.req.take().unwrap(),
res,
)))
Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res)))
}
}
}
@ -279,7 +276,7 @@ impl<T: FromRequest, S> Extract<T, S> {
}
}
impl<T: FromRequest, S> NewService for Extract<T, S>
impl<T: FromRequest, S> ServiceFactory for Extract<T, S>
where
S: Service<
Request = (T, HttpRequest),
@ -293,7 +290,7 @@ where
type Error = (Error, ServiceRequest);
type InitError = ();
type Service = ExtractService<T, S>;
type Future = FutureResult<Self::Service, ()>;
type Future = Ready<Result<Self::Service, ()>>;
fn new_service(&self, _: &()) -> Self::Future {
ok(ExtractService {
@ -321,13 +318,13 @@ where
type Error = (Error, ServiceRequest);
type Future = ExtractResponse<T, S>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
let (req, mut payload) = req.into_parts();
let fut = T::from_request(&req, &mut payload).into_future();
let fut = T::from_request(&req, &mut payload);
ExtractResponse {
fut,
@ -338,10 +335,13 @@ where
}
}
#[pin_project]
pub struct ExtractResponse<T: FromRequest, S: Service> {
req: HttpRequest,
service: S,
fut: <T::Future as IntoFuture>::Future,
#[pin]
fut: T::Future,
#[pin]
fut_s: Option<S::Future>,
}
@ -353,21 +353,26 @@ where
Error = Infallible,
>,
{
type Item = ServiceResponse;
type Error = (Error, ServiceRequest);
type Output = Result<ServiceResponse, (Error, ServiceRequest)>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut_s {
return fut.poll().map_err(|_| panic!());
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let this = self.as_mut().project();
if let Some(fut) = this.fut_s.as_pin_mut() {
return fut.poll(cx).map_err(|_| panic!());
}
let item = try_ready!(self.fut.poll().map_err(|e| {
let req = ServiceRequest::new(self.req.clone());
(e.into(), req)
}));
self.fut_s = Some(self.service.call((item, self.req.clone())));
self.poll()
match ready!(this.fut.poll(cx)) {
Err(e) => {
let req = ServiceRequest::new(this.req.clone());
Poll::Ready(Err((e.into(), req)))
}
Ok(item) => {
let fut = Some(this.service.call((item, this.req.clone())));
self.as_mut().project().fut_s.set(fut);
self.poll(cx)
}
}
}
}
@ -382,11 +387,11 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => {
}
}
impl<Func, $($T,)+ Res> AsyncFactory<($($T,)+), Res> for Func
impl<Func, $($T,)+ Res, O, E1> AsyncFactory<($($T,)+), Res, O, E1> for Func
where Func: Fn($($T,)+) -> Res + Clone + 'static,
Res: IntoFuture,
Res::Item: Responder,
Res::Error: Into<Error>,
Res: Future<Output = Result<O, E1>>,
O: Responder,
E1: Into<Error>,
{
fn call(&self, param: ($($T,)+)) -> Res {
(self)($(param.$n,)+)

View file

@ -1,4 +1,4 @@
#![allow(clippy::borrow_interior_mutable_const)]
#![allow(clippy::borrow_interior_mutable_const, unused_imports, dead_code)]
//! Actix web is a small, pragmatic, and extremely fast web framework
//! for Rust.
//!
@ -68,8 +68,8 @@
//! ## Package feature
//!
//! * `client` - enables http client (default enabled)
//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2`
//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2`
//! * `openssl` - enables ssl support via `openssl` crate, supports `http/2`
//! * `rustls` - enables ssl support via `rustls` crate, supports `http/2`
//! * `secure-cookies` - enables secure cookies support, includes `ring` crate as
//! dependency
//! * `brotli` - enables `brotli` compression support, requires `c`
@ -78,7 +78,6 @@
//! `c` compiler (default enabled)
//! * `flate2-rust` - experimental rust based implementation for
//! `gzip`, `deflate` compression.
//! * `uds` - Unix domain support, enables `HttpServer::bind_uds()` method.
//!
#![allow(clippy::type_complexity, clippy::new_without_default)]
@ -143,9 +142,10 @@ pub mod dev {
pub use crate::service::{
HttpServiceFactory, ServiceRequest, ServiceResponse, WebService,
};
pub use crate::types::form::UrlEncoded;
pub use crate::types::json::JsonBody;
pub use crate::types::readlines::Readlines;
//pub use crate::types::form::UrlEncoded;
//pub use crate::types::json::JsonBody;
//pub use crate::types::readlines::Readlines;
pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody, SizedStream};
pub use actix_http::encoding::Decoder as Decompress;
@ -176,18 +176,16 @@ pub mod client {
//! use actix_web::client::Client;
//!
//! fn main() {
//! System::new("test").block_on(lazy(|| {
//! System::new("test").block_on(async {
//! let mut client = Client::default();
//!
//! client.get("http://www.rust-lang.org") // <- Create request builder
//! // Create request builder and send request
//! let response = client.get("http://www.rust-lang.org")
//! .header("User-Agent", "Actix-web")
//! .send() // <- Send http request
//! .map_err(|_| ())
//! .and_then(|response| { // <- server http response
//! println!("Response: {:?}", response);
//! Ok(())
//! })
//! }));
//! .send().await; // <- Send http request
//!
//! println!("Response: {:?}", response);
//! });
//! }
//! ```
pub use awc::error::{

View file

@ -1,15 +1,18 @@
//! `Middleware` for compressing response body.
use std::cmp;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::str::FromStr;
use std::task::{Context, Poll};
use actix_http::body::MessageBody;
use actix_http::encoding::Encoder;
use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING};
use actix_http::{Error, Response, ResponseBuilder};
use actix_service::{Service, Transform};
use futures::future::{ok, FutureResult};
use futures::{Async, Future, Poll};
use futures::future::{ok, Ready};
use pin_project::pin_project;
use crate::service::{ServiceRequest, ServiceResponse};
@ -78,7 +81,7 @@ where
type Error = Error;
type InitError = ();
type Transform = CompressMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(CompressMiddleware {
@ -103,8 +106,8 @@ where
type Error = Error;
type Future = CompressResponse<S, B>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
@ -128,11 +131,13 @@ where
}
#[doc(hidden)]
#[pin_project]
pub struct CompressResponse<S, B>
where
S: Service,
B: MessageBody,
{
#[pin]
fut: S::Future,
encoding: ContentEncoding,
_t: PhantomData<(B)>,
@ -143,21 +148,25 @@ where
B: MessageBody,
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{
type Item = ServiceResponse<Encoder<B>>;
type Error = Error;
type Output = Result<ServiceResponse<Encoder<B>>, Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let resp = futures::try_ready!(self.fut.poll());
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let this = self.project();
let enc = if let Some(enc) = resp.response().extensions().get::<Enc>() {
enc.0
} else {
self.encoding
};
match futures::ready!(this.fut.poll(cx)) {
Ok(resp) => {
let enc = if let Some(enc) = resp.response().extensions().get::<Enc>() {
enc.0
} else {
*this.encoding
};
Ok(Async::Ready(resp.map_body(move |head, body| {
Encoder::response(enc, head, body)
})))
Poll::Ready(Ok(
resp.map_body(move |head, body| Encoder::response(enc, head, body))
))
}
Err(e) => Poll::Ready(Err(e)),
}
}
}

View file

@ -1,9 +1,11 @@
//! Middleware for setting default response headers
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_service::{Service, Transform};
use futures::future::{ok, FutureResult};
use futures::{Future, Poll};
use futures::future::{ok, FutureExt, LocalBoxFuture, Ready};
use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
use crate::http::{HeaderMap, HttpTryFrom};
@ -96,7 +98,7 @@ where
type Error = Error;
type InitError = ();
type Transform = DefaultHeadersMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(DefaultHeadersMiddleware {
@ -119,16 +121,19 @@ where
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
let inner = self.inner.clone();
let fut = self.service.call(req);
async move {
let mut res = fut.await?;
Box::new(self.service.call(req).map(move |mut res| {
// set response headers
for (key, value) in inner.headers.iter() {
if !res.headers().contains_key(key) {
@ -142,15 +147,16 @@ where
HeaderValue::from_static("application/octet-stream"),
);
}
res
}))
Ok(res)
}
.boxed_local()
}
}
#[cfg(test)]
mod tests {
use actix_service::IntoService;
use futures::future::ok;
use super::*;
use crate::dev::ServiceRequest;
@ -160,46 +166,50 @@ mod tests {
#[test]
fn test_default_headers() {
let mut mw = block_on(
DefaultHeaders::new()
block_on(async {
let mut mw = DefaultHeaders::new()
.header(CONTENT_TYPE, "0001")
.new_transform(ok_service()),
)
.unwrap();
.new_transform(ok_service())
.await
.unwrap();
let req = TestRequest::default().to_srv_request();
let resp = block_on(mw.call(req)).unwrap();
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let req = TestRequest::default().to_srv_request();
let resp = mw.call(req).await.unwrap();
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let req = TestRequest::default().to_srv_request();
let srv = |req: ServiceRequest| {
req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish())
};
let mut mw = block_on(
DefaultHeaders::new()
let req = TestRequest::default().to_srv_request();
let srv = |req: ServiceRequest| {
ok(req.into_response(
HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(),
))
};
let mut mw = DefaultHeaders::new()
.header(CONTENT_TYPE, "0001")
.new_transform(srv.into_service()),
)
.unwrap();
let resp = block_on(mw.call(req)).unwrap();
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002");
.new_transform(srv.into_service())
.await
.unwrap();
let resp = mw.call(req).await.unwrap();
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002");
})
}
#[test]
fn test_content_type() {
let srv = |req: ServiceRequest| req.into_response(HttpResponse::Ok().finish());
let mut mw = block_on(
DefaultHeaders::new()
block_on(async {
let srv =
|req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish()));
let mut mw = DefaultHeaders::new()
.content_type()
.new_transform(srv.into_service()),
)
.unwrap();
.new_transform(srv.into_service())
.await
.unwrap();
let req = TestRequest::default().to_srv_request();
let resp = block_on(mw.call(req)).unwrap();
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
"application/octet-stream"
);
let req = TestRequest::default().to_srv_request();
let resp = mw.call(req).await.unwrap();
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
"application/octet-stream"
);
})
}
}

View file

@ -2,13 +2,15 @@
use std::collections::HashSet;
use std::env;
use std::fmt::{self, Display, Formatter};
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_service::{Service, Transform};
use bytes::Bytes;
use futures::future::{ok, FutureResult};
use futures::{Async, Future, Poll};
use futures::future::{ok, Ready};
use log::debug;
use regex::Regex;
use time;
@ -125,7 +127,7 @@ where
type Error = Error;
type InitError = ();
type Transform = LoggerMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(LoggerMiddleware {
@ -151,8 +153,8 @@ where
type Error = Error;
type Future = LoggerResponse<S, B>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
@ -181,11 +183,13 @@ where
}
#[doc(hidden)]
#[pin_project::pin_project]
pub struct LoggerResponse<S, B>
where
B: MessageBody,
S: Service,
{
#[pin]
fut: S::Future,
time: time::Tm,
format: Option<Format>,
@ -197,11 +201,15 @@ where
B: MessageBody,
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{
type Item = ServiceResponse<StreamLog<B>>;
type Error = Error;
type Output = Result<ServiceResponse<StreamLog<B>>, Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let res = futures::try_ready!(self.fut.poll());
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let this = self.project();
let res = match futures::ready!(this.fut.poll(cx)) {
Ok(res) => res,
Err(e) => return Poll::Ready(Err(e)),
};
if let Some(error) = res.response().error() {
if res.response().head().status != StatusCode::INTERNAL_SERVER_ERROR {
@ -209,18 +217,21 @@ where
}
}
if let Some(ref mut format) = self.format {
if let Some(ref mut format) = this.format {
for unit in &mut format.0 {
unit.render_response(res.response());
}
}
Ok(Async::Ready(res.map_body(move |_, body| {
let time = *this.time;
let format = this.format.take();
Poll::Ready(Ok(res.map_body(move |_, body| {
ResponseBody::Body(StreamLog {
body,
time,
format,
size: 0,
time: self.time,
format: self.format.take(),
})
})))
}
@ -252,13 +263,13 @@ impl<B: MessageBody> MessageBody for StreamLog<B> {
self.body.size()
}
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
match self.body.poll_next()? {
Async::Ready(Some(chunk)) => {
fn poll_next(&mut self, cx: &mut Context) -> Poll<Option<Result<Bytes, Error>>> {
match self.body.poll_next(cx) {
Poll::Ready(Some(Ok(chunk))) => {
self.size += chunk.len();
Ok(Async::Ready(Some(chunk)))
Poll::Ready(Some(Ok(chunk)))
}
val => Ok(val),
val => val,
}
}
}
@ -464,6 +475,7 @@ impl<'a> fmt::Display for FormatDisplay<'a> {
#[cfg(test)]
mod tests {
use actix_service::{IntoService, Service, Transform};
use futures::future::ok;
use super::*;
use crate::http::{header, StatusCode};
@ -472,11 +484,11 @@ mod tests {
#[test]
fn test_logger() {
let srv = |req: ServiceRequest| {
req.into_response(
ok(req.into_response(
HttpResponse::build(StatusCode::OK)
.header("X-Test", "ttt")
.finish(),
)
))
};
let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test");

View file

@ -2,13 +2,13 @@
mod compress;
pub use self::compress::{BodyEncoding, Compress};
mod condition;
//mod condition;
mod defaultheaders;
pub mod errhandlers;
//pub mod errhandlers;
mod logger;
mod normalize;
//mod normalize;
pub use self::condition::Condition;
//pub use self::condition::Condition;
pub use self::defaultheaders::DefaultHeaders;
pub use self::logger::Logger;
pub use self::normalize::NormalizePath;
//pub use self::normalize::NormalizePath;

View file

@ -5,6 +5,7 @@ use std::{fmt, net};
use actix_http::http::{HeaderMap, Method, Uri, Version};
use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead};
use actix_router::{Path, Url};
use futures::future::{ok, Ready};
use crate::config::AppConfig;
use crate::data::Data;
@ -289,11 +290,11 @@ impl Drop for HttpRequest {
impl FromRequest for HttpRequest {
type Config = ();
type Error = Error;
type Future = Result<Self, Error>;
type Future = Ready<Result<Self, Error>>;
#[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
Ok(req.clone())
ok(req.clone())
}
}
@ -349,7 +350,7 @@ mod tests {
use super::*;
use crate::dev::{ResourceDef, ResourceMap};
use crate::http::{header, StatusCode};
use crate::test::{call_service, init_service, TestRequest};
use crate::test::{block_on, call_service, init_service, TestRequest};
use crate::{web, App, HttpResponse};
#[test]
@ -467,66 +468,73 @@ mod tests {
#[test]
fn test_app_data() {
let mut srv = init_service(App::new().data(10usize).service(
web::resource("/").to(|req: HttpRequest| {
if req.app_data::<usize>().is_some() {
HttpResponse::Ok()
} else {
HttpResponse::BadRequest()
}
}),
));
block_on(async {
let mut srv = init_service(App::new().data(10usize).service(
web::resource("/").to(|req: HttpRequest| {
if req.app_data::<usize>().is_some() {
HttpResponse::Ok()
} else {
HttpResponse::BadRequest()
}
}),
))
.await;
let req = TestRequest::default().to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::default().to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let mut srv = init_service(App::new().data(10u32).service(
web::resource("/").to(|req: HttpRequest| {
if req.app_data::<usize>().is_some() {
HttpResponse::Ok()
} else {
HttpResponse::BadRequest()
}
}),
));
let mut srv = init_service(App::new().data(10u32).service(
web::resource("/").to(|req: HttpRequest| {
if req.app_data::<usize>().is_some() {
HttpResponse::Ok()
} else {
HttpResponse::BadRequest()
}
}),
))
.await;
let req = TestRequest::default().to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::default().to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
})
}
#[test]
fn test_extensions_dropped() {
struct Tracker {
pub dropped: bool,
}
struct Foo {
tracker: Rc<RefCell<Tracker>>,
}
impl Drop for Foo {
fn drop(&mut self) {
self.tracker.borrow_mut().dropped = true;
block_on(async {
struct Tracker {
pub dropped: bool,
}
struct Foo {
tracker: Rc<RefCell<Tracker>>,
}
impl Drop for Foo {
fn drop(&mut self) {
self.tracker.borrow_mut().dropped = true;
}
}
}
let tracker = Rc::new(RefCell::new(Tracker { dropped: false }));
{
let tracker2 = Rc::clone(&tracker);
let mut srv = init_service(App::new().data(10u32).service(
web::resource("/").to(move |req: HttpRequest| {
req.extensions_mut().insert(Foo {
tracker: Rc::clone(&tracker2),
});
HttpResponse::Ok()
}),
));
let tracker = Rc::new(RefCell::new(Tracker { dropped: false }));
{
let tracker2 = Rc::clone(&tracker);
let mut srv = init_service(App::new().data(10u32).service(
web::resource("/").to(move |req: HttpRequest| {
req.extensions_mut().insert(Foo {
tracker: Rc::clone(&tracker2),
});
HttpResponse::Ok()
}),
))
.await;
let req = TestRequest::default().to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
}
let req = TestRequest::default().to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
}
assert!(tracker.borrow().dropped);
assert!(tracker.borrow().dropped);
})
}
}

View file

@ -1,14 +1,17 @@
use std::cell::RefCell;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_http::{Error, Extensions, Response};
use actix_service::boxed::{self, BoxedNewService, BoxedService};
use actix_service::{
apply_transform, IntoNewService, IntoTransform, NewService, Service, Transform,
apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform,
};
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, IntoFuture, Poll};
use futures::future::{ok, Either, LocalBoxFuture, Ready};
use pin_project::pin_project;
use crate::data::Data;
use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef};
@ -74,7 +77,7 @@ impl Resource {
impl<T> Resource<T>
where
T: NewService<
T: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse,
@ -243,8 +246,8 @@ where
/// use actix_web::*;
/// use futures::future::{ok, Future};
///
/// fn index(req: HttpRequest) -> impl Future<Item=HttpResponse, Error=Error> {
/// ok(HttpResponse::Ok().finish())
/// async fn index(req: HttpRequest) -> Result<HttpResponse, Error> {
/// Ok(HttpResponse::Ok().finish())
/// }
///
/// App::new().service(web::resource("/").to_async(index));
@ -255,19 +258,19 @@ where
/// ```rust
/// # use actix_web::*;
/// # use futures::future::Future;
/// # fn index(req: HttpRequest) -> Box<dyn Future<Item=HttpResponse, Error=Error>> {
/// # async fn index(req: HttpRequest) -> Result<HttpResponse, Error> {
/// # unimplemented!()
/// # }
/// App::new().service(web::resource("/").route(web::route().to_async(index)));
/// ```
#[allow(clippy::wrong_self_convention)]
pub fn to_async<F, I, R>(mut self, handler: F) -> Self
pub fn to_async<F, I, R, O, E>(mut self, handler: F) -> Self
where
F: AsyncFactory<I, R>,
F: AsyncFactory<I, R, O, E>,
I: FromRequest + 'static,
R: IntoFuture + 'static,
R::Item: Responder,
R::Error: Into<Error>,
R: Future<Output = Result<O, E>> + 'static,
O: Responder + 'static,
E: Into<Error> + 'static,
{
self.routes.push(Route::new().to_async(handler));
self
@ -280,11 +283,11 @@ where
/// type (i.e modify response's body).
///
/// **Note**: middlewares get called in opposite order of middlewares registration.
pub fn wrap<M, F>(
pub fn wrap<M>(
self,
mw: F,
mw: M,
) -> Resource<
impl NewService<
impl ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse,
@ -300,11 +303,9 @@ where
Error = Error,
InitError = (),
>,
F: IntoTransform<M, T::Service>,
{
let endpoint = apply_transform(mw, self.endpoint);
Resource {
endpoint,
endpoint: apply(mw, self.endpoint),
rdef: self.rdef,
name: self.name,
guards: self.guards,
@ -337,13 +338,16 @@ where
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html")
/// .wrap_fn(|req, srv|
/// srv.call(req).map(|mut res| {
/// .wrap_fn(|req, srv| {
/// let fut = srv.call(req);
/// async {
/// let mut res = fut.await?;
/// res.headers_mut().insert(
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
/// );
/// res
/// }))
/// Ok(res)
/// }
/// })
/// .route(web::get().to(index)));
/// }
/// ```
@ -351,7 +355,7 @@ where
self,
mw: F,
) -> Resource<
impl NewService<
impl ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse,
@ -361,9 +365,18 @@ where
>
where
F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone,
R: IntoFuture<Item = ServiceResponse, Error = Error>,
R: Future<Output = Result<ServiceResponse, Error>>,
{
self.wrap(mw)
Resource {
endpoint: apply_fn_factory(self.endpoint, mw),
rdef: self.rdef,
name: self.name,
guards: self.guards,
routes: self.routes,
default: self.default,
data: self.data,
factory_ref: self.factory_ref,
}
}
/// Default service to be used if no matching route could be found.
@ -371,8 +384,8 @@ where
/// default handler from `App` or `Scope`.
pub fn default_service<F, U>(mut self, f: F) -> Self
where
F: IntoNewService<U>,
U: NewService<
F: IntoServiceFactory<U>,
U: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse,
@ -381,8 +394,8 @@ where
U::InitError: fmt::Debug,
{
// create and configure default resource
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service(
f.into_new_service().map_init_err(|e| {
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
f.into_factory().map_init_err(|e| {
log::error!("Can not construct default service: {:?}", e)
}),
)))));
@ -393,7 +406,7 @@ where
impl<T> HttpServiceFactory for Resource<T>
where
T: NewService<
T: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse,
@ -423,9 +436,9 @@ where
}
}
impl<T> IntoNewService<T> for Resource<T>
impl<T> IntoServiceFactory<T> for Resource<T>
where
T: NewService<
T: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse,
@ -433,7 +446,7 @@ where
InitError = (),
>,
{
fn into_new_service(self) -> T {
fn into_factory(self) -> T {
*self.factory_ref.borrow_mut() = Some(ResourceFactory {
routes: self.routes,
data: self.data.map(Rc::new),
@ -450,7 +463,7 @@ pub struct ResourceFactory {
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
}
impl NewService for ResourceFactory {
impl ServiceFactory for ResourceFactory {
type Config = ();
type Request = ServiceRequest;
type Response = ServiceResponse;
@ -488,31 +501,30 @@ pub struct CreateResourceService {
fut: Vec<CreateRouteServiceItem>,
data: Option<Rc<Extensions>>,
default: Option<HttpService>,
default_fut: Option<Box<dyn Future<Item = HttpService, Error = ()>>>,
default_fut: Option<LocalBoxFuture<'static, Result<HttpService, ()>>>,
}
impl Future for CreateResourceService {
type Item = ResourceService;
type Error = ();
type Output = Result<ResourceService, ()>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let mut done = true;
if let Some(ref mut fut) = self.default_fut {
match fut.poll()? {
Async::Ready(default) => self.default = Some(default),
Async::NotReady => done = false,
match Pin::new(fut).poll(cx)? {
Poll::Ready(default) => self.default = Some(default),
Poll::Pending => done = false,
}
}
// poll http services
for item in &mut self.fut {
match item {
CreateRouteServiceItem::Future(ref mut fut) => match fut.poll()? {
Async::Ready(route) => {
*item = CreateRouteServiceItem::Service(route)
}
Async::NotReady => {
CreateRouteServiceItem::Future(ref mut fut) => match Pin::new(fut)
.poll(cx)?
{
Poll::Ready(route) => *item = CreateRouteServiceItem::Service(route),
Poll::Pending => {
done = false;
}
},
@ -529,13 +541,13 @@ impl Future for CreateResourceService {
CreateRouteServiceItem::Future(_) => unreachable!(),
})
.collect();
Ok(Async::Ready(ResourceService {
Poll::Ready(Ok(ResourceService {
routes,
data: self.data.clone(),
default: self.default.take(),
}))
} else {
Ok(Async::NotReady)
Poll::Pending
}
}
}
@ -551,12 +563,12 @@ impl Service for ResourceService {
type Response = ServiceResponse;
type Error = Error;
type Future = Either<
FutureResult<ServiceResponse, Error>,
Box<dyn Future<Item = ServiceResponse, Error = Error>>,
Ready<Result<ServiceResponse, Error>>,
LocalBoxFuture<'static, Result<ServiceResponse, Error>>,
>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
@ -565,14 +577,14 @@ impl Service for ResourceService {
if let Some(ref data) = self.data {
req.set_data_container(data.clone());
}
return route.call(req);
return Either::Right(route.call(req));
}
}
if let Some(ref mut default) = self.default {
default.call(req)
Either::Right(default.call(req))
} else {
let req = req.into_parts().0;
Either::A(ok(ServiceResponse::new(
Either::Left(ok(ServiceResponse::new(
req,
Response::MethodNotAllowed().finish(),
)))
@ -591,7 +603,7 @@ impl ResourceEndpoint {
}
}
impl NewService for ResourceEndpoint {
impl ServiceFactory for ResourceEndpoint {
type Config = ();
type Request = ServiceRequest;
type Response = ServiceResponse;
@ -610,18 +622,19 @@ mod tests {
use std::time::Duration;
use actix_service::Service;
use futures::{Future, IntoFuture};
use tokio_timer::sleep;
use futures::future::{ok, Future};
use tokio_timer::delay_for;
use crate::http::{header, HeaderValue, Method, StatusCode};
use crate::middleware::DefaultHeaders;
use crate::service::{ServiceRequest, ServiceResponse};
use crate::test::{call_service, init_service, TestRequest};
use crate::test::{block_on, call_service, init_service, TestRequest};
use crate::{guard, web, App, Error, HttpResponse};
fn md<S, B>(
req: ServiceRequest,
srv: &mut S,
) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error>
) -> impl Future<Output = Result<ServiceResponse<B>, Error>>
where
S: Service<
Request = ServiceRequest,
@ -629,178 +642,210 @@ mod tests {
Error = Error,
>,
{
srv.call(req).map(|mut res| {
let fut = srv.call(req);
async move {
let mut res = fut.await?;
res.headers_mut()
.insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
res
})
Ok(res)
}
}
#[test]
fn test_middleware() {
let mut srv = init_service(
App::new().service(
web::resource("/test")
.name("test")
.wrap(md)
.route(web::get().to(|| HttpResponse::Ok())),
),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
block_on(async {
let mut srv = init_service(
App::new().service(
web::resource("/test")
.name("test")
.wrap(DefaultHeaders::new().header(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
))
.route(web::get().to(|| HttpResponse::Ok())),
),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
})
}
#[test]
fn test_middleware_fn() {
let mut srv = init_service(
App::new().service(
web::resource("/test")
.wrap_fn(|req, srv| {
srv.call(req).map(|mut res| {
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
);
res
block_on(async {
let mut srv = init_service(
App::new().service(
web::resource("/test")
.wrap_fn(|req, srv| {
let fut = srv.call(req);
async {
fut.await.map(|mut res| {
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("0001"),
);
res
})
}
})
})
.route(web::get().to(|| HttpResponse::Ok())),
),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
.route(web::get().to(|| HttpResponse::Ok())),
),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001")
);
})
}
#[test]
fn test_to_async() {
let mut srv =
init_service(App::new().service(web::resource("/test").to_async(|| {
sleep(Duration::from_millis(100)).then(|_| HttpResponse::Ok())
})));
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
block_on(async {
let mut srv = init_service(App::new().service(
web::resource("/test").to_async(|| {
async {
delay_for(Duration::from_millis(100)).await;
Ok::<_, Error>(HttpResponse::Ok())
}
}),
))
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
})
}
#[test]
fn test_default_resource() {
let mut srv = init_service(
App::new()
.service(
web::resource("/test").route(web::get().to(|| HttpResponse::Ok())),
)
.default_service(|r: ServiceRequest| {
r.into_response(HttpResponse::BadRequest())
}),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test")
.method(Method::POST)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let mut srv = init_service(
App::new().service(
web::resource("/test")
.route(web::get().to(|| HttpResponse::Ok()))
block_on(async {
let mut srv = init_service(
App::new()
.service(
web::resource("/test")
.route(web::get().to(|| HttpResponse::Ok())),
)
.default_service(|r: ServiceRequest| {
r.into_response(HttpResponse::BadRequest())
ok(r.into_response(HttpResponse::BadRequest()))
}),
),
);
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test")
.method(Method::POST)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let req = TestRequest::with_uri("/test")
.method(Method::POST)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let mut srv = init_service(
App::new().service(
web::resource("/test")
.route(web::get().to(|| HttpResponse::Ok()))
.default_service(|r: ServiceRequest| {
ok(r.into_response(HttpResponse::BadRequest()))
}),
),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test")
.method(Method::POST)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
})
}
#[test]
fn test_resource_guards() {
let mut srv = init_service(
App::new()
.service(
web::resource("/test/{p}")
.guard(guard::Get())
.to(|| HttpResponse::Ok()),
)
.service(
web::resource("/test/{p}")
.guard(guard::Put())
.to(|| HttpResponse::Created()),
)
.service(
web::resource("/test/{p}")
.guard(guard::Delete())
.to(|| HttpResponse::NoContent()),
),
);
block_on(async {
let mut srv = init_service(
App::new()
.service(
web::resource("/test/{p}")
.guard(guard::Get())
.to(|| HttpResponse::Ok()),
)
.service(
web::resource("/test/{p}")
.guard(guard::Put())
.to(|| HttpResponse::Created()),
)
.service(
web::resource("/test/{p}")
.guard(guard::Delete())
.to(|| HttpResponse::NoContent()),
),
)
.await;
let req = TestRequest::with_uri("/test/it")
.method(Method::GET)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/it")
.method(Method::GET)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/it")
.method(Method::PUT)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test/it")
.method(Method::PUT)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test/it")
.method(Method::DELETE)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NO_CONTENT);
let req = TestRequest::with_uri("/test/it")
.method(Method::DELETE)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::NO_CONTENT);
})
}
#[test]
fn test_data() {
let mut srv = init_service(
App::new()
.data(1.0f64)
.data(1usize)
.register_data(web::Data::new('-'))
.service(
web::resource("/test")
.data(10usize)
.register_data(web::Data::new('*'))
.guard(guard::Get())
.to(
|data1: web::Data<usize>,
data2: web::Data<char>,
data3: web::Data<f64>| {
assert_eq!(*data1, 10);
assert_eq!(*data2, '*');
assert_eq!(*data3, 1.0);
HttpResponse::Ok()
},
),
),
);
block_on(async {
let mut srv = init_service(
App::new()
.data(1.0f64)
.data(1usize)
.register_data(web::Data::new('-'))
.service(
web::resource("/test")
.data(10usize)
.register_data(web::Data::new('*'))
.guard(guard::Get())
.to(
|data1: web::Data<usize>,
data2: web::Data<char>,
data3: web::Data<f64>| {
assert_eq!(*data1, 10);
assert_eq!(*data2, '*');
assert_eq!(*data3, 1.0);
HttpResponse::Ok()
},
),
),
)
.await;
let req = TestRequest::get().uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::get().uri("/test").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
})
}
}

View file

@ -1,3 +1,8 @@
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_http::error::InternalError;
use actix_http::http::{
header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, HttpTryFrom,
@ -5,8 +10,9 @@ use actix_http::http::{
};
use actix_http::{Error, Response, ResponseBuilder};
use bytes::{Bytes, BytesMut};
use futures::future::{err, ok, Either as EitherFuture, FutureResult};
use futures::{try_ready, Async, Future, IntoFuture, Poll};
use futures::future::{err, ok, Either as EitherFuture, LocalBoxFuture, Ready};
use futures::ready;
use pin_project::{pin_project, project};
use crate::request::HttpRequest;
@ -18,7 +24,7 @@ pub trait Responder {
type Error: Into<Error>;
/// The future response value.
type Future: IntoFuture<Item = Response, Error = Self::Error>;
type Future: Future<Output = Result<Response, Self::Error>>;
/// Convert itself to `AsyncResult` or `Error`.
fn respond_to(self, req: &HttpRequest) -> Self::Future;
@ -71,7 +77,7 @@ pub trait Responder {
impl Responder for Response {
type Error = Error;
type Future = FutureResult<Response, Error>;
type Future = Ready<Result<Response, Error>>;
#[inline]
fn respond_to(self, _: &HttpRequest) -> Self::Future {
@ -84,15 +90,14 @@ where
T: Responder,
{
type Error = T::Error;
type Future = EitherFuture<
<T::Future as IntoFuture>::Future,
FutureResult<Response, T::Error>,
>;
type Future = EitherFuture<T::Future, Ready<Result<Response, T::Error>>>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
match self {
Some(t) => EitherFuture::A(t.respond_to(req).into_future()),
None => EitherFuture::B(ok(Response::build(StatusCode::NOT_FOUND).finish())),
Some(t) => EitherFuture::Left(t.respond_to(req)),
None => {
EitherFuture::Right(ok(Response::build(StatusCode::NOT_FOUND).finish()))
}
}
}
}
@ -104,23 +109,21 @@ where
{
type Error = Error;
type Future = EitherFuture<
ResponseFuture<<T::Future as IntoFuture>::Future>,
FutureResult<Response, Error>,
ResponseFuture<T::Future, T::Error>,
Ready<Result<Response, Error>>,
>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
match self {
Ok(val) => {
EitherFuture::A(ResponseFuture::new(val.respond_to(req).into_future()))
}
Err(e) => EitherFuture::B(err(e.into())),
Ok(val) => EitherFuture::Left(ResponseFuture::new(val.respond_to(req))),
Err(e) => EitherFuture::Right(err(e.into())),
}
}
}
impl Responder for ResponseBuilder {
type Error = Error;
type Future = FutureResult<Response, Error>;
type Future = Ready<Result<Response, Error>>;
#[inline]
fn respond_to(mut self, _: &HttpRequest) -> Self::Future {
@ -130,7 +133,7 @@ impl Responder for ResponseBuilder {
impl Responder for () {
type Error = Error;
type Future = FutureResult<Response, Error>;
type Future = Ready<Result<Response, Error>>;
fn respond_to(self, _: &HttpRequest) -> Self::Future {
ok(Response::build(StatusCode::OK).finish())
@ -146,7 +149,7 @@ where
fn respond_to(self, req: &HttpRequest) -> Self::Future {
CustomResponderFut {
fut: self.0.respond_to(req).into_future(),
fut: self.0.respond_to(req),
status: Some(self.1),
headers: None,
}
@ -155,7 +158,7 @@ where
impl Responder for &'static str {
type Error = Error;
type Future = FutureResult<Response, Error>;
type Future = Ready<Result<Response, Error>>;
fn respond_to(self, _: &HttpRequest) -> Self::Future {
ok(Response::build(StatusCode::OK)
@ -166,7 +169,7 @@ impl Responder for &'static str {
impl Responder for &'static [u8] {
type Error = Error;
type Future = FutureResult<Response, Error>;
type Future = Ready<Result<Response, Error>>;
fn respond_to(self, _: &HttpRequest) -> Self::Future {
ok(Response::build(StatusCode::OK)
@ -177,7 +180,7 @@ impl Responder for &'static [u8] {
impl Responder for String {
type Error = Error;
type Future = FutureResult<Response, Error>;
type Future = Ready<Result<Response, Error>>;
fn respond_to(self, _: &HttpRequest) -> Self::Future {
ok(Response::build(StatusCode::OK)
@ -188,7 +191,7 @@ impl Responder for String {
impl<'a> Responder for &'a String {
type Error = Error;
type Future = FutureResult<Response, Error>;
type Future = Ready<Result<Response, Error>>;
fn respond_to(self, _: &HttpRequest) -> Self::Future {
ok(Response::build(StatusCode::OK)
@ -199,7 +202,7 @@ impl<'a> Responder for &'a String {
impl Responder for Bytes {
type Error = Error;
type Future = FutureResult<Response, Error>;
type Future = Ready<Result<Response, Error>>;
fn respond_to(self, _: &HttpRequest) -> Self::Future {
ok(Response::build(StatusCode::OK)
@ -210,7 +213,7 @@ impl Responder for Bytes {
impl Responder for BytesMut {
type Error = Error;
type Future = FutureResult<Response, Error>;
type Future = Ready<Result<Response, Error>>;
fn respond_to(self, _: &HttpRequest) -> Self::Future {
ok(Response::build(StatusCode::OK)
@ -299,34 +302,40 @@ impl<T: Responder> Responder for CustomResponder<T> {
fn respond_to(self, req: &HttpRequest) -> Self::Future {
CustomResponderFut {
fut: self.responder.respond_to(req).into_future(),
fut: self.responder.respond_to(req),
status: self.status,
headers: self.headers,
}
}
}
#[pin_project]
pub struct CustomResponderFut<T: Responder> {
fut: <T::Future as IntoFuture>::Future,
#[pin]
fut: T::Future,
status: Option<StatusCode>,
headers: Option<HeaderMap>,
}
impl<T: Responder> Future for CustomResponderFut<T> {
type Item = Response;
type Error = T::Error;
type Output = Result<Response, T::Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut res = try_ready!(self.fut.poll());
if let Some(status) = self.status {
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let this = self.project();
let mut res = match ready!(this.fut.poll(cx)) {
Ok(res) => res,
Err(e) => return Poll::Ready(Err(e)),
};
if let Some(status) = this.status.take() {
*res.status_mut() = status;
}
if let Some(ref headers) = self.headers {
if let Some(ref headers) = this.headers {
for (k, v) in headers {
res.headers_mut().insert(k.clone(), v.clone());
}
}
Ok(Async::Ready(res))
Poll::Ready(Ok(res))
}
}
@ -336,8 +345,7 @@ impl<T: Responder> Future for CustomResponderFut<T> {
/// # use futures::future::{ok, Future};
/// use actix_web::{Either, Error, HttpResponse};
///
/// type RegisterResult =
/// Either<HttpResponse, Box<dyn Future<Item = HttpResponse, Error = Error>>>;
/// type RegisterResult = Either<HttpResponse, Result<HttpResponse, Error>>;
///
/// fn index() -> RegisterResult {
/// if is_a_variant() {
@ -346,9 +354,9 @@ impl<T: Responder> Future for CustomResponderFut<T> {
/// } else {
/// Either::B(
/// // <- Right variant
/// Box::new(ok(HttpResponse::Ok()
/// Ok(HttpResponse::Ok()
/// .content_type("text/html")
/// .body("Hello!")))
/// .body("Hello!"))
/// )
/// }
/// }
@ -369,97 +377,85 @@ where
B: Responder,
{
type Error = Error;
type Future = EitherResponder<
<A::Future as IntoFuture>::Future,
<B::Future as IntoFuture>::Future,
>;
type Future = EitherResponder<A, B>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
match self {
Either::A(a) => EitherResponder::A(a.respond_to(req).into_future()),
Either::B(b) => EitherResponder::B(b.respond_to(req).into_future()),
Either::A(a) => EitherResponder::A(a.respond_to(req)),
Either::B(b) => EitherResponder::B(b.respond_to(req)),
}
}
}
#[pin_project]
pub enum EitherResponder<A, B>
where
A: Future<Item = Response>,
A::Error: Into<Error>,
B: Future<Item = Response>,
B::Error: Into<Error>,
A: Responder,
B: Responder,
{
A(A),
B(B),
A(#[pin] A::Future),
B(#[pin] B::Future),
}
impl<A, B> Future for EitherResponder<A, B>
where
A: Future<Item = Response>,
A::Error: Into<Error>,
B: Future<Item = Response>,
B::Error: Into<Error>,
A: Responder,
B: Responder,
{
type Item = Response;
type Error = Error;
type Output = Result<Response, Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self {
EitherResponder::A(ref mut fut) => Ok(fut.poll().map_err(|e| e.into())?),
EitherResponder::B(ref mut fut) => Ok(fut.poll().map_err(|e| e.into())?),
#[project]
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
#[project]
match self.project() {
EitherResponder::A(fut) => {
Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into()))
}
EitherResponder::B(fut) => {
Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into())))
}
}
}
}
impl<I, E> Responder for Box<dyn Future<Item = I, Error = E>>
where
I: Responder + 'static,
E: Into<Error> + 'static,
{
type Error = Error;
type Future = Box<dyn Future<Item = Response, Error = Error>>;
#[inline]
fn respond_to(self, req: &HttpRequest) -> Self::Future {
let req = req.clone();
Box::new(
self.map_err(|e| e.into())
.and_then(move |r| ResponseFuture(r.respond_to(&req).into_future())),
)
}
}
impl<T> Responder for InternalError<T>
where
T: std::fmt::Debug + std::fmt::Display + 'static,
{
type Error = Error;
type Future = Result<Response, Error>;
type Future = Ready<Result<Response, Error>>;
fn respond_to(self, _: &HttpRequest) -> Self::Future {
let err: Error = self.into();
Ok(err.into())
ok(err.into())
}
}
pub struct ResponseFuture<T>(T);
#[pin_project]
pub struct ResponseFuture<T, E> {
#[pin]
fut: T,
_t: PhantomData<E>,
}
impl<T> ResponseFuture<T> {
impl<T, E> ResponseFuture<T, E> {
pub fn new(fut: T) -> Self {
ResponseFuture(fut)
ResponseFuture {
fut,
_t: PhantomData,
}
}
}
impl<T> Future for ResponseFuture<T>
impl<T, E> Future for ResponseFuture<T, E>
where
T: Future<Item = Response>,
T::Error: Into<Error>,
T: Future<Output = Result<Response, E>>,
E: Into<Error>,
{
type Item = Response;
type Error = Error;
type Output = Result<Response, Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
Ok(self.0.poll().map_err(|e| e.into())?)
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
Poll::Ready(ready!(self.project().fut.poll(cx)).map_err(|e| e.into()))
}
}
@ -476,26 +472,31 @@ pub(crate) mod tests {
#[test]
fn test_option_responder() {
let mut srv = init_service(
App::new()
.service(web::resource("/none").to(|| -> Option<&'static str> { None }))
.service(web::resource("/some").to(|| Some("some"))),
);
block_on(async {
let mut srv = init_service(
App::new()
.service(
web::resource("/none").to(|| -> Option<&'static str> { None }),
)
.service(web::resource("/some").to(|| Some("some"))),
)
.await;
let req = TestRequest::with_uri("/none").to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/none").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/some").to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
match resp.response().body() {
ResponseBody::Body(Body::Bytes(ref b)) => {
let bytes: Bytes = b.clone().into();
assert_eq!(bytes, Bytes::from_static(b"some"));
let req = TestRequest::with_uri("/some").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
match resp.response().body() {
ResponseBody::Body(Body::Bytes(ref b)) => {
let bytes: Bytes = b.clone().into();
assert_eq!(bytes, Bytes::from_static(b"some"));
}
_ => panic!(),
}
_ => panic!(),
}
})
}
pub(crate) trait BodyTest {
@ -526,142 +527,155 @@ pub(crate) mod tests {
#[test]
fn test_responder() {
let req = TestRequest::default().to_http_request();
block_on(async {
let req = TestRequest::default().to_http_request();
let resp: HttpResponse = block_on(().respond_to(&req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(*resp.body().body(), Body::Empty);
let resp: HttpResponse = ().respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(*resp.body().body(), Body::Empty);
let resp: HttpResponse = block_on("test".respond_to(&req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8")
);
let resp: HttpResponse = "test".respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8")
);
let resp: HttpResponse = block_on(b"test".respond_to(&req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/octet-stream")
);
let resp: HttpResponse = b"test".respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/octet-stream")
);
let resp: HttpResponse = block_on("test".to_string().respond_to(&req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8")
);
let resp: HttpResponse = "test".to_string().respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8")
);
let resp: HttpResponse =
block_on((&"test".to_string()).respond_to(&req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8")
);
let resp: HttpResponse =
(&"test".to_string()).respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8")
);
let resp: HttpResponse =
block_on(Bytes::from_static(b"test").respond_to(&req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/octet-stream")
);
let resp: HttpResponse =
Bytes::from_static(b"test").respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/octet-stream")
);
let resp: HttpResponse =
block_on(BytesMut::from(b"test".as_ref()).respond_to(&req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/octet-stream")
);
// InternalError
let resp: HttpResponse =
error::InternalError::new("err", StatusCode::BAD_REQUEST)
let resp: HttpResponse = BytesMut::from(b"test".as_ref())
.respond_to(&req)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/octet-stream")
);
// InternalError
let resp: HttpResponse =
error::InternalError::new("err", StatusCode::BAD_REQUEST)
.respond_to(&req)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
})
}
#[test]
fn test_result_responder() {
let req = TestRequest::default().to_http_request();
block_on(async {
let req = TestRequest::default().to_http_request();
// Result<I, E>
let resp: HttpResponse =
block_on(Ok::<_, Error>("test".to_string()).respond_to(&req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8")
);
// Result<I, E>
let resp: HttpResponse = Ok::<_, Error>("test".to_string())
.respond_to(&req)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8")
);
let res = block_on(
Err::<String, _>(error::InternalError::new("err", StatusCode::BAD_REQUEST))
.respond_to(&req),
);
assert!(res.is_err());
let res = Err::<String, _>(error::InternalError::new(
"err",
StatusCode::BAD_REQUEST,
))
.respond_to(&req)
.await;
assert!(res.is_err());
})
}
#[test]
fn test_custom_responder() {
let req = TestRequest::default().to_http_request();
let res = block_on(
"test"
block_on(async {
let req = TestRequest::default().to_http_request();
let res = "test"
.to_string()
.with_status(StatusCode::BAD_REQUEST)
.respond_to(&req),
)
.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(res.body().bin_ref(), b"test");
.respond_to(&req)
.await
.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(res.body().bin_ref(), b"test");
let res = block_on(
"test"
let res = "test"
.to_string()
.with_header("content-type", "json")
.respond_to(&req),
)
.unwrap();
.respond_to(&req)
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.body().bin_ref(), b"test");
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("json")
);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.body().bin_ref(), b"test");
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("json")
);
})
}
#[test]
fn test_tuple_responder_with_status_code() {
let req = TestRequest::default().to_http_request();
let res =
block_on(("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req))
block_on(async {
let req = TestRequest::default().to_http_request();
let res = ("test".to_string(), StatusCode::BAD_REQUEST)
.respond_to(&req)
.await
.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(res.body().bin_ref(), b"test");
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(res.body().bin_ref(), b"test");
let req = TestRequest::default().to_http_request();
let res = block_on(
("test".to_string(), StatusCode::OK)
let req = TestRequest::default().to_http_request();
let res = ("test".to_string(), StatusCode::OK)
.with_header("content-type", "json")
.respond_to(&req),
)
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.body().bin_ref(), b"test");
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("json")
);
.respond_to(&req)
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.body().bin_ref(), b"test");
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("json")
);
})
}
}

View file

@ -1,9 +1,11 @@
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_http::{http::Method, Error};
use actix_service::{NewService, Service};
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, IntoFuture, Poll};
use actix_service::{Service, ServiceFactory};
use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready};
use crate::extract::FromRequest;
use crate::guard::{self, Guard};
@ -17,22 +19,19 @@ type BoxedRouteService<Req, Res> = Box<
Request = Req,
Response = Res,
Error = Error,
Future = Either<
FutureResult<Res, Error>,
Box<dyn Future<Item = Res, Error = Error>>,
>,
Future = LocalBoxFuture<'static, Result<Res, Error>>,
>,
>;
type BoxedRouteNewService<Req, Res> = Box<
dyn NewService<
dyn ServiceFactory<
Config = (),
Request = Req,
Response = Res,
Error = Error,
InitError = (),
Service = BoxedRouteService<Req, Res>,
Future = Box<dyn Future<Item = BoxedRouteService<Req, Res>, Error = ()>>,
Future = LocalBoxFuture<'static, Result<BoxedRouteService<Req, Res>, ()>>,
>,
>;
@ -61,7 +60,7 @@ impl Route {
}
}
impl NewService for Route {
impl ServiceFactory for Route {
type Config = ();
type Request = ServiceRequest;
type Response = ServiceResponse;
@ -78,26 +77,30 @@ impl NewService for Route {
}
}
type RouteFuture = Box<
dyn Future<Item = BoxedRouteService<ServiceRequest, ServiceResponse>, Error = ()>,
type RouteFuture = LocalBoxFuture<
'static,
Result<BoxedRouteService<ServiceRequest, ServiceResponse>, ()>,
>;
#[pin_project::pin_project]
pub struct CreateRouteService {
#[pin]
fut: RouteFuture,
guards: Rc<Vec<Box<dyn Guard>>>,
}
impl Future for CreateRouteService {
type Item = RouteService;
type Error = ();
type Output = Result<RouteService, ()>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.fut.poll()? {
Async::Ready(service) => Ok(Async::Ready(RouteService {
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let this = self.project();
match this.fut.poll(cx)? {
Poll::Ready(service) => Poll::Ready(Ok(RouteService {
service,
guards: self.guards.clone(),
guards: this.guards.clone(),
})),
Async::NotReady => Ok(Async::NotReady),
Poll::Pending => Poll::Pending,
}
}
}
@ -122,17 +125,14 @@ impl Service for RouteService {
type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = Error;
type Future = Either<
FutureResult<Self::Response, Self::Error>,
Box<dyn Future<Item = Self::Response, Error = Self::Error>>,
>;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
self.service.call(req)
self.service.call(req).boxed_local()
}
}
@ -249,8 +249,8 @@ impl Route {
/// }
///
/// /// extract path info using serde
/// fn index(info: web::Path<Info>) -> impl Future<Item = &'static str, Error = Error> {
/// ok("Hello World!")
/// async fn index(info: web::Path<Info>) -> Result<&'static str, Error> {
/// Ok("Hello World!")
/// }
///
/// fn main() {
@ -261,13 +261,13 @@ impl Route {
/// }
/// ```
#[allow(clippy::wrong_self_convention)]
pub fn to_async<F, T, R>(mut self, handler: F) -> Self
pub fn to_async<F, T, R, O, E>(mut self, handler: F) -> Self
where
F: AsyncFactory<T, R>,
F: AsyncFactory<T, R, O, E>,
T: FromRequest + 'static,
R: IntoFuture + 'static,
R::Item: Responder,
R::Error: Into<Error>,
R: Future<Output = Result<O, E>> + 'static,
O: Responder + 'static,
E: Into<Error> + 'static,
{
self.service = Box::new(RouteNewService::new(Extract::new(AsyncHandler::new(
handler,
@ -278,14 +278,14 @@ impl Route {
struct RouteNewService<T>
where
T: NewService<Request = ServiceRequest, Error = (Error, ServiceRequest)>,
T: ServiceFactory<Request = ServiceRequest, Error = (Error, ServiceRequest)>,
{
service: T,
}
impl<T> RouteNewService<T>
where
T: NewService<
T: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse,
@ -300,9 +300,9 @@ where
}
}
impl<T> NewService for RouteNewService<T>
impl<T> ServiceFactory for RouteNewService<T>
where
T: NewService<
T: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse,
@ -318,19 +318,20 @@ where
type Error = Error;
type InitError = ();
type Service = BoxedRouteService<ServiceRequest, Self::Response>;
type Future = Box<dyn Future<Item = Self::Service, Error = Self::InitError>>;
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &()) -> Self::Future {
Box::new(
self.service
.new_service(&())
.map_err(|_| ())
.and_then(|service| {
self.service
.new_service(&())
.map(|result| match result {
Ok(service) => {
let service: BoxedRouteService<_, _> =
Box::new(RouteServiceWrapper { service });
Ok(service)
}),
)
}
Err(_) => Err(()),
})
.boxed_local()
}
}
@ -350,25 +351,30 @@ where
type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = Error;
type Future = Either<
FutureResult<Self::Response, Self::Error>,
Box<dyn Future<Item = Self::Response, Error = Self::Error>>,
>;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready().map_err(|(e, _)| e)
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx).map_err(|(e, _)| e)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
let mut fut = self.service.call(req);
match fut.poll() {
Ok(Async::Ready(res)) => Either::A(ok(res)),
Err((e, req)) => Either::A(ok(req.error_response(e))),
Ok(Async::NotReady) => Either::B(Box::new(fut.then(|res| match res {
// let mut fut = self.service.call(req);
self.service
.call(req)
.map(|res| match res {
Ok(res) => Ok(res),
Err((err, req)) => Ok(req.error_response(err)),
}))),
}
})
.boxed_local()
// match fut.poll() {
// Poll::Ready(Ok(res)) => Either::Left(ok(res)),
// Poll::Ready(Err((e, req))) => Either::Left(ok(req.error_response(e))),
// Poll::Pending => Either::Right(Box::new(fut.then(|res| match res {
// Ok(res) => Ok(res),
// Err((err, req)) => Ok(req.error_response(err)),
// }))),
// }
}
}
@ -379,11 +385,11 @@ mod tests {
use bytes::Bytes;
use futures::Future;
use serde_derive::Serialize;
use tokio_timer::sleep;
use tokio_timer::delay_for;
use crate::http::{Method, StatusCode};
use crate::test::{call_service, init_service, read_body, TestRequest};
use crate::{error, web, App, HttpResponse};
use crate::test::{block_on, call_service, init_service, read_body, TestRequest};
use crate::{error, web, App, Error, HttpResponse};
#[derive(Serialize, PartialEq, Debug)]
struct MyObject {
@ -392,68 +398,75 @@ mod tests {
#[test]
fn test_route() {
let mut srv = init_service(
App::new()
.service(
web::resource("/test")
.route(web::get().to(|| HttpResponse::Ok()))
.route(web::put().to(|| {
Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
}))
.route(web::post().to_async(|| {
sleep(Duration::from_millis(100))
.then(|_| HttpResponse::Created())
}))
.route(web::delete().to_async(|| {
sleep(Duration::from_millis(100)).then(|_| {
block_on(async {
let mut srv = init_service(
App::new()
.service(
web::resource("/test")
.route(web::get().to(|| HttpResponse::Ok()))
.route(web::put().to(|| {
Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
})
})),
)
.service(web::resource("/json").route(web::get().to_async(|| {
sleep(Duration::from_millis(25)).then(|_| {
Ok::<_, crate::Error>(web::Json(MyObject {
name: "test".to_string(),
}))
})
}))),
);
}))
.route(web::post().to_async(|| {
async {
delay_for(Duration::from_millis(100)).await;
Ok::<_, Error>(HttpResponse::Created())
}
}))
.route(web::delete().to_async(|| {
async {
delay_for(Duration::from_millis(100)).await;
Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
}
})),
)
.service(web::resource("/json").route(web::get().to_async(|| {
async {
delay_for(Duration::from_millis(25)).await;
Ok::<_, Error>(web::Json(MyObject {
name: "test".to_string(),
}))
}
}))),
)
.await;
let req = TestRequest::with_uri("/test")
.method(Method::GET)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test")
.method(Method::GET)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test")
.method(Method::POST)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test")
.method(Method::POST)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test")
.method(Method::PUT)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/test")
.method(Method::PUT)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/test")
.method(Method::DELETE)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/test")
.method(Method::DELETE)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/test")
.method(Method::HEAD)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let req = TestRequest::with_uri("/test")
.method(Method::HEAD)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let req = TestRequest::with_uri("/json").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/json").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let body = read_body(resp);
assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}"));
let body = read_body(resp).await;
assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}"));
})
}
}

File diff suppressed because it is too large Load diff

View file

@ -6,15 +6,15 @@ use actix_http::{body::MessageBody, Error, HttpService, KeepAlive, Request, Resp
use actix_rt::System;
use actix_server::{Server, ServerBuilder};
use actix_server_config::ServerConfig;
use actix_service::{IntoNewService, NewService};
use actix_service::{IntoServiceFactory, Service, ServiceFactory};
use parking_lot::Mutex;
use net2::TcpBuilder;
#[cfg(feature = "ssl")]
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder};
#[cfg(feature = "rust-tls")]
use rustls::ServerConfig as RustlsServerConfig;
#[cfg(feature = "openssl")]
use open_ssl::ssl::{SslAcceptor, SslAcceptorBuilder};
#[cfg(feature = "rustls")]
use rust_tls::ServerConfig as RustlsServerConfig;
struct Socket {
scheme: &'static str,
@ -51,12 +51,11 @@ struct Config {
pub struct HttpServer<F, I, S, B>
where
F: Fn() -> I + Send + Clone + 'static,
I: IntoNewService<S>,
S: NewService<Config = ServerConfig, Request = Request>,
I: IntoServiceFactory<S>,
S: ServiceFactory<Config = ServerConfig, Request = Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
S::Service: 'static,
B: MessageBody,
{
pub(super) factory: F,
@ -71,12 +70,12 @@ where
impl<F, I, S, B> HttpServer<F, I, S, B>
where
F: Fn() -> I + Send + Clone + 'static,
I: IntoNewService<S>,
S: NewService<Config = ServerConfig, Request = Request>,
S::Error: Into<Error>,
I: IntoServiceFactory<S>,
S: ServiceFactory<Config = ServerConfig, Request = Request>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
S::Service: 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
{
/// Create new http server with application factory
@ -254,11 +253,11 @@ where
Ok(self)
}
#[cfg(feature = "ssl")]
#[cfg(feature = "openssl")]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_ssl(
pub fn listen_openssl(
self,
lst: net::TcpListener,
builder: SslAcceptorBuilder,
@ -266,13 +265,14 @@ where
self.listen_ssl_inner(lst, openssl_acceptor(builder)?)
}
#[cfg(feature = "ssl")]
#[cfg(feature = "openssl")]
fn listen_ssl_inner(
mut self,
lst: net::TcpListener,
acceptor: SslAcceptor,
) -> io::Result<Self> {
use actix_server::ssl::{OpensslAcceptor, SslError};
use actix_service::pipeline_factory;
let acceptor = OpensslAcceptor::new(acceptor);
let factory = self.factory.clone();
@ -288,7 +288,7 @@ where
lst,
move || {
let c = cfg.lock();
acceptor.clone().map_err(SslError::Ssl).and_then(
pipeline_factory(acceptor.clone().map_err(SslError::Ssl)).and_then(
HttpService::build()
.keep_alive(c.keep_alive)
.client_timeout(c.client_timeout)
@ -302,7 +302,7 @@ where
Ok(self)
}
#[cfg(feature = "rust-tls")]
#[cfg(feature = "rustls")]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
@ -314,13 +314,14 @@ where
self.listen_rustls_inner(lst, config)
}
#[cfg(feature = "rust-tls")]
#[cfg(feature = "rustls")]
fn listen_rustls_inner(
mut self,
lst: net::TcpListener,
mut config: RustlsServerConfig,
) -> io::Result<Self> {
use actix_server::ssl::{RustlsAcceptor, SslError};
use actix_service::pipeline_factory;
let protos = vec!["h2".to_string().into(), "http/1.1".to_string().into()];
config.set_protocols(&protos);
@ -339,7 +340,7 @@ where
lst,
move || {
let c = cfg.lock();
acceptor.clone().map_err(SslError::Ssl).and_then(
pipeline_factory(acceptor.clone().map_err(SslError::Ssl)).and_then(
HttpService::build()
.keep_alive(c.keep_alive)
.client_timeout(c.client_timeout)
@ -397,11 +398,11 @@ where
}
}
#[cfg(feature = "ssl")]
#[cfg(feature = "openssl")]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_ssl<A>(
pub fn bind_openssl<A>(
mut self,
addr: A,
builder: SslAcceptorBuilder,
@ -419,7 +420,7 @@ where
Ok(self)
}
#[cfg(feature = "rust-tls")]
#[cfg(feature = "rustls")]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
@ -435,7 +436,7 @@ where
Ok(self)
}
#[cfg(feature = "uds")]
#[cfg(unix)]
/// Start listening for unix domain connections on existing listener.
///
/// This method is available with `uds` feature.
@ -466,7 +467,7 @@ where
Ok(self)
}
#[cfg(feature = "uds")]
#[cfg(unix)]
/// Start listening for incoming unix domain connections.
///
/// This method is available with `uds` feature.
@ -502,8 +503,8 @@ where
impl<F, I, S, B> HttpServer<F, I, S, B>
where
F: Fn() -> I + Send + Clone + 'static,
I: IntoNewService<S>,
S: NewService<Config = ServerConfig, Request = Request>,
I: IntoServiceFactory<S>,
S: ServiceFactory<Config = ServerConfig, Request = Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
@ -577,10 +578,10 @@ fn create_tcp_listener(
Ok(builder.listen(backlog)?)
}
#[cfg(feature = "ssl")]
#[cfg(feature = "openssl")]
/// Configure `SslAcceptorBuilder` with custom server flags.
fn openssl_acceptor(mut builder: SslAcceptorBuilder) -> io::Result<SslAcceptor> {
use openssl::ssl::AlpnError;
use open_ssl::ssl::AlpnError;
builder.set_alpn_select_callback(|_, protos| {
const H2: &[u8] = b"\x02h2";

View file

@ -9,8 +9,8 @@ use actix_http::{
ResponseHead,
};
use actix_router::{Path, Resource, ResourceDef, Url};
use actix_service::{IntoNewService, NewService};
use futures::future::{ok, FutureResult, IntoFuture};
use actix_service::{IntoServiceFactory, ServiceFactory};
use futures::future::{ok, Ready};
use crate::config::{AppConfig, AppService};
use crate::data::Data;
@ -24,7 +24,7 @@ pub trait HttpServiceFactory {
fn register(self, config: &mut AppService);
}
pub(crate) trait ServiceFactory {
pub(crate) trait AppServiceFactory {
fn register(&mut self, config: &mut AppService);
}
@ -40,7 +40,7 @@ impl<T> ServiceFactoryWrapper<T> {
}
}
impl<T> ServiceFactory for ServiceFactoryWrapper<T>
impl<T> AppServiceFactory for ServiceFactoryWrapper<T>
where
T: HttpServiceFactory,
{
@ -404,16 +404,6 @@ impl<B> Into<Response<B>> for ServiceResponse<B> {
}
}
impl<B> IntoFuture for ServiceResponse<B> {
type Item = ServiceResponse<B>;
type Error = Error;
type Future = FutureResult<ServiceResponse<B>, Error>;
fn into_future(self) -> Self::Future {
ok(self)
}
}
impl<B: MessageBody> fmt::Debug for ServiceResponse<B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = writeln!(
@ -459,10 +449,11 @@ impl WebService {
/// Add match guard to a web service.
///
/// ```rust
/// use actix_web::{web, guard, dev, App, HttpResponse};
/// use futures::future::{ok, Ready};
/// use actix_web::{web, guard, dev, App, Error, HttpResponse};
///
/// fn index(req: dev::ServiceRequest) -> dev::ServiceResponse {
/// req.into_response(HttpResponse::Ok().finish())
/// fn index(req: dev::ServiceRequest) -> Ready<Result<dev::ServiceResponse, Error>> {
/// ok(req.into_response(HttpResponse::Ok().finish()))
/// }
///
/// fn main() {
@ -482,8 +473,8 @@ impl WebService {
/// Set a service factory implementation and generate web service.
pub fn finish<T, F>(self, service: F) -> impl HttpServiceFactory
where
F: IntoNewService<T>,
T: NewService<
F: IntoServiceFactory<T>,
T: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse,
@ -492,7 +483,7 @@ impl WebService {
> + 'static,
{
WebServiceImpl {
srv: service.into_new_service(),
srv: service.into_factory(),
rdef: self.rdef,
name: self.name,
guards: self.guards,
@ -509,7 +500,7 @@ struct WebServiceImpl<T> {
impl<T> HttpServiceFactory for WebServiceImpl<T>
where
T: NewService<
T: ServiceFactory<
Config = (),
Request = ServiceRequest,
Response = ServiceResponse,
@ -539,8 +530,9 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::test::{call_service, init_service, TestRequest};
use crate::test::{block_on, init_service, TestRequest};
use crate::{guard, http, web, App, HttpResponse};
use actix_service::Service;
#[test]
fn test_service_request() {
@ -565,25 +557,33 @@ mod tests {
#[test]
fn test_service() {
let mut srv = init_service(
App::new().service(web::service("/test").name("test").finish(
|req: ServiceRequest| req.into_response(HttpResponse::Ok().finish()),
)),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), http::StatusCode::OK);
block_on(async {
let mut srv = init_service(
App::new().service(web::service("/test").name("test").finish(
|req: ServiceRequest| {
ok(req.into_response(HttpResponse::Ok().finish()))
},
)),
)
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), http::StatusCode::OK);
let mut srv = init_service(
App::new().service(web::service("/test").guard(guard::Get()).finish(
|req: ServiceRequest| req.into_response(HttpResponse::Ok().finish()),
)),
);
let req = TestRequest::with_uri("/test")
.method(http::Method::PUT)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), http::StatusCode::NOT_FOUND);
let mut srv = init_service(App::new().service(
web::service("/test").guard(guard::Get()).finish(
|req: ServiceRequest| {
ok(req.into_response(HttpResponse::Ok().finish()))
},
),
))
.await;
let req = TestRequest::with_uri("/test")
.method(http::Method::PUT)
.to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), http::StatusCode::NOT_FOUND);
})
}
#[test]

View file

@ -7,10 +7,10 @@ use actix_http::test::TestRequest as HttpTestRequest;
use actix_http::{cookie::Cookie, Extensions, Request};
use actix_router::{Path, ResourceDef, Url};
use actix_server_config::ServerConfig;
use actix_service::{IntoNewService, IntoService, NewService, Service};
use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory};
use bytes::{Bytes, BytesMut};
use futures::future::{ok, Future};
use futures::Stream;
use futures::future::{ok, Future, FutureExt};
use futures::stream::{Stream, StreamExt};
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json;
@ -39,7 +39,7 @@ pub fn default_service(
) -> impl Service<Request = ServiceRequest, Response = ServiceResponse<Body>, Error = Error>
{
(move |req: ServiceRequest| {
req.into_response(HttpResponse::build(status_code).finish())
ok(req.into_response(HttpResponse::build(status_code).finish()))
})
.into_service()
}
@ -66,12 +66,12 @@ pub fn default_service(
/// assert_eq!(resp.status(), StatusCode::OK);
/// }
/// ```
pub fn init_service<R, S, B, E>(
pub async fn init_service<R, S, B, E>(
app: R,
) -> impl Service<Request = Request, Response = ServiceResponse<B>, Error = E>
where
R: IntoNewService<S>,
S: NewService<
R: IntoServiceFactory<S>,
S: ServiceFactory<
Config = ServerConfig,
Request = Request,
Response = ServiceResponse<B>,
@ -80,9 +80,8 @@ where
S::InitError: std::fmt::Debug,
{
let cfg = ServerConfig::new("127.0.0.1:8080".parse().unwrap());
let srv = app.into_new_service();
let fut = run_on(move || srv.new_service(&cfg));
block_on(fut).unwrap()
let srv = app.into_factory();
srv.new_service(&cfg).await.unwrap()
}
/// Calls service and waits for response future completion.
@ -106,12 +105,12 @@ where
/// assert_eq!(resp.status(), StatusCode::OK);
/// }
/// ```
pub fn call_service<S, R, B, E>(app: &mut S, req: R) -> S::Response
pub async fn call_service<S, R, B, E>(app: &mut S, req: R) -> S::Response
where
S: Service<Request = R, Response = ServiceResponse<B>, Error = E>,
E: std::fmt::Debug,
{
block_on(run_on(move || app.call(req))).unwrap()
app.call(req).await.unwrap()
}
/// Helper function that returns a response body of a TestRequest
@ -138,22 +137,22 @@ where
/// assert_eq!(result, Bytes::from_static(b"welcome!"));
/// }
/// ```
pub fn read_response<S, B>(app: &mut S, req: Request) -> Bytes
pub async fn read_response<S, B>(app: &mut S, req: Request) -> Bytes
where
S: Service<Request = Request, Response = ServiceResponse<B>, Error = Error>,
B: MessageBody,
{
block_on(run_on(move || {
app.call(req).and_then(|mut resp: ServiceResponse<B>| {
resp.take_body()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, Error>(body)
})
.map(|body: BytesMut| body.freeze())
})
}))
.unwrap_or_else(|_| panic!("read_response failed at block_on unwrap"))
let mut resp = app
.call(req)
.await
.unwrap_or_else(|_| panic!("read_response failed at block_on unwrap"));
let mut body = resp.take_body();
let mut bytes = BytesMut::new();
while let Some(item) = body.next().await {
bytes.extend_from_slice(&item.unwrap());
}
bytes.freeze()
}
/// Helper function that returns a response body of a ServiceResponse.
@ -181,19 +180,27 @@ where
/// assert_eq!(result, Bytes::from_static(b"welcome!"));
/// }
/// ```
pub fn read_body<B>(mut res: ServiceResponse<B>) -> Bytes
pub async fn read_body<B>(mut res: ServiceResponse<B>) -> Bytes
where
B: MessageBody,
{
block_on(run_on(move || {
res.take_body()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, Error>(body)
})
.map(|body: BytesMut| body.freeze())
}))
.unwrap_or_else(|_| panic!("read_response failed at block_on unwrap"))
let mut body = res.take_body();
let mut bytes = BytesMut::new();
while let Some(item) = body.next().await {
bytes.extend_from_slice(&item.unwrap());
}
bytes.freeze()
}
pub async fn load_stream<S>(mut stream: S) -> Result<Bytes, Error>
where
S: Stream<Item = Result<Bytes, Error>> + Unpin,
{
let mut data = BytesMut::new();
while let Some(item) = stream.next().await {
data.extend_from_slice(&item?);
}
Ok(data.freeze())
}
/// Helper function that returns a deserialized response body of a TestRequest
@ -230,27 +237,16 @@ where
/// let result: Person = test::read_response_json(&mut app, req);
/// }
/// ```
pub fn read_response_json<S, B, T>(app: &mut S, req: Request) -> T
pub async fn read_response_json<S, B, T>(app: &mut S, req: Request) -> T
where
S: Service<Request = Request, Response = ServiceResponse<B>, Error = Error>,
B: MessageBody,
T: DeserializeOwned,
{
block_on(run_on(move || {
app.call(req).and_then(|mut resp: ServiceResponse<B>| {
resp.take_body()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, Error>(body)
})
.and_then(|body: BytesMut| {
ok(serde_json::from_slice(&body).unwrap_or_else(|_| {
panic!("read_response_json failed during deserialization")
}))
})
})
}))
.unwrap_or_else(|_| panic!("read_response_json failed at block_on unwrap"))
let body = read_response(app, req).await;
serde_json::from_slice(&body)
.unwrap_or_else(|_| panic!("read_response_json failed during deserialization"))
}
/// Test `Request` builder.
@ -511,74 +507,82 @@ mod tests {
#[test]
fn test_basics() {
let req = TestRequest::with_hdr(header::ContentType::json())
.version(Version::HTTP_2)
.set(header::Date(SystemTime::now().into()))
.param("test", "123")
.data(10u32)
.to_http_request();
assert!(req.headers().contains_key(header::CONTENT_TYPE));
assert!(req.headers().contains_key(header::DATE));
assert_eq!(&req.match_info()["test"], "123");
assert_eq!(req.version(), Version::HTTP_2);
let data = req.get_app_data::<u32>().unwrap();
assert!(req.get_app_data::<u64>().is_none());
assert_eq!(*data, 10);
assert_eq!(*data.get_ref(), 10);
block_on(async {
let req = TestRequest::with_hdr(header::ContentType::json())
.version(Version::HTTP_2)
.set(header::Date(SystemTime::now().into()))
.param("test", "123")
.data(10u32)
.to_http_request();
assert!(req.headers().contains_key(header::CONTENT_TYPE));
assert!(req.headers().contains_key(header::DATE));
assert_eq!(&req.match_info()["test"], "123");
assert_eq!(req.version(), Version::HTTP_2);
let data = req.get_app_data::<u32>().unwrap();
assert!(req.get_app_data::<u64>().is_none());
assert_eq!(*data, 10);
assert_eq!(*data.get_ref(), 10);
assert!(req.app_data::<u64>().is_none());
let data = req.app_data::<u32>().unwrap();
assert_eq!(*data, 10);
assert!(req.app_data::<u64>().is_none());
let data = req.app_data::<u32>().unwrap();
assert_eq!(*data, 10);
})
}
#[test]
fn test_request_methods() {
let mut app = init_service(
App::new().service(
web::resource("/index.html")
.route(web::put().to(|| HttpResponse::Ok().body("put!")))
.route(web::patch().to(|| HttpResponse::Ok().body("patch!")))
.route(web::delete().to(|| HttpResponse::Ok().body("delete!"))),
),
);
block_on(async {
let mut app = init_service(
App::new().service(
web::resource("/index.html")
.route(web::put().to(|| HttpResponse::Ok().body("put!")))
.route(web::patch().to(|| HttpResponse::Ok().body("patch!")))
.route(web::delete().to(|| HttpResponse::Ok().body("delete!"))),
),
)
.await;
let put_req = TestRequest::put()
.uri("/index.html")
.header(header::CONTENT_TYPE, "application/json")
.to_request();
let put_req = TestRequest::put()
.uri("/index.html")
.header(header::CONTENT_TYPE, "application/json")
.to_request();
let result = read_response(&mut app, put_req);
assert_eq!(result, Bytes::from_static(b"put!"));
let result = read_response(&mut app, put_req).await;
assert_eq!(result, Bytes::from_static(b"put!"));
let patch_req = TestRequest::patch()
.uri("/index.html")
.header(header::CONTENT_TYPE, "application/json")
.to_request();
let patch_req = TestRequest::patch()
.uri("/index.html")
.header(header::CONTENT_TYPE, "application/json")
.to_request();
let result = read_response(&mut app, patch_req);
assert_eq!(result, Bytes::from_static(b"patch!"));
let result = read_response(&mut app, patch_req).await;
assert_eq!(result, Bytes::from_static(b"patch!"));
let delete_req = TestRequest::delete().uri("/index.html").to_request();
let result = read_response(&mut app, delete_req);
assert_eq!(result, Bytes::from_static(b"delete!"));
let delete_req = TestRequest::delete().uri("/index.html").to_request();
let result = read_response(&mut app, delete_req).await;
assert_eq!(result, Bytes::from_static(b"delete!"));
})
}
#[test]
fn test_response() {
let mut app = init_service(
App::new().service(
web::resource("/index.html")
.route(web::post().to(|| HttpResponse::Ok().body("welcome!"))),
),
);
block_on(async {
let mut app = init_service(
App::new().service(
web::resource("/index.html")
.route(web::post().to(|| HttpResponse::Ok().body("welcome!"))),
),
)
.await;
let req = TestRequest::post()
.uri("/index.html")
.header(header::CONTENT_TYPE, "application/json")
.to_request();
let req = TestRequest::post()
.uri("/index.html")
.header(header::CONTENT_TYPE, "application/json")
.to_request();
let result = read_response(&mut app, req);
assert_eq!(result, Bytes::from_static(b"welcome!"));
let result = read_response(&mut app, req).await;
assert_eq!(result, Bytes::from_static(b"welcome!"));
})
}
#[derive(Serialize, Deserialize)]
@ -589,129 +593,147 @@ mod tests {
#[test]
fn test_response_json() {
let mut app = init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Json<Person>| {
HttpResponse::Ok().json(person.into_inner())
}),
)));
block_on(async {
let mut app =
init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Json<Person>| {
HttpResponse::Ok().json(person.into_inner())
}),
)))
.await;
let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
let req = TestRequest::post()
.uri("/people")
.header(header::CONTENT_TYPE, "application/json")
.set_payload(payload)
.to_request();
let req = TestRequest::post()
.uri("/people")
.header(header::CONTENT_TYPE, "application/json")
.set_payload(payload)
.to_request();
let result: Person = read_response_json(&mut app, req);
assert_eq!(&result.id, "12345");
let result: Person = read_response_json(&mut app, req).await;
assert_eq!(&result.id, "12345");
})
}
#[test]
fn test_request_response_form() {
let mut app = init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Form<Person>| {
HttpResponse::Ok().json(person.into_inner())
}),
)));
block_on(async {
let mut app =
init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Form<Person>| {
HttpResponse::Ok().json(person.into_inner())
}),
)))
.await;
let payload = Person {
id: "12345".to_string(),
name: "User name".to_string(),
};
let payload = Person {
id: "12345".to_string(),
name: "User name".to_string(),
};
let req = TestRequest::post()
.uri("/people")
.set_form(&payload)
.to_request();
let req = TestRequest::post()
.uri("/people")
.set_form(&payload)
.to_request();
assert_eq!(req.content_type(), "application/x-www-form-urlencoded");
assert_eq!(req.content_type(), "application/x-www-form-urlencoded");
let result: Person = read_response_json(&mut app, req);
assert_eq!(&result.id, "12345");
assert_eq!(&result.name, "User name");
let result: Person = read_response_json(&mut app, req).await;
assert_eq!(&result.id, "12345");
assert_eq!(&result.name, "User name");
})
}
#[test]
fn test_request_response_json() {
let mut app = init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Json<Person>| {
HttpResponse::Ok().json(person.into_inner())
}),
)));
block_on(async {
let mut app =
init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Json<Person>| {
HttpResponse::Ok().json(person.into_inner())
}),
)))
.await;
let payload = Person {
id: "12345".to_string(),
name: "User name".to_string(),
};
let payload = Person {
id: "12345".to_string(),
name: "User name".to_string(),
};
let req = TestRequest::post()
.uri("/people")
.set_json(&payload)
.to_request();
let req = TestRequest::post()
.uri("/people")
.set_json(&payload)
.to_request();
assert_eq!(req.content_type(), "application/json");
assert_eq!(req.content_type(), "application/json");
let result: Person = read_response_json(&mut app, req);
assert_eq!(&result.id, "12345");
assert_eq!(&result.name, "User name");
let result: Person = read_response_json(&mut app, req).await;
assert_eq!(&result.id, "12345");
assert_eq!(&result.name, "User name");
})
}
#[test]
fn test_async_with_block() {
fn async_with_block() -> impl Future<Item = HttpResponse, Error = Error> {
web::block(move || Some(4).ok_or("wrong")).then(|res| match res {
Ok(value) => HttpResponse::Ok()
.content_type("text/plain")
.body(format!("Async with block value: {}", value)),
Err(_) => panic!("Unexpected"),
})
}
block_on(async {
async fn async_with_block() -> Result<HttpResponse, Error> {
let res = web::block(move || Some(4usize).ok_or("wrong")).await;
let mut app = init_service(
App::new().service(web::resource("/index.html").to_async(async_with_block)),
);
let req = TestRequest::post().uri("/index.html").to_request();
let res = block_fn(|| app.call(req)).unwrap();
assert!(res.status().is_success());
}
#[test]
fn test_actor() {
use actix::Actor;
struct MyActor;
struct Num(usize);
impl actix::Message for Num {
type Result = usize;
}
impl actix::Actor for MyActor {
type Context = actix::Context<Self>;
}
impl actix::Handler<Num> for MyActor {
type Result = usize;
fn handle(&mut self, msg: Num, _: &mut Self::Context) -> Self::Result {
msg.0
match res? {
Ok(value) => Ok(HttpResponse::Ok()
.content_type("text/plain")
.body(format!("Async with block value: {}", value))),
Err(_) => panic!("Unexpected"),
}
}
}
let addr = run_on(|| MyActor.start());
let mut app = init_service(App::new().service(
web::resource("/index.html").to_async(move || {
addr.send(Num(1)).from_err().and_then(|res| {
if res == 1 {
HttpResponse::Ok()
} else {
HttpResponse::BadRequest()
}
})
}),
));
let mut app = init_service(
App::new()
.service(web::resource("/index.html").to_async(async_with_block)),
)
.await;
let req = TestRequest::post().uri("/index.html").to_request();
let res = block_fn(|| app.call(req)).unwrap();
assert!(res.status().is_success());
let req = TestRequest::post().uri("/index.html").to_request();
let res = app.call(req).await.unwrap();
assert!(res.status().is_success());
})
}
// #[test]
// fn test_actor() {
// use actix::Actor;
// struct MyActor;
// struct Num(usize);
// impl actix::Message for Num {
// type Result = usize;
// }
// impl actix::Actor for MyActor {
// type Context = actix::Context<Self>;
// }
// impl actix::Handler<Num> for MyActor {
// type Result = usize;
// fn handle(&mut self, msg: Num, _: &mut Self::Context) -> Self::Result {
// msg.0
// }
// }
// let addr = run_on(|| MyActor.start());
// let mut app = init_service(App::new().service(
// web::resource("/index.html").to_async(move || {
// addr.send(Num(1)).from_err().and_then(|res| {
// if res == 1 {
// HttpResponse::Ok()
// } else {
// HttpResponse::BadRequest()
// }
// })
// }),
// ));
// let req = TestRequest::post().uri("/index.html").to_request();
// let res = block_fn(|| app.call(req)).unwrap();
// assert!(res.status().is_success());
// }
}

View file

@ -1,12 +1,16 @@
//! Form extractor
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use std::{fmt, ops};
use actix_http::{Error, HttpMessage, Payload, Response};
use bytes::BytesMut;
use encoding_rs::{Encoding, UTF_8};
use futures::{Future, Poll, Stream};
use futures::future::{err, ok, FutureExt, LocalBoxFuture, Ready};
use futures::{Stream, StreamExt};
use serde::de::DeserializeOwned;
use serde::Serialize;
@ -110,7 +114,7 @@ where
{
type Config = FormConfig;
type Error = Error;
type Future = Box<dyn Future<Item = Self, Error = Error>>;
type Future = LocalBoxFuture<'static, Result<Self, Error>>;
#[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
@ -120,18 +124,19 @@ where
.map(|c| (c.limit, c.ehandler.clone()))
.unwrap_or((16384, None));
Box::new(
UrlEncoded::new(req, payload)
.limit(limit)
.map_err(move |e| {
UrlEncoded::new(req, payload)
.limit(limit)
.map(move |res| match res {
Err(e) => {
if let Some(err) = err {
(*err)(e, &req2)
Err((*err)(e, &req2))
} else {
e.into()
Err(e.into())
}
})
.map(Form),
)
}
Ok(item) => Ok(Form(item)),
})
.boxed_local()
}
}
@ -149,15 +154,15 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
impl<T: Serialize> Responder for Form<T> {
type Error = Error;
type Future = Result<Response, Error>;
type Future = Ready<Result<Response, Error>>;
fn respond_to(self, _: &HttpRequest) -> Self::Future {
let body = match serde_urlencoded::to_string(&self.0) {
Ok(body) => body,
Err(e) => return Err(e.into()),
Err(e) => return err(e.into()),
};
Ok(Response::build(StatusCode::OK)
ok(Response::build(StatusCode::OK)
.set(ContentType::form_url_encoded())
.body(body))
}
@ -240,7 +245,7 @@ pub struct UrlEncoded<U> {
length: Option<usize>,
encoding: &'static Encoding,
err: Option<UrlencodedError>,
fut: Option<Box<dyn Future<Item = U, Error = UrlencodedError>>>,
fut: Option<LocalBoxFuture<'static, Result<U, UrlencodedError>>>,
}
impl<U> UrlEncoded<U> {
@ -301,45 +306,45 @@ impl<U> Future for UrlEncoded<U>
where
U: DeserializeOwned + 'static,
{
type Item = U;
type Error = UrlencodedError;
type Output = Result<U, UrlencodedError>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
if let Some(ref mut fut) = self.fut {
return fut.poll();
return Pin::new(fut).poll(cx);
}
if let Some(err) = self.err.take() {
return Err(err);
return Poll::Ready(Err(err));
}
// payload size
let limit = self.limit;
if let Some(len) = self.length.take() {
if len > limit {
return Err(UrlencodedError::Overflow { size: len, limit });
return Poll::Ready(Err(UrlencodedError::Overflow { size: len, limit }));
}
}
// future
let encoding = self.encoding;
let fut = self
.stream
.take()
.unwrap()
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(UrlencodedError::Overflow {
size: body.len() + chunk.len(),
limit,
})
} else {
body.extend_from_slice(&chunk);
Ok(body)
let mut stream = self.stream.take().unwrap();
self.fut = Some(
async move {
let mut body = BytesMut::with_capacity(8192);
while let Some(item) = stream.next().await {
let chunk = item?;
if (body.len() + chunk.len()) > limit {
return Err(UrlencodedError::Overflow {
size: body.len() + chunk.len(),
limit,
});
} else {
body.extend_from_slice(&chunk);
}
}
})
.and_then(move |body| {
if encoding == UTF_8 {
serde_urlencoded::from_bytes::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
@ -351,9 +356,10 @@ where
serde_urlencoded::from_str::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
}
});
self.fut = Some(Box::new(fut));
self.poll()
}
.boxed_local(),
);
self.poll(cx)
}
}
@ -374,20 +380,24 @@ mod tests {
#[test]
fn test_form() {
let (req, mut pl) =
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
.to_http_parts();
block_on(async {
let (req, mut pl) = TestRequest::with_header(
CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.header(CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
.to_http_parts();
let Form(s) = block_on(Form::<Info>::from_request(&req, &mut pl)).unwrap();
assert_eq!(
s,
Info {
hello: "world".into(),
counter: 123
}
);
let Form(s) = Form::<Info>::from_request(&req, &mut pl).await.unwrap();
assert_eq!(
s,
Info {
hello: "world".into(),
counter: 123
}
);
})
}
fn eq(err: UrlencodedError, other: UrlencodedError) -> bool {
@ -410,81 +420,93 @@ mod tests {
#[test]
fn test_urlencoded_error() {
let (req, mut pl) =
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(CONTENT_LENGTH, "xxxx")
.to_http_parts();
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl));
assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength));
let (req, mut pl) =
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(CONTENT_LENGTH, "1000000")
.to_http_parts();
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl));
assert!(eq(
info.err().unwrap(),
UrlencodedError::Overflow { size: 0, limit: 0 }
));
let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain")
.header(CONTENT_LENGTH, "10")
block_on(async {
let (req, mut pl) = TestRequest::with_header(
CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.header(CONTENT_LENGTH, "xxxx")
.to_http_parts();
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl));
assert!(eq(info.err().unwrap(), UrlencodedError::ContentType));
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength));
let (req, mut pl) = TestRequest::with_header(
CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.header(CONTENT_LENGTH, "1000000")
.to_http_parts();
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
assert!(eq(
info.err().unwrap(),
UrlencodedError::Overflow { size: 0, limit: 0 }
));
let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain")
.header(CONTENT_LENGTH, "10")
.to_http_parts();
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
assert!(eq(info.err().unwrap(), UrlencodedError::ContentType));
})
}
#[test]
fn test_urlencoded() {
let (req, mut pl) =
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
.to_http_parts();
block_on(async {
let (req, mut pl) = TestRequest::with_header(
CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.header(CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
.to_http_parts();
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl)).unwrap();
assert_eq!(
info,
Info {
hello: "world".to_owned(),
counter: 123
}
);
let info = UrlEncoded::<Info>::new(&req, &mut pl).await.unwrap();
assert_eq!(
info,
Info {
hello: "world".to_owned(),
counter: 123
}
);
let (req, mut pl) = TestRequest::with_header(
CONTENT_TYPE,
"application/x-www-form-urlencoded; charset=utf-8",
)
.header(CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
.to_http_parts();
let (req, mut pl) = TestRequest::with_header(
CONTENT_TYPE,
"application/x-www-form-urlencoded; charset=utf-8",
)
.header(CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
.to_http_parts();
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl)).unwrap();
assert_eq!(
info,
Info {
hello: "world".to_owned(),
counter: 123
}
);
let info = UrlEncoded::<Info>::new(&req, &mut pl).await.unwrap();
assert_eq!(
info,
Info {
hello: "world".to_owned(),
counter: 123
}
);
})
}
#[test]
fn test_responder() {
let req = TestRequest::default().to_http_request();
block_on(async {
let req = TestRequest::default().to_http_request();
let form = Form(Info {
hello: "world".to_string(),
counter: 123,
});
let resp = form.respond_to(&req).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/x-www-form-urlencoded")
);
let form = Form(Info {
hello: "world".to_string(),
counter: 123,
});
let resp = form.respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/x-www-form-urlencoded")
);
use crate::responder::tests::BodyTest;
assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123");
use crate::responder::tests::BodyTest;
assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123");
})
}
}

View file

@ -1,10 +1,14 @@
//! Json extractor/responder
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::{fmt, ops};
use bytes::BytesMut;
use futures::{Future, Poll, Stream};
use futures::future::{err, ok, FutureExt, LocalBoxFuture, Ready};
use futures::{Stream, StreamExt};
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json;
@ -118,15 +122,15 @@ where
impl<T: Serialize> Responder for Json<T> {
type Error = Error;
type Future = Result<Response, Error>;
type Future = Ready<Result<Response, Error>>;
fn respond_to(self, _: &HttpRequest) -> Self::Future {
let body = match serde_json::to_string(&self.0) {
Ok(body) => body,
Err(e) => return Err(e.into()),
Err(e) => return err(e.into()),
};
Ok(Response::build(StatusCode::OK)
ok(Response::build(StatusCode::OK)
.content_type("application/json")
.body(body))
}
@ -169,7 +173,7 @@ where
T: DeserializeOwned + 'static,
{
type Error = Error;
type Future = Box<dyn Future<Item = Self, Error = Error>>;
type Future = LocalBoxFuture<'static, Result<Self, Error>>;
type Config = JsonConfig;
#[inline]
@ -180,23 +184,24 @@ where
.map(|c| (c.limit, c.ehandler.clone(), c.content_type.clone()))
.unwrap_or((32768, None, None));
Box::new(
JsonBody::new(req, payload, ctype)
.limit(limit)
.map_err(move |e| {
JsonBody::new(req, payload, ctype)
.limit(limit)
.map(move |res| match res {
Err(e) => {
log::debug!(
"Failed to deserialize Json from payload. \
Request path: {}",
req2.path()
);
if let Some(err) = err {
(*err)(e, &req2)
Err((*err)(e, &req2))
} else {
e.into()
Err(e.into())
}
})
.map(Json),
)
}
Ok(data) => Ok(Json(data)),
})
.boxed_local()
}
}
@ -290,7 +295,7 @@ pub struct JsonBody<U> {
length: Option<usize>,
stream: Option<Decompress<Payload>>,
err: Option<JsonPayloadError>,
fut: Option<Box<dyn Future<Item = U, Error = JsonPayloadError>>>,
fut: Option<LocalBoxFuture<'static, Result<U, JsonPayloadError>>>,
}
impl<U> JsonBody<U>
@ -349,41 +354,43 @@ impl<U> Future for JsonBody<U>
where
U: DeserializeOwned + 'static,
{
type Item = U;
type Error = JsonPayloadError;
type Output = Result<U, JsonPayloadError>;
fn poll(&mut self) -> Poll<U, JsonPayloadError> {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
if let Some(ref mut fut) = self.fut {
return fut.poll();
return Pin::new(fut).poll(cx);
}
if let Some(err) = self.err.take() {
return Err(err);
return Poll::Ready(Err(err));
}
let limit = self.limit;
if let Some(len) = self.length.take() {
if len > limit {
return Err(JsonPayloadError::Overflow);
return Poll::Ready(Err(JsonPayloadError::Overflow));
}
}
let mut stream = self.stream.take().unwrap();
let fut = self
.stream
.take()
.unwrap()
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
self.fut = Some(
async move {
let mut body = BytesMut::with_capacity(8192);
while let Some(item) = stream.next().await {
let chunk = item?;
if (body.len() + chunk.len()) > limit {
return Err(JsonPayloadError::Overflow);
} else {
body.extend_from_slice(&chunk);
}
}
})
.and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
self.fut = Some(Box::new(fut));
self.poll()
Ok(serde_json::from_slice::<U>(&body)?)
}
.boxed_local(),
);
self.poll(cx)
}
}
@ -395,7 +402,7 @@ mod tests {
use super::*;
use crate::error::InternalError;
use crate::http::header;
use crate::test::{block_on, TestRequest};
use crate::test::{block_on, load_stream, TestRequest};
use crate::HttpResponse;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
@ -419,218 +426,234 @@ mod tests {
#[test]
fn test_responder() {
let req = TestRequest::default().to_http_request();
block_on(async {
let req = TestRequest::default().to_http_request();
let j = Json(MyObject {
name: "test".to_string(),
});
let resp = j.respond_to(&req).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
header::HeaderValue::from_static("application/json")
);
let j = Json(MyObject {
name: "test".to_string(),
});
let resp = j.respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
header::HeaderValue::from_static("application/json")
);
use crate::responder::tests::BodyTest;
assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}");
use crate::responder::tests::BodyTest;
assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}");
})
}
#[test]
fn test_custom_error_responder() {
let (req, mut pl) = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.data(JsonConfig::default().limit(10).error_handler(|err, _| {
let msg = MyObject {
name: "invalid request".to_string(),
};
let resp = HttpResponse::BadRequest()
.body(serde_json::to_string(&msg).unwrap());
InternalError::from_response(err, resp).into()
}))
.to_http_parts();
block_on(async {
let (req, mut pl) = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.data(JsonConfig::default().limit(10).error_handler(|err, _| {
let msg = MyObject {
name: "invalid request".to_string(),
};
let resp = HttpResponse::BadRequest()
.body(serde_json::to_string(&msg).unwrap());
InternalError::from_response(err, resp).into()
}))
.to_http_parts();
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
let mut resp = Response::from_error(s.err().unwrap().into());
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
let mut resp = Response::from_error(s.err().unwrap().into());
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let body = block_on(resp.take_body().concat2()).unwrap();
let msg: MyObject = serde_json::from_slice(&body).unwrap();
assert_eq!(msg.name, "invalid request");
let body = load_stream(resp.take_body()).await.unwrap();
let msg: MyObject = serde_json::from_slice(&body).unwrap();
assert_eq!(msg.name, "invalid request");
})
}
#[test]
fn test_extract() {
let (req, mut pl) = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.to_http_parts();
block_on(async {
let (req, mut pl) = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.to_http_parts();
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl)).unwrap();
assert_eq!(s.name, "test");
assert_eq!(
s.into_inner(),
MyObject {
name: "test".to_string()
}
);
let s = Json::<MyObject>::from_request(&req, &mut pl).await.unwrap();
assert_eq!(s.name, "test");
assert_eq!(
s.into_inner(),
MyObject {
name: "test".to_string()
}
);
let (req, mut pl) = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.data(JsonConfig::default().limit(10))
.to_http_parts();
let (req, mut pl) = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.data(JsonConfig::default().limit(10))
.to_http_parts();
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
assert!(format!("{}", s.err().unwrap())
.contains("Json payload size is bigger than allowed"));
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
assert!(format!("{}", s.err().unwrap())
.contains("Json payload size is bigger than allowed"));
let (req, mut pl) = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.data(
JsonConfig::default()
.limit(10)
.error_handler(|_, _| JsonPayloadError::ContentType.into()),
)
.to_http_parts();
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
assert!(format!("{}", s.err().unwrap()).contains("Content type error"));
let (req, mut pl) = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.data(
JsonConfig::default()
.limit(10)
.error_handler(|_, _| JsonPayloadError::ContentType.into()),
)
.to_http_parts();
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
assert!(format!("{}", s.err().unwrap()).contains("Content type error"));
})
}
#[test]
fn test_json_body() {
let (req, mut pl) = TestRequest::default().to_http_parts();
let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl, None));
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
block_on(async {
let (req, mut pl) = TestRequest::default().to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await;
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let (req, mut pl) = TestRequest::default()
.header(
let (req, mut pl) = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"),
)
.to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await;
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let (req, mut pl) = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000"),
)
.to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None)
.limit(100)
.await;
assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow));
let (req, mut pl) = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await;
assert_eq!(
json.ok().unwrap(),
MyObject {
name: "test".to_owned()
}
);
})
}
#[test]
fn test_with_json_and_bad_content_type() {
block_on(async {
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"),
)
.to_http_parts();
let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl, None));
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let (req, mut pl) = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000"),
)
.to_http_parts();
let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl, None).limit(100));
assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow));
let (req, mut pl) = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
header::HeaderValue::from_static("text/plain"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.data(JsonConfig::default().limit(4096))
.to_http_parts();
let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl, None));
assert_eq!(
json.ok().unwrap(),
MyObject {
name: "test".to_owned()
}
);
}
#[test]
fn test_with_json_and_bad_content_type() {
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.data(JsonConfig::default().limit(4096))
.to_http_parts();
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
assert!(s.is_err())
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
assert!(s.is_err())
})
}
#[test]
fn test_with_json_and_good_custom_content_type() {
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.data(JsonConfig::default().content_type(|mime: mime::Mime| {
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
}))
.to_http_parts();
block_on(async {
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.data(JsonConfig::default().content_type(|mime: mime::Mime| {
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
}))
.to_http_parts();
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
assert!(s.is_ok())
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
assert!(s.is_ok())
})
}
#[test]
fn test_with_json_and_bad_custom_content_type() {
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/html"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.data(JsonConfig::default().content_type(|mime: mime::Mime| {
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
}))
.to_http_parts();
block_on(async {
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/html"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.data(JsonConfig::default().content_type(|mime: mime::Mime| {
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
}))
.to_http_parts();
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
assert!(s.is_err())
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
assert!(s.is_err())
})
}
}

View file

@ -5,6 +5,7 @@ use std::{fmt, ops};
use actix_http::error::{Error, ErrorNotFound};
use actix_router::PathDeserializer;
use futures::future::{ready, Ready};
use serde::de;
use crate::dev::Payload;
@ -159,7 +160,7 @@ where
T: de::DeserializeOwned,
{
type Error = Error;
type Future = Result<Self, Error>;
type Future = Ready<Result<Self, Error>>;
type Config = PathConfig;
#[inline]
@ -169,21 +170,23 @@ where
.map(|c| c.ehandler.clone())
.unwrap_or(None);
de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
.map(|inner| Path { inner })
.map_err(move |e| {
log::debug!(
"Failed during Path extractor deserialization. \
Request path: {:?}",
req.path()
);
if let Some(error_handler) = error_handler {
let e = PathError::Deserialize(e);
(error_handler)(e, req)
} else {
ErrorNotFound(e)
}
})
ready(
de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
.map(|inner| Path { inner })
.map_err(move |e| {
log::debug!(
"Failed during Path extractor deserialization. \
Request path: {:?}",
req.path()
);
if let Some(error_handler) = error_handler {
let e = PathError::Deserialize(e);
(error_handler)(e, req)
} else {
ErrorNotFound(e)
}
}),
)
}
}
@ -268,100 +271,116 @@ mod tests {
#[test]
fn test_extract_path_single() {
let resource = ResourceDef::new("/{value}/");
block_on(async {
let resource = ResourceDef::new("/{value}/");
let mut req = TestRequest::with_uri("/32/").to_srv_request();
resource.match_path(req.match_info_mut());
let mut req = TestRequest::with_uri("/32/").to_srv_request();
resource.match_path(req.match_info_mut());
let (req, mut pl) = req.into_parts();
assert_eq!(*Path::<i8>::from_request(&req, &mut pl).unwrap(), 32);
assert!(Path::<MyStruct>::from_request(&req, &mut pl).is_err());
let (req, mut pl) = req.into_parts();
assert_eq!(*Path::<i8>::from_request(&req, &mut pl).await.unwrap(), 32);
assert!(Path::<MyStruct>::from_request(&req, &mut pl).await.is_err());
})
}
#[test]
fn test_tuple_extract() {
let resource = ResourceDef::new("/{key}/{value}/");
block_on(async {
let resource = ResourceDef::new("/{key}/{value}/");
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
resource.match_path(req.match_info_mut());
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
resource.match_path(req.match_info_mut());
let (req, mut pl) = req.into_parts();
let res =
block_on(<(Path<(String, String)>,)>::from_request(&req, &mut pl)).unwrap();
assert_eq!((res.0).0, "name");
assert_eq!((res.0).1, "user1");
let (req, mut pl) = req.into_parts();
let res = <(Path<(String, String)>,)>::from_request(&req, &mut pl)
.await
.unwrap();
assert_eq!((res.0).0, "name");
assert_eq!((res.0).1, "user1");
let res = block_on(
<(Path<(String, String)>, Path<(String, String)>)>::from_request(
let res = <(Path<(String, String)>, Path<(String, String)>)>::from_request(
&req, &mut pl,
),
)
.unwrap();
assert_eq!((res.0).0, "name");
assert_eq!((res.0).1, "user1");
assert_eq!((res.1).0, "name");
assert_eq!((res.1).1, "user1");
)
.await
.unwrap();
assert_eq!((res.0).0, "name");
assert_eq!((res.0).1, "user1");
assert_eq!((res.1).0, "name");
assert_eq!((res.1).1, "user1");
let () = <()>::from_request(&req, &mut pl).unwrap();
let () = <()>::from_request(&req, &mut pl).await.unwrap();
})
}
#[test]
fn test_request_extract() {
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
block_on(async {
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
let resource = ResourceDef::new("/{key}/{value}/");
resource.match_path(req.match_info_mut());
let resource = ResourceDef::new("/{key}/{value}/");
resource.match_path(req.match_info_mut());
let (req, mut pl) = req.into_parts();
let mut s = Path::<MyStruct>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.key, "name");
assert_eq!(s.value, "user1");
s.value = "user2".to_string();
assert_eq!(s.value, "user2");
assert_eq!(
format!("{}, {:?}", s, s),
"MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }"
);
let s = s.into_inner();
assert_eq!(s.value, "user2");
let (req, mut pl) = req.into_parts();
let mut s = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap();
assert_eq!(s.key, "name");
assert_eq!(s.value, "user1");
s.value = "user2".to_string();
assert_eq!(s.value, "user2");
assert_eq!(
format!("{}, {:?}", s, s),
"MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }"
);
let s = s.into_inner();
assert_eq!(s.value, "user2");
let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, "user1");
let s = Path::<(String, String)>::from_request(&req, &mut pl)
.await
.unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, "user1");
let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
let resource = ResourceDef::new("/{key}/{value}/");
resource.match_path(req.match_info_mut());
let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
let resource = ResourceDef::new("/{key}/{value}/");
resource.match_path(req.match_info_mut());
let (req, mut pl) = req.into_parts();
let s = Path::<Test2>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.as_ref().key, "name");
assert_eq!(s.value, 32);
let (req, mut pl) = req.into_parts();
let s = Path::<Test2>::from_request(&req, &mut pl).await.unwrap();
assert_eq!(s.as_ref().key, "name");
assert_eq!(s.value, 32);
let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, 32);
let s = Path::<(String, u8)>::from_request(&req, &mut pl)
.await
.unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, 32);
let res = Path::<Vec<String>>::from_request(&req, &mut pl).unwrap();
assert_eq!(res[0], "name".to_owned());
assert_eq!(res[1], "32".to_owned());
let res = Path::<Vec<String>>::from_request(&req, &mut pl)
.await
.unwrap();
assert_eq!(res[0], "name".to_owned());
assert_eq!(res[1], "32".to_owned());
})
}
#[test]
fn test_custom_err_handler() {
let (req, mut pl) = TestRequest::with_uri("/name/user1/")
.data(PathConfig::default().error_handler(|err, _| {
error::InternalError::from_response(
err,
HttpResponse::Conflict().finish(),
)
.into()
}))
.to_http_parts();
block_on(async {
let (req, mut pl) = TestRequest::with_uri("/name/user1/")
.data(PathConfig::default().error_handler(|err, _| {
error::InternalError::from_response(
err,
HttpResponse::Conflict().finish(),
)
.into()
}))
.to_http_parts();
let s = block_on(Path::<(usize,)>::from_request(&req, &mut pl)).unwrap_err();
let res: HttpResponse = s.into();
let s = Path::<(usize,)>::from_request(&req, &mut pl)
.await
.unwrap_err();
let res: HttpResponse = s.into();
assert_eq!(res.status(), http::StatusCode::CONFLICT);
assert_eq!(res.status(), http::StatusCode::CONFLICT);
})
}
}

View file

@ -1,12 +1,15 @@
//! Payload/Bytes/String extractors
use std::future::Future;
use std::pin::Pin;
use std::str;
use std::task::{Context, Poll};
use actix_http::error::{Error, ErrorBadRequest, PayloadError};
use actix_http::HttpMessage;
use bytes::{Bytes, BytesMut};
use encoding_rs::UTF_8;
use futures::future::{err, Either, FutureResult};
use futures::{Future, Poll, Stream};
use futures::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready};
use futures::{Stream, StreamExt};
use mime::Mime;
use crate::dev;
@ -19,21 +22,19 @@ use crate::request::HttpRequest;
/// ## Example
///
/// ```rust
/// use futures::{Future, Stream};
/// use futures::{Future, Stream, StreamExt};
/// use actix_web::{web, error, App, Error, HttpResponse};
///
/// /// extract binary data from request
/// fn index(body: web::Payload) -> impl Future<Item = HttpResponse, Error = Error>
/// async fn index(mut body: web::Payload) -> Result<HttpResponse, Error>
/// {
/// body.map_err(Error::from)
/// .fold(web::BytesMut::new(), move |mut body, chunk| {
/// body.extend_from_slice(&chunk);
/// Ok::<_, Error>(body)
/// })
/// .and_then(|body| {
/// format!("Body {:?}!", body);
/// Ok(HttpResponse::Ok().finish())
/// })
/// let mut bytes = web::BytesMut::new();
/// while let Some(item) = body.next().await {
/// bytes.extend_from_slice(&item?);
/// }
///
/// format!("Body {:?}!", bytes);
/// Ok(HttpResponse::Ok().finish())
/// }
///
/// fn main() {
@ -53,12 +54,14 @@ impl Payload {
}
impl Stream for Payload {
type Item = Bytes;
type Error = PayloadError;
type Item = Result<Bytes, PayloadError>;
#[inline]
fn poll(&mut self) -> Poll<Option<Self::Item>, PayloadError> {
self.0.poll()
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.0).poll_next(cx)
}
}
@ -67,21 +70,19 @@ impl Stream for Payload {
/// ## Example
///
/// ```rust
/// use futures::{Future, Stream};
/// use futures::{Future, Stream, StreamExt};
/// use actix_web::{web, error, App, Error, HttpResponse};
///
/// /// extract binary data from request
/// fn index(body: web::Payload) -> impl Future<Item = HttpResponse, Error = Error>
/// async fn index(mut body: web::Payload) -> Result<HttpResponse, Error>
/// {
/// body.map_err(Error::from)
/// .fold(web::BytesMut::new(), move |mut body, chunk| {
/// body.extend_from_slice(&chunk);
/// Ok::<_, Error>(body)
/// })
/// .and_then(|body| {
/// format!("Body {:?}!", body);
/// Ok(HttpResponse::Ok().finish())
/// })
/// let mut bytes = web::BytesMut::new();
/// while let Some(item) = body.next().await {
/// bytes.extend_from_slice(&item?);
/// }
///
/// format!("Body {:?}!", bytes);
/// Ok(HttpResponse::Ok().finish())
/// }
///
/// fn main() {
@ -94,11 +95,11 @@ impl Stream for Payload {
impl FromRequest for Payload {
type Config = PayloadConfig;
type Error = Error;
type Future = Result<Payload, Error>;
type Future = Ready<Result<Payload, Error>>;
#[inline]
fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
Ok(Payload(payload.take()))
ok(Payload(payload.take()))
}
}
@ -130,8 +131,10 @@ impl FromRequest for Payload {
impl FromRequest for Bytes {
type Config = PayloadConfig;
type Error = Error;
type Future =
Either<Box<dyn Future<Item = Bytes, Error = Error>>, FutureResult<Bytes, Error>>;
type Future = Either<
LocalBoxFuture<'static, Result<Bytes, Error>>,
Ready<Result<Bytes, Error>>,
>;
#[inline]
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
@ -144,13 +147,12 @@ impl FromRequest for Bytes {
};
if let Err(e) = cfg.check_mimetype(req) {
return Either::B(err(e));
return Either::Right(err(e));
}
let limit = cfg.limit;
Either::A(Box::new(
HttpMessageBody::new(req, payload).limit(limit).from_err(),
))
let fut = HttpMessageBody::new(req, payload).limit(limit);
Either::Left(async move { Ok(fut.await?) }.boxed_local())
}
}
@ -185,8 +187,8 @@ impl FromRequest for String {
type Config = PayloadConfig;
type Error = Error;
type Future = Either<
Box<dyn Future<Item = String, Error = Error>>,
FutureResult<String, Error>,
LocalBoxFuture<'static, Result<String, Error>>,
Ready<Result<String, Error>>,
>;
#[inline]
@ -201,33 +203,34 @@ impl FromRequest for String {
// check content-type
if let Err(e) = cfg.check_mimetype(req) {
return Either::B(err(e));
return Either::Right(err(e));
}
// check charset
let encoding = match req.encoding() {
Ok(enc) => enc,
Err(e) => return Either::B(err(e.into())),
Err(e) => return Either::Right(err(e.into())),
};
let limit = cfg.limit;
let fut = HttpMessageBody::new(req, payload).limit(limit);
Either::A(Box::new(
HttpMessageBody::new(req, payload)
.limit(limit)
.from_err()
.and_then(move |body| {
if encoding == UTF_8 {
Ok(str::from_utf8(body.as_ref())
.map_err(|_| ErrorBadRequest("Can not decode body"))?
.to_owned())
} else {
Ok(encoding
.decode_without_bom_handling_and_without_replacement(&body)
.map(|s| s.into_owned())
.ok_or_else(|| ErrorBadRequest("Can not decode body"))?)
}
}),
))
Either::Left(
async move {
let body = fut.await?;
if encoding == UTF_8 {
Ok(str::from_utf8(body.as_ref())
.map_err(|_| ErrorBadRequest("Can not decode body"))?
.to_owned())
} else {
Ok(encoding
.decode_without_bom_handling_and_without_replacement(&body)
.map(|s| s.into_owned())
.ok_or_else(|| ErrorBadRequest("Can not decode body"))?)
}
}
.boxed_local(),
)
}
}
/// Payload configuration for request's payload.
@ -300,7 +303,7 @@ pub struct HttpMessageBody {
length: Option<usize>,
stream: Option<dev::Decompress<dev::Payload>>,
err: Option<PayloadError>,
fut: Option<Box<dyn Future<Item = Bytes, Error = PayloadError>>>,
fut: Option<LocalBoxFuture<'static, Result<Bytes, PayloadError>>>,
}
impl HttpMessageBody {
@ -346,42 +349,43 @@ impl HttpMessageBody {
}
impl Future for HttpMessageBody {
type Item = Bytes;
type Error = PayloadError;
type Output = Result<Bytes, PayloadError>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
if let Some(ref mut fut) = self.fut {
return fut.poll();
return Pin::new(fut).poll(cx);
}
if let Some(err) = self.err.take() {
return Err(err);
return Poll::Ready(Err(err));
}
if let Some(len) = self.length.take() {
if len > self.limit {
return Err(PayloadError::Overflow);
return Poll::Ready(Err(PayloadError::Overflow));
}
}
// future
let limit = self.limit;
self.fut = Some(Box::new(
self.stream
.take()
.unwrap()
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(PayloadError::Overflow)
let mut stream = self.stream.take().unwrap();
self.fut = Some(
async move {
let mut body = BytesMut::with_capacity(8192);
while let Some(item) = stream.next().await {
let chunk = item?;
if body.len() + chunk.len() > limit {
return Err(PayloadError::Overflow);
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.map(|body| body.freeze()),
));
self.poll()
}
Ok(body.freeze())
}
.boxed_local(),
);
self.poll(cx)
}
}

View file

@ -4,6 +4,7 @@ use std::sync::Arc;
use std::{fmt, ops};
use actix_http::error::Error;
use futures::future::{err, ok, Ready};
use serde::de;
use serde_urlencoded;
@ -132,7 +133,7 @@ where
T: de::DeserializeOwned,
{
type Error = Error;
type Future = Result<Self, Error>;
type Future = Ready<Result<Self, Error>>;
type Config = QueryConfig;
#[inline]
@ -143,7 +144,7 @@ where
.unwrap_or(None);
serde_urlencoded::from_str::<T>(req.query_string())
.map(|val| Ok(Query(val)))
.map(|val| ok(Query(val)))
.unwrap_or_else(move |e| {
let e = QueryPayloadError::Deserialize(e);
@ -159,7 +160,7 @@ where
e.into()
};
Err(e)
err(e)
})
}
}
@ -227,7 +228,7 @@ mod tests {
use super::*;
use crate::error::InternalError;
use crate::test::TestRequest;
use crate::test::{block_on, TestRequest};
use crate::HttpResponse;
#[derive(Deserialize, Debug, Display)]
@ -253,42 +254,46 @@ mod tests {
#[test]
fn test_request_extract() {
let req = TestRequest::with_uri("/name/user1/").to_srv_request();
let (req, mut pl) = req.into_parts();
assert!(Query::<Id>::from_request(&req, &mut pl).is_err());
block_on(async {
let req = TestRequest::with_uri("/name/user1/").to_srv_request();
let (req, mut pl) = req.into_parts();
assert!(Query::<Id>::from_request(&req, &mut pl).await.is_err());
let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
let (req, mut pl) = req.into_parts();
let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
let (req, mut pl) = req.into_parts();
let mut s = Query::<Id>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.id, "test");
assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }");
let mut s = Query::<Id>::from_request(&req, &mut pl).await.unwrap();
assert_eq!(s.id, "test");
assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }");
s.id = "test1".to_string();
let s = s.into_inner();
assert_eq!(s.id, "test1");
s.id = "test1".to_string();
let s = s.into_inner();
assert_eq!(s.id, "test1");
})
}
#[test]
fn test_custom_error_responder() {
let req = TestRequest::with_uri("/name/user1/")
.data(QueryConfig::default().error_handler(|e, _| {
let resp = HttpResponse::UnprocessableEntity().finish();
InternalError::from_response(e, resp).into()
}))
.to_srv_request();
block_on(async {
let req = TestRequest::with_uri("/name/user1/")
.data(QueryConfig::default().error_handler(|e, _| {
let resp = HttpResponse::UnprocessableEntity().finish();
InternalError::from_response(e, resp).into()
}))
.to_srv_request();
let (req, mut pl) = req.into_parts();
let query = Query::<Id>::from_request(&req, &mut pl);
let (req, mut pl) = req.into_parts();
let query = Query::<Id>::from_request(&req, &mut pl).await;
assert!(query.is_err());
assert_eq!(
query
.unwrap_err()
.as_response_error()
.error_response()
.status(),
StatusCode::UNPROCESSABLE_ENTITY
);
assert!(query.is_err());
assert_eq!(
query
.unwrap_err()
.as_response_error()
.error_response()
.status(),
StatusCode::UNPROCESSABLE_ENTITY
);
})
}
}

View file

@ -1,9 +1,13 @@
use std::borrow::Cow;
use std::future::Future;
use std::pin::Pin;
use std::str;
use std::task::{Context, Poll};
use bytes::{Bytes, BytesMut};
use encoding_rs::{Encoding, UTF_8};
use futures::{Async, Poll, Stream};
use futures::Stream;
use pin_project::pin_project;
use crate::dev::Payload;
use crate::error::{PayloadError, ReadlinesError};
@ -22,7 +26,7 @@ pub struct Readlines<T: HttpMessage> {
impl<T> Readlines<T>
where
T: HttpMessage,
T::Stream: Stream<Item = Bytes, Error = PayloadError>,
T::Stream: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
{
/// Create a new stream to read request line by line.
pub fn new(req: &mut T) -> Self {
@ -62,20 +66,21 @@ where
impl<T> Stream for Readlines<T>
where
T: HttpMessage,
T::Stream: Stream<Item = Bytes, Error = PayloadError>,
T::Stream: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
{
type Item = String;
type Error = ReadlinesError;
type Item = Result<String, ReadlinesError>;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(err) = self.err.take() {
return Err(err);
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
if let Some(err) = this.err.take() {
return Poll::Ready(Some(Err(err)));
}
// check if there is a newline in the buffer
if !self.checked_buff {
if !this.checked_buff {
let mut found: Option<usize> = None;
for (ind, b) in self.buff.iter().enumerate() {
for (ind, b) in this.buff.iter().enumerate() {
if *b == b'\n' {
found = Some(ind);
break;
@ -83,28 +88,28 @@ where
}
if let Some(ind) = found {
// check if line is longer than limit
if ind + 1 > self.limit {
return Err(ReadlinesError::LimitOverflow);
if ind + 1 > this.limit {
return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow)));
}
let line = if self.encoding == UTF_8 {
str::from_utf8(&self.buff.split_to(ind + 1))
let line = if this.encoding == UTF_8 {
str::from_utf8(&this.buff.split_to(ind + 1))
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
self.encoding
this.encoding
.decode_without_bom_handling_and_without_replacement(
&self.buff.split_to(ind + 1),
&this.buff.split_to(ind + 1),
)
.map(Cow::into_owned)
.ok_or(ReadlinesError::EncodingError)?
};
return Ok(Async::Ready(Some(line)));
return Poll::Ready(Some(Ok(line)));
}
self.checked_buff = true;
this.checked_buff = true;
}
// poll req for more bytes
match self.stream.poll() {
Ok(Async::Ready(Some(mut bytes))) => {
match Pin::new(&mut this.stream).poll_next(cx) {
Poll::Ready(Some(Ok(mut bytes))) => {
// check if there is a newline in bytes
let mut found: Option<usize> = None;
for (ind, b) in bytes.iter().enumerate() {
@ -115,15 +120,15 @@ where
}
if let Some(ind) = found {
// check if line is longer than limit
if ind + 1 > self.limit {
return Err(ReadlinesError::LimitOverflow);
if ind + 1 > this.limit {
return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow)));
}
let line = if self.encoding == UTF_8 {
let line = if this.encoding == UTF_8 {
str::from_utf8(&bytes.split_to(ind + 1))
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
self.encoding
this.encoding
.decode_without_bom_handling_and_without_replacement(
&bytes.split_to(ind + 1),
)
@ -131,83 +136,72 @@ where
.ok_or(ReadlinesError::EncodingError)?
};
// extend buffer with rest of the bytes;
self.buff.extend_from_slice(&bytes);
self.checked_buff = false;
return Ok(Async::Ready(Some(line)));
this.buff.extend_from_slice(&bytes);
this.checked_buff = false;
return Poll::Ready(Some(Ok(line)));
}
self.buff.extend_from_slice(&bytes);
Ok(Async::NotReady)
this.buff.extend_from_slice(&bytes);
Poll::Pending
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => {
if self.buff.is_empty() {
return Ok(Async::Ready(None));
Poll::Pending => Poll::Pending,
Poll::Ready(None) => {
if this.buff.is_empty() {
return Poll::Ready(None);
}
if self.buff.len() > self.limit {
return Err(ReadlinesError::LimitOverflow);
if this.buff.len() > this.limit {
return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow)));
}
let line = if self.encoding == UTF_8 {
str::from_utf8(&self.buff)
let line = if this.encoding == UTF_8 {
str::from_utf8(&this.buff)
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
self.encoding
.decode_without_bom_handling_and_without_replacement(&self.buff)
this.encoding
.decode_without_bom_handling_and_without_replacement(&this.buff)
.map(Cow::into_owned)
.ok_or(ReadlinesError::EncodingError)?
};
self.buff.clear();
Ok(Async::Ready(Some(line)))
this.buff.clear();
Poll::Ready(Some(Ok(line)))
}
Err(e) => Err(ReadlinesError::from(e)),
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ReadlinesError::from(e)))),
}
}
}
#[cfg(test)]
mod tests {
use futures::stream::StreamExt;
use super::*;
use crate::test::{block_on, TestRequest};
#[test]
fn test_readlines() {
let mut req = TestRequest::default()
block_on(async {
let mut req = TestRequest::default()
.set_payload(Bytes::from_static(
b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\
industry. Lorem Ipsum has been the industry's standard dummy\n\
Contrary to popular belief, Lorem Ipsum is not simply random text.",
))
.to_request();
let stream = match block_on(Readlines::new(&mut req).into_future()) {
Ok((Some(s), stream)) => {
assert_eq!(
s,
"Lorem Ipsum is simply dummy text of the printing and typesetting\n"
);
stream
}
_ => unreachable!("error"),
};
let stream = match block_on(stream.into_future()) {
Ok((Some(s), stream)) => {
assert_eq!(
s,
"industry. Lorem Ipsum has been the industry's standard dummy\n"
);
stream
}
_ => unreachable!("error"),
};
let mut stream = Readlines::new(&mut req);
assert_eq!(
stream.next().await.unwrap().unwrap(),
"Lorem Ipsum is simply dummy text of the printing and typesetting\n"
);
match block_on(stream.into_future()) {
Ok((Some(s), _)) => {
assert_eq!(
s,
"Contrary to popular belief, Lorem Ipsum is not simply random text."
);
}
_ => unreachable!("error"),
}
assert_eq!(
stream.next().await.unwrap().unwrap(),
"industry. Lorem Ipsum has been the industry's standard dummy\n"
);
assert_eq!(
stream.next().await.unwrap().unwrap(),
"Contrary to popular belief, Lorem Ipsum is not simply random text."
);
})
}
}

View file

@ -1,11 +1,12 @@
//! Essentials helper functions and types for application registration.
use actix_http::http::Method;
use futures::{Future, IntoFuture};
use futures::Future;
pub use actix_http::Response as HttpResponse;
pub use bytes::{Bytes, BytesMut};
pub use futures::channel::oneshot::Canceled;
use crate::error::{BlockingError, Error};
use crate::error::Error;
use crate::extract::FromRequest;
use crate::handler::{AsyncFactory, Factory};
use crate::resource::Resource;
@ -256,21 +257,21 @@ where
/// # use futures::future::{ok, Future};
/// use actix_web::{web, App, HttpResponse, Error};
///
/// fn index() -> impl Future<Item=HttpResponse, Error=Error> {
/// ok(HttpResponse::Ok().finish())
/// async fn index() -> Result<HttpResponse, Error> {
/// Ok(HttpResponse::Ok().finish())
/// }
///
/// App::new().service(web::resource("/").route(
/// web::to_async(index))
/// );
/// ```
pub fn to_async<F, I, R>(handler: F) -> Route
pub fn to_async<F, I, R, O, E>(handler: F) -> Route
where
F: AsyncFactory<I, R>,
F: AsyncFactory<I, R, O, E>,
I: FromRequest + 'static,
R: IntoFuture + 'static,
R::Item: Responder,
R::Error: Into<Error>,
R: Future<Output = Result<O, E>> + 'static,
O: Responder + 'static,
E: Into<Error> + 'static,
{
Route::new().to_async(handler)
}
@ -279,10 +280,11 @@ where
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{dev, web, guard, App, HttpResponse};
/// use futures::future::{ok, Ready};
/// use actix_web::{dev, web, guard, App, Error, HttpResponse};
///
/// fn my_service(req: dev::ServiceRequest) -> dev::ServiceResponse {
/// req.into_response(HttpResponse::Ok().finish())
/// fn my_service(req: dev::ServiceRequest) -> Ready<Result<dev::ServiceResponse, Error>> {
/// ok(req.into_response(HttpResponse::Ok().finish()))
/// }
///
/// fn main() {
@ -299,11 +301,10 @@ pub fn service(path: &str) -> WebService {
/// Execute blocking function on a thread pool, returns future that resolves
/// to result of the function execution.
pub fn block<F, I, E>(f: F) -> impl Future<Item = I, Error = BlockingError<E>>
pub fn block<F, R>(f: F) -> impl Future<Output = Result<R, Canceled>>
where
F: FnOnce() -> Result<I, E> + Send + 'static,
I: Send + 'static,
E: Send + std::fmt::Debug + 'static,
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
actix_threadpool::run(f).from_err()
actix_threadpool::run(f)
}

View file

@ -59,19 +59,5 @@ tokio-timer = "0.3.0-alpha.6"
open-ssl = { version="0.10", package="openssl", optional = true }
[dev-dependencies]
#actix-web = "1.0.7"
actix-web = "2.0.0-alpha.1"
actix-http = "0.3.0-alpha.1"
[patch.crates-io]
actix-http = { path = "../actix-http" }
awc = { path = "../awc" }
actix-codec = { path = "../../actix-net/actix-codec" }
actix-connect = { path = "../../actix-net/actix-connect" }
actix-rt = { path = "../../actix-net/actix-rt" }
actix-server = { path = "../../actix-net/actix-server" }
actix-server-config = { path = "../../actix-net/actix-server-config" }
actix-service = { path = "../../actix-net/actix-service" }
actix-testing = { path = "../../actix-net/actix-testing" }
actix-threadpool = { path = "../../actix-net/actix-threadpool" }
actix-utils = { path = "../../actix-net/actix-utils" }

View file

@ -3,7 +3,7 @@ use std::sync::mpsc;
use std::{net, thread, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_rt::{System};
use actix_rt::System;
use actix_server::{Server, ServiceFactory};
use awc::{error::PayloadError, ws, Client, ClientRequest, ClientResponse, Connector};
use bytes::Bytes;

View file

@ -2,8 +2,8 @@ use net2::TcpBuilder;
use std::sync::mpsc;
use std::{net, thread, time::Duration};
#[cfg(feature = "ssl")]
use openssl::ssl::SslAcceptorBuilder;
#[cfg(feature = "openssl")]
use open_ssl::ssl::SslAcceptorBuilder;
use actix_http::Response;
use actix_web::{test, web, App, HttpServer};
@ -55,22 +55,19 @@ fn test_start() {
use actix_http::client;
use actix_web::test;
let client = test::run_on(|| {
Ok::<_, ()>(
awc::Client::build()
.connector(
client::Connector::new()
.timeout(Duration::from_millis(100))
.finish(),
)
.finish(),
)
})
.unwrap();
let host = format!("http://{}", addr);
test::block_on(async {
let client = awc::Client::build()
.connector(
client::Connector::new()
.timeout(Duration::from_millis(100))
.finish(),
)
.finish();
let response = test::block_on(client.get(host.clone()).send()).unwrap();
assert!(response.status().is_success());
let host = format!("http://{}", addr);
let response = client.get(host.clone()).send().await.unwrap();
assert!(response.status().is_success());
});
}
// stop
@ -80,9 +77,9 @@ fn test_start() {
let _ = sys.stop();
}
#[cfg(feature = "ssl")]
#[cfg(feature = "openssl")]
fn ssl_acceptor() -> std::io::Result<SslAcceptorBuilder> {
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
// load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder
@ -95,7 +92,7 @@ fn ssl_acceptor() -> std::io::Result<SslAcceptorBuilder> {
}
#[test]
#[cfg(feature = "ssl")]
#[cfg(feature = "openssl")]
fn test_start_ssl() {
let addr = unused_addr();
let (tx, rx) = mpsc::channel();
@ -113,7 +110,7 @@ fn test_start_ssl() {
.shutdown_timeout(1)
.system_exit()
.disable_signals()
.bind_ssl(format!("{}", addr), builder)
.bind_openssl(format!("{}", addr), builder)
.unwrap()
.start();
@ -122,30 +119,27 @@ fn test_start_ssl() {
});
let (srv, sys) = rx.recv().unwrap();
let client = test::run_on(|| {
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
test::block_on(async move {
use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_verify(SslVerifyMode::NONE);
let _ = builder
.set_alpn_protos(b"\x02h2\x08http/1.1")
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
Ok::<_, ()>(
awc::Client::build()
.connector(
awc::Connector::new()
.ssl(builder.build())
.timeout(Duration::from_millis(100))
.finish(),
)
.finish(),
)
})
.unwrap();
let host = format!("https://{}", addr);
let client = awc::Client::build()
.connector(
awc::Connector::new()
.ssl(builder.build())
.timeout(Duration::from_millis(100))
.finish(),
)
.finish();
let response = test::block_on(client.get(host.clone()).send()).unwrap();
assert!(response.status().is_success());
let host = format!("https://{}", addr);
let response = client.get(host.clone()).send().await.unwrap();
assert!(response.status().is_success());
});
// stop
let _ = srv.stop(false);

File diff suppressed because it is too large Load diff