Merge branch 'master' into fix-modified-secs

This commit is contained in:
Rob Ede 2021-01-11 17:27:49 +00:00 committed by GitHub
commit 73b9847edb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1291 additions and 1574 deletions

View File

@ -1,6 +1,24 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
### Added
* The method `Either<web::Json<T>, web::Form<T>>::into_inner()` which returns the inner type for
whichever variant was created. Also works for `Either<web::Form<T>, web::Json<T>>`. [#1894]
### Changed
* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly.
Making it more simple and performant. [#1891]
* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail.
`ServiceRequest::from_request` would not fail and no payload would be generated [#1893]
* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894]
### Removed
* Public field of `web::Path` has been made private. [#1894]
* Public field of `web::Query` has been made private. [#1894]
[#1891]: https://github.com/actix/actix-web/pull/1891
[#1893]: https://github.com/actix/actix-web/pull/1893
[#1894]: https://github.com/actix/actix-web/pull/1894
## 4.0.0-beta.1 - 2021-01-07 ## 4.0.0-beta.1 - 2021-01-07
@ -24,13 +42,15 @@
### Removed ### Removed
* Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now * Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now
exposed directly by the `middleware` module. exposed directly by the `middleware` module.
* Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported
from `actix_web::error` module. [#1878]
[#1812]: https://github.com/actix/actix-web/pull/1812 [#1812]: https://github.com/actix/actix-web/pull/1812
[#1813]: https://github.com/actix/actix-web/pull/1813 [#1813]: https://github.com/actix/actix-web/pull/1813
[#1852]: https://github.com/actix/actix-web/pull/1852 [#1852]: https://github.com/actix/actix-web/pull/1852
[#1865]: https://github.com/actix/actix-web/pull/1865 [#1865]: https://github.com/actix/actix-web/pull/1865
[#1875]: https://github.com/actix/actix-web/pull/1875 [#1875]: https://github.com/actix/actix-web/pull/1875
[#1878]: https://github.com/actix/actix-web/pull/1878
## 3.3.2 - 2020-12-01 ## 3.3.2 - 2020-12-01
### Fixed ### Fixed

View File

@ -76,11 +76,10 @@ required-features = ["rustls"]
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-macros = "0.1.0" actix-macros = "0.1.0"
actix-router = "0.2.4" actix-router = "0.2.4"
actix-rt = "2.0.0-beta.1" actix-rt = "2.0.0-beta.2"
actix-server = "2.0.0-beta.2" actix-server = "2.0.0-beta.2"
actix-service = "2.0.0-beta.2" actix-service = "2.0.0-beta.3"
actix-utils = "3.0.0-beta.1" actix-utils = "3.0.0-beta.1"
actix-threadpool = "0.3.1"
actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true } actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true }
actix-web-codegen = "0.4.0" actix-web-codegen = "0.4.0"
@ -90,6 +89,7 @@ awc = { version = "3.0.0-beta.1", default-features = false }
ahash = "0.6" ahash = "0.6"
bytes = "1" bytes = "1"
derive_more = "0.99.5" derive_more = "0.99.5"
either = "1.5.3"
encoding_rs = "0.8" encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
@ -139,3 +139,7 @@ harness = false
[[bench]] [[bench]]
name = "service" name = "service"
harness = false harness = false
[[bench]]
name = "responder"
harness = false

View File

@ -18,7 +18,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = { version = "4.0.0-beta.1", default-features = false } actix-web = { version = "4.0.0-beta.1", default-features = false }
actix-service = "2.0.0-beta.2" actix-service = "2.0.0-beta.3"
bitflags = "1" bitflags = "1"
bytes = "1" bytes = "1"
futures-core = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.7", default-features = false }
@ -31,5 +31,5 @@ percent-encoding = "2.1"
v_htmlescape = "0.12" v_htmlescape = "0.12"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.0.0-beta.1" actix-rt = "2.0.0-beta.2"
actix-web = "4.0.0-beta.1" actix-web = "4.0.0-beta.1"

View File

@ -8,17 +8,11 @@ use std::{
}; };
use actix_web::{ use actix_web::{
error::{BlockingError, Error}, error::{Error, ErrorInternalServerError},
web, rt::task::{spawn_blocking, JoinHandle},
}; };
use bytes::Bytes; use bytes::Bytes;
use futures_core::{ready, Stream}; use futures_core::{ready, Stream};
use futures_util::future::{FutureExt, LocalBoxFuture};
use crate::handle_error;
type ChunkedBoxFuture =
LocalBoxFuture<'static, Result<(File, Bytes), BlockingError<io::Error>>>;
#[doc(hidden)] #[doc(hidden)]
/// A helper created from a `std::fs::File` which reads the file /// A helper created from a `std::fs::File` which reads the file
@ -27,7 +21,7 @@ pub struct ChunkedReadFile {
pub(crate) size: u64, pub(crate) size: u64,
pub(crate) offset: u64, pub(crate) offset: u64,
pub(crate) file: Option<File>, pub(crate) file: Option<File>,
pub(crate) fut: Option<ChunkedBoxFuture>, pub(crate) fut: Option<JoinHandle<Result<(File, Bytes), io::Error>>>,
pub(crate) counter: u64, pub(crate) counter: u64,
} }
@ -45,18 +39,20 @@ impl Stream for ChunkedReadFile {
cx: &mut Context<'_>, cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> { ) -> Poll<Option<Self::Item>> {
if let Some(ref mut fut) = self.fut { if let Some(ref mut fut) = self.fut {
return match ready!(Pin::new(fut).poll(cx)) { let res = match ready!(Pin::new(fut).poll(cx)) {
Ok((file, bytes)) => { Ok(Ok((file, bytes))) => {
self.fut.take(); self.fut.take();
self.file = Some(file); self.file = Some(file);
self.offset += bytes.len() as u64; self.offset += bytes.len() as u64;
self.counter += bytes.len() as u64; self.counter += bytes.len() as u64;
Poll::Ready(Some(Ok(bytes))) Ok(bytes)
} }
Err(e) => Poll::Ready(Some(Err(handle_error(e)))), Ok(Err(e)) => Err(e.into()),
Err(_) => Err(ErrorInternalServerError("Unexpected error")),
}; };
return Poll::Ready(Some(res));
} }
let size = self.size; let size = self.size;
@ -68,25 +64,21 @@ impl Stream for ChunkedReadFile {
} else { } else {
let mut file = self.file.take().expect("Use after completion"); let mut file = self.file.take().expect("Use after completion");
self.fut = Some( self.fut = Some(spawn_blocking(move || {
web::block(move || { let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize;
let max_bytes =
cmp::min(size.saturating_sub(counter), 65_536) as usize;
let mut buf = Vec::with_capacity(max_bytes); let mut buf = Vec::with_capacity(max_bytes);
file.seek(io::SeekFrom::Start(offset))?; file.seek(io::SeekFrom::Start(offset))?;
let n_bytes = let n_bytes =
file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?;
if n_bytes == 0 { if n_bytes == 0 {
return Err(io::ErrorKind::UnexpectedEof.into()); return Err(io::ErrorKind::UnexpectedEof.into());
} }
Ok((file, Bytes::from(buf))) Ok((file, Bytes::from(buf)))
}) }));
.boxed_local(),
);
self.poll_next(cx) self.poll_next(cx)
} }

View File

@ -82,8 +82,9 @@ impl Files {
/// be inaccessible. Register more specific handlers and services first. /// be inaccessible. Register more specific handlers and services first.
/// ///
/// `Files` uses a threadpool for blocking filesystem operations. By default, the pool uses a /// `Files` uses a threadpool for blocking filesystem operations. By default, the pool uses a
/// number of threads equal to 5x the number of available logical CPUs. Pool size can be changed /// max number of threads equal to `512 * HttpServer::worker`. Real time thread count are
/// by setting ACTIX_THREADPOOL environment variable. /// adjusted with work load. More threads would spawn when need and threads goes idle for a
/// period of time would be de-spawned.
pub fn new<T: Into<PathBuf>>(mount_path: &str, serve_from: T) -> Files { pub fn new<T: Into<PathBuf>>(mount_path: &str, serve_from: T) -> Files {
let orig_dir = serve_from.into(); let orig_dir = serve_from.into();
let dir = match orig_dir.canonicalize() { let dir = match orig_dir.canonicalize() {

View File

@ -14,12 +14,10 @@
#![deny(rust_2018_idioms)] #![deny(rust_2018_idioms)]
#![warn(missing_docs, missing_debug_implementations)] #![warn(missing_docs, missing_debug_implementations)]
use std::io;
use actix_service::boxed::{BoxService, BoxServiceFactory}; use actix_service::boxed::{BoxService, BoxServiceFactory};
use actix_web::{ use actix_web::{
dev::{ServiceRequest, ServiceResponse}, dev::{ServiceRequest, ServiceResponse},
error::{BlockingError, Error, ErrorInternalServerError}, error::Error,
http::header::DispositionType, http::header::DispositionType,
}; };
use mime_guess::from_ext; use mime_guess::from_ext;
@ -56,13 +54,6 @@ pub fn file_extension_to_mime(ext: &str) -> mime::Mime {
from_ext(ext).first_or_octet_stream() from_ext(ext).first_or_octet_stream()
} }
pub(crate) fn handle_error(err: BlockingError<io::Error>) -> Error {
match err {
BlockingError::Error(err) => err.into(),
BlockingError::Canceled => ErrorInternalServerError("Unexpected error"),
}
}
type MimeOverride = dyn Fn(&mime::Name<'_>) -> DispositionType; type MimeOverride = dyn Fn(&mime::Name<'_>) -> DispositionType;
#[cfg(test)] #[cfg(test)]

View File

@ -16,10 +16,9 @@ use actix_web::{
}, },
ContentEncoding, StatusCode, ContentEncoding, StatusCode,
}, },
Error, HttpMessage, HttpRequest, HttpResponse, Responder, HttpMessage, HttpRequest, HttpResponse, Responder,
}; };
use bitflags::bitflags; use bitflags::bitflags;
use futures_util::future::{ready, Ready};
use mime_guess::from_path; use mime_guess::from_path;
use crate::ChunkedReadFile; use crate::ChunkedReadFile;
@ -277,7 +276,7 @@ impl NamedFile {
} }
/// Creates an `HttpResponse` with file as a streaming body. /// Creates an `HttpResponse` with file as a streaming body.
pub fn into_response(self, req: &HttpRequest) -> Result<HttpResponse, Error> { pub fn into_response(self, req: &HttpRequest) -> HttpResponse {
if self.status_code != StatusCode::OK { if self.status_code != StatusCode::OK {
let mut res = HttpResponse::build(self.status_code); let mut res = HttpResponse::build(self.status_code);
@ -307,7 +306,7 @@ impl NamedFile {
counter: 0, counter: 0,
}; };
return Ok(res.streaming(reader)); return res.streaming(reader);
} }
let etag = if self.flags.contains(Flags::ETAG) { let etag = if self.flags.contains(Flags::ETAG) {
@ -411,17 +410,17 @@ impl NamedFile {
); );
} else { } else {
resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); resp.header(header::CONTENT_RANGE, format!("bytes */{}", length));
return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
}; };
} else { } else {
return Ok(resp.status(StatusCode::BAD_REQUEST).finish()); return resp.status(StatusCode::BAD_REQUEST).finish();
}; };
}; };
if precondition_failed { if precondition_failed {
return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); return resp.status(StatusCode::PRECONDITION_FAILED).finish();
} else if not_modified { } else if not_modified {
return Ok(resp.status(StatusCode::NOT_MODIFIED).finish()); return resp.status(StatusCode::NOT_MODIFIED).finish();
} }
let reader = ChunkedReadFile { let reader = ChunkedReadFile {
@ -436,7 +435,7 @@ impl NamedFile {
resp.status(StatusCode::PARTIAL_CONTENT); resp.status(StatusCode::PARTIAL_CONTENT);
} }
Ok(resp.body(SizedStream::new(length, reader))) resp.body(SizedStream::new(length, reader))
} }
} }
@ -495,10 +494,7 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
} }
impl Responder for NamedFile { impl Responder for NamedFile {
type Error = Error; fn respond_to(self, req: &HttpRequest) -> HttpResponse {
type Future = Ready<Result<HttpResponse, Error>>; self.into_response(req)
fn respond_to(self, req: &HttpRequest) -> Self::Future {
ready(self.into_response(req))
} }
} }

View File

@ -120,10 +120,8 @@ impl Service<ServiceRequest> for FilesService {
named_file.flags = self.file_flags; named_file.flags = self.file_flags;
let (req, _) = req.into_parts(); let (req, _) = req.into_parts();
Either::Left(ok(match named_file.into_response(&req) { let res = named_file.into_response(&req);
Ok(item) => ServiceResponse::new(req, item), Either::Left(ok(ServiceResponse::new(req, res)))
Err(e) => ServiceResponse::from_err(e, req),
}))
} }
Err(e) => self.handle_err(e, req), Err(e) => self.handle_err(e, req),
} }
@ -154,12 +152,8 @@ impl Service<ServiceRequest> for FilesService {
named_file.flags = self.file_flags; named_file.flags = self.file_flags;
let (req, _) = req.into_parts(); let (req, _) = req.into_parts();
match named_file.into_response(&req) { let res = named_file.into_response(&req);
Ok(item) => { Either::Left(ok(ServiceResponse::new(req, res)))
Either::Left(ok(ServiceResponse::new(req.clone(), item)))
}
Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))),
}
} }
Err(e) => self.handle_err(e, req), Err(e) => self.handle_err(e, req),
} }

View File

@ -29,11 +29,11 @@ default = []
openssl = ["open-ssl", "awc/openssl"] openssl = ["open-ssl", "awc/openssl"]
[dependencies] [dependencies]
actix-service = "2.0.0-beta.2" actix-service = "2.0.0-beta.3"
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-tls = "3.0.0-beta.2" actix-tls = "3.0.0-beta.2"
actix-utils = "3.0.0-beta.1" actix-utils = "3.0.0-beta.1"
actix-rt = "2.0.0-beta.1" actix-rt = "2.0.0-beta.2"
actix-server = "2.0.0-beta.2" actix-server = "2.0.0-beta.2"
awc = "3.0.0-beta.1" awc = "3.0.0-beta.1"

View File

@ -1,6 +1,9 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* `Response::content_type` now takes an `impl IntoHeaderValue` to support `mime` types. [#1894]
[#1894]: https://github.com/actix/actix-web/pull/1894
## 3.0.0-beta.1 - 2021-01-07 ## 3.0.0-beta.1 - 2021-01-07
@ -22,11 +25,14 @@
* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. * Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`.
due to the removal of this type from `tokio-openssl` crate. openssl handshake due to the removal of this type from `tokio-openssl` crate. openssl handshake
error would return as `ConnectError::SslError`. [#1813] error would return as `ConnectError::SslError`. [#1813]
* Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`.
Due to this change `actix_threadpool::BlockingError` type is moved into
`actix_http::error` module. [#1878]
[#1813]: https://github.com/actix/actix-web/pull/1813 [#1813]: https://github.com/actix/actix-web/pull/1813
[#1857]: https://github.com/actix/actix-web/pull/1857 [#1857]: https://github.com/actix/actix-web/pull/1857
[#1864]: https://github.com/actix/actix-web/pull/1864 [#1864]: https://github.com/actix/actix-web/pull/1864
[#1878]: https://github.com/actix/actix-web/pull/1878
## 2.2.0 - 2020-11-25 ## 2.2.0 - 2020-11-25
### Added ### Added

View File

@ -40,11 +40,10 @@ secure-cookies = ["cookie/secure"]
actors = ["actix"] actors = ["actix"]
[dependencies] [dependencies]
actix-service = "2.0.0-beta.2" actix-service = "2.0.0-beta.3"
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-utils = "3.0.0-beta.1" actix-utils = "3.0.0-beta.1"
actix-rt = "2.0.0-beta.1" actix-rt = "2.0.0-beta.2"
actix-threadpool = "0.3.1"
actix-tls = "3.0.0-beta.2" actix-tls = "3.0.0-beta.2"
actix = { version = "0.11.0-beta.1", optional = true } actix = { version = "0.11.0-beta.1", optional = true }

View File

@ -3,14 +3,14 @@ use std::io::{self, Write};
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_threadpool::{run, CpuFuture}; use actix_rt::task::{spawn_blocking, JoinHandle};
use brotli2::write::BrotliDecoder; use brotli2::write::BrotliDecoder;
use bytes::Bytes; use bytes::Bytes;
use flate2::write::{GzDecoder, ZlibDecoder}; use flate2::write::{GzDecoder, ZlibDecoder};
use futures_core::{ready, Stream}; use futures_core::{ready, Stream};
use super::Writer; use super::Writer;
use crate::error::PayloadError; use crate::error::{BlockingError, PayloadError};
use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}; use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING};
const INPLACE: usize = 2049; const INPLACE: usize = 2049;
@ -19,7 +19,7 @@ pub struct Decoder<S> {
decoder: Option<ContentDecoder>, decoder: Option<ContentDecoder>,
stream: S, stream: S,
eof: bool, eof: bool,
fut: Option<CpuFuture<(Option<Bytes>, ContentDecoder), io::Error>>, fut: Option<JoinHandle<Result<(Option<Bytes>, ContentDecoder), io::Error>>>,
} }
impl<S> Decoder<S> impl<S> Decoder<S>
@ -80,8 +80,13 @@ where
loop { loop {
if let Some(ref mut fut) = self.fut { if let Some(ref mut fut) = self.fut {
let (chunk, decoder) = match ready!(Pin::new(fut).poll(cx)) { let (chunk, decoder) = match ready!(Pin::new(fut).poll(cx)) {
Ok(item) => item, Ok(Ok(item)) => item,
Err(e) => return Poll::Ready(Some(Err(e.into()))), Ok(Err(e)) => {
return Poll::Ready(Some(Err(BlockingError::Error(e).into())))
}
Err(_) => {
return Poll::Ready(Some(Err(BlockingError::Canceled.into())))
}
}; };
self.decoder = Some(decoder); self.decoder = Some(decoder);
self.fut.take(); self.fut.take();
@ -105,7 +110,7 @@ where
return Poll::Ready(Some(Ok(chunk))); return Poll::Ready(Some(Ok(chunk)));
} }
} else { } else {
self.fut = Some(run(move || { self.fut = Some(spawn_blocking(move || {
let chunk = decoder.feed_data(chunk)?; let chunk = decoder.feed_data(chunk)?;
Ok((chunk, decoder)) Ok((chunk, decoder))
})); }));

View File

@ -4,7 +4,7 @@ use std::io::{self, Write};
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_threadpool::{run, CpuFuture}; use actix_rt::task::{spawn_blocking, JoinHandle};
use brotli2::write::BrotliEncoder; use brotli2::write::BrotliEncoder;
use bytes::Bytes; use bytes::Bytes;
use flate2::write::{GzEncoder, ZlibEncoder}; use flate2::write::{GzEncoder, ZlibEncoder};
@ -17,6 +17,7 @@ use crate::http::{HeaderValue, StatusCode};
use crate::{Error, ResponseHead}; use crate::{Error, ResponseHead};
use super::Writer; use super::Writer;
use crate::error::BlockingError;
const INPLACE: usize = 1024; const INPLACE: usize = 1024;
@ -26,7 +27,7 @@ pub struct Encoder<B> {
#[pin] #[pin]
body: EncoderBody<B>, body: EncoderBody<B>,
encoder: Option<ContentEncoder>, encoder: Option<ContentEncoder>,
fut: Option<CpuFuture<ContentEncoder, io::Error>>, fut: Option<JoinHandle<Result<ContentEncoder, io::Error>>>,
} }
impl<B: MessageBody> Encoder<B> { impl<B: MessageBody> Encoder<B> {
@ -136,8 +137,15 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
if let Some(ref mut fut) = this.fut { if let Some(ref mut fut) = this.fut {
let mut encoder = match ready!(Pin::new(fut).poll(cx)) { let mut encoder = match ready!(Pin::new(fut).poll(cx)) {
Ok(item) => item, Ok(Ok(item)) => item,
Err(e) => return Poll::Ready(Some(Err(e.into()))), Ok(Err(e)) => {
return Poll::Ready(Some(Err(BlockingError::Error(e).into())))
}
Err(_) => {
return Poll::Ready(Some(Err(
BlockingError::<io::Error>::Canceled.into(),
)))
}
}; };
let chunk = encoder.take(); let chunk = encoder.take();
*this.encoder = Some(encoder); *this.encoder = Some(encoder);
@ -160,7 +168,7 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
return Poll::Ready(Some(Ok(chunk))); return Poll::Ready(Some(Ok(chunk)));
} }
} else { } else {
*this.fut = Some(run(move || { *this.fut = Some(spawn_blocking(move || {
encoder.write(&chunk)?; encoder.write(&chunk)?;
Ok(encoder) Ok(encoder)
})); }));

View File

@ -7,7 +7,6 @@ use std::string::FromUtf8Error;
use std::{fmt, io, result}; use std::{fmt, io, result};
use actix_codec::{Decoder, Encoder}; use actix_codec::{Decoder, Encoder};
pub use actix_threadpool::BlockingError;
use actix_utils::dispatcher::DispatcherError as FramedDispatcherError; use actix_utils::dispatcher::DispatcherError as FramedDispatcherError;
use actix_utils::timeout::TimeoutError; use actix_utils::timeout::TimeoutError;
use bytes::BytesMut; use bytes::BytesMut;
@ -100,10 +99,6 @@ impl fmt::Debug for Error {
} }
impl std::error::Error for Error { impl std::error::Error for Error {
fn cause(&self) -> Option<&dyn std::error::Error> {
None
}
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None None
} }
@ -190,9 +185,6 @@ impl ResponseError for DeError {
/// `InternalServerError` for `Canceled` /// `InternalServerError` for `Canceled`
impl ResponseError for Canceled {} impl ResponseError for Canceled {}
/// `InternalServerError` for `BlockingError`
impl<E: fmt::Debug> ResponseError for BlockingError<E> {}
/// Return `BAD_REQUEST` for `Utf8Error` /// Return `BAD_REQUEST` for `Utf8Error`
impl ResponseError for Utf8Error { impl ResponseError for Utf8Error {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
@ -304,33 +296,64 @@ impl From<httparse::Error> for ParseError {
} }
} }
/// A set of errors that can occur running blocking tasks in thread pool.
#[derive(Debug, Display)]
pub enum BlockingError<E: fmt::Debug> {
#[display(fmt = "{:?}", _0)]
Error(E),
#[display(fmt = "Thread pool is gone")]
Canceled,
}
impl<E: fmt::Debug> std::error::Error for BlockingError<E> {}
/// `InternalServerError` for `BlockingError`
impl<E: fmt::Debug> ResponseError for BlockingError<E> {}
#[derive(Display, Debug)] #[derive(Display, Debug)]
/// A set of errors that can occur during payload parsing /// A set of errors that can occur during payload parsing
pub enum PayloadError { pub enum PayloadError {
/// A payload reached EOF, but is not complete. /// A payload reached EOF, but is not complete.
#[display( #[display(
fmt = "A payload reached EOF, but is not complete. With error: {:?}", fmt = "A payload reached EOF, but is not complete. Inner error: {:?}",
_0 _0
)] )]
Incomplete(Option<io::Error>), Incomplete(Option<io::Error>),
/// Content encoding stream corruption
/// Content encoding stream corruption.
#[display(fmt = "Can not decode content-encoding.")] #[display(fmt = "Can not decode content-encoding.")]
EncodingCorrupted, EncodingCorrupted,
/// A payload reached size limit.
#[display(fmt = "A payload reached size limit.")] /// Payload reached size limit.
#[display(fmt = "Payload reached size limit.")]
Overflow, Overflow,
/// A payload length is unknown.
#[display(fmt = "A payload length is unknown.")] /// Payload length is unknown.
#[display(fmt = "Payload length is unknown.")]
UnknownLength, UnknownLength,
/// Http2 payload error
/// HTTP/2 payload error.
#[display(fmt = "{}", _0)] #[display(fmt = "{}", _0)]
Http2Payload(h2::Error), Http2Payload(h2::Error),
/// Io error
/// Generic I/O error.
#[display(fmt = "{}", _0)] #[display(fmt = "{}", _0)]
Io(io::Error), Io(io::Error),
} }
impl std::error::Error for PayloadError {} impl std::error::Error for PayloadError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
PayloadError::Incomplete(None) => None,
PayloadError::Incomplete(Some(err)) => Some(err as &dyn std::error::Error),
PayloadError::EncodingCorrupted => None,
PayloadError::Overflow => None,
PayloadError::UnknownLength => None,
PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error),
PayloadError::Io(err) => Some(err as &dyn std::error::Error),
}
}
}
impl From<h2::Error> for PayloadError { impl From<h2::Error> for PayloadError {
fn from(err: h2::Error) -> Self { fn from(err: h2::Error) -> Self {
@ -1009,22 +1032,22 @@ mod tests {
fn test_payload_error() { fn test_payload_error() {
let err: PayloadError = let err: PayloadError =
io::Error::new(io::ErrorKind::Other, "ParseError").into(); io::Error::new(io::ErrorKind::Other, "ParseError").into();
assert!(format!("{}", err).contains("ParseError")); assert!(err.to_string().contains("ParseError"));
let err = PayloadError::Incomplete(None); let err = PayloadError::Incomplete(None);
assert_eq!( assert_eq!(
format!("{}", err), err.to_string(),
"A payload reached EOF, but is not complete. With error: None" "A payload reached EOF, but is not complete. Inner error: None"
); );
} }
macro_rules! from { macro_rules! from {
($from:expr => $error:pat) => { ($from:expr => $error:pat) => {
match ParseError::from($from) { match ParseError::from($from) {
e @ $error => { err @ $error => {
assert!(format!("{}", e).len() >= 5); assert!(err.to_string().len() >= 5);
} }
e => unreachable!("{:?}", e), err => unreachable!("{:?}", err),
} }
}; };
} }
@ -1067,7 +1090,7 @@ mod tests {
let err = PayloadError::Overflow; let err = PayloadError::Overflow;
let resp_err: &dyn ResponseError = &err; let resp_err: &dyn ResponseError = &err;
let err = resp_err.downcast_ref::<PayloadError>().unwrap(); let err = resp_err.downcast_ref::<PayloadError>().unwrap();
assert_eq!(err.to_string(), "A payload reached size limit."); assert_eq!(err.to_string(), "Payload reached size limit.");
let not_err = resp_err.downcast_ref::<ContentTypeError>(); let not_err = resp_err.downcast_ref::<ContentTypeError>();
assert!(not_err.is_none()); assert!(not_err.is_none());
} }

View File

@ -287,42 +287,35 @@ where
self: Pin<&mut Self>, self: Pin<&mut Self>,
cx: &mut Context<'_>, cx: &mut Context<'_>,
) -> Result<bool, DispatchError> { ) -> Result<bool, DispatchError> {
if self.write_buf.is_empty() { let len = self.write_buf.len();
if len == 0 {
return Ok(false); return Ok(false);
} }
let len = self.write_buf.len();
let mut written = 0;
let InnerDispatcherProj { io, write_buf, .. } = self.project(); let InnerDispatcherProj { io, write_buf, .. } = self.project();
let mut io = Pin::new(io.as_mut().unwrap()); let mut io = Pin::new(io.as_mut().unwrap());
let mut written = 0;
while written < len { while written < len {
match io.as_mut().poll_write(cx, &write_buf[written..]) { match io.as_mut().poll_write(cx, &write_buf[written..]) {
Poll::Ready(Ok(0)) => { Poll::Ready(Ok(0)) => {
return Err(DispatchError::Io(io::Error::new( return Err(DispatchError::Io(io::Error::new(
io::ErrorKind::WriteZero, io::ErrorKind::WriteZero,
"", "",
))); )))
}
Poll::Ready(Ok(n)) => {
written += n;
} }
Poll::Ready(Ok(n)) => written += n,
Poll::Pending => { Poll::Pending => {
if written > 0 { write_buf.advance(written);
write_buf.advance(written);
}
return Ok(true); return Ok(true);
} }
Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)), Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)),
} }
} }
if written == write_buf.len() { // SAFETY: setting length to 0 is safe
// SAFETY: setting length to 0 is safe // skips one length check vs truncate
// skips one length check vs truncate unsafe { write_buf.set_len(0) }
unsafe { write_buf.set_len(0) }
} else {
write_buf.advance(written);
}
Ok(false) Ok(false)
} }
@ -766,19 +759,12 @@ where
} else { } else {
// flush buffer // flush buffer
inner.as_mut().poll_flush(cx)?; inner.as_mut().poll_flush(cx)?;
if !inner.write_buf.is_empty() || inner.io.is_none() { if !inner.write_buf.is_empty() {
Poll::Pending Poll::Pending
} else { } else {
match Pin::new(inner.project().io) Pin::new(inner.project().io.as_mut().unwrap())
.as_pin_mut()
.unwrap()
.poll_shutdown(cx) .poll_shutdown(cx)
{ .map_err(DispatchError::from)
Poll::Ready(res) => {
Poll::Ready(res.map_err(DispatchError::from))
}
Poll::Pending => Poll::Pending,
}
} }
} }
} else { } else {
@ -920,7 +906,7 @@ where
buf.reserve(HW_BUFFER_SIZE - remaining); buf.reserve(HW_BUFFER_SIZE - remaining);
} }
match read(cx, io, buf) { match actix_codec::poll_read_buf(Pin::new(io), cx, buf) {
Poll::Pending => { Poll::Pending => {
return if read_some { Ok(Some(false)) } else { Ok(None) }; return if read_some { Ok(Some(false)) } else { Ok(None) };
} }
@ -948,17 +934,6 @@ where
} }
} }
fn read<T>(
cx: &mut Context<'_>,
io: &mut T,
buf: &mut BytesMut,
) -> Poll<Result<usize, io::Error>>
where
T: AsyncRead + Unpin,
{
actix_codec::poll_read_buf(Pin::new(io), cx, buf)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::str; use std::str;

View File

@ -34,7 +34,9 @@ bitflags! {
pub trait Head: Default + 'static { pub trait Head: Default + 'static {
fn clear(&mut self); fn clear(&mut self);
fn pool() -> &'static MessagePool<Self>; fn with_pool<F, R>(f: F) -> R
where
F: FnOnce(&MessagePool<Self>) -> R;
} }
#[derive(Debug)] #[derive(Debug)]
@ -69,8 +71,11 @@ impl Head for RequestHead {
self.extensions.get_mut().clear(); self.extensions.get_mut().clear();
} }
fn pool() -> &'static MessagePool<Self> { fn with_pool<F, R>(f: F) -> R
REQUEST_POOL.with(|p| *p) where
F: FnOnce(&MessagePool<Self>) -> R,
{
REQUEST_POOL.with(|p| f(p))
} }
} }
@ -344,7 +349,7 @@ pub struct Message<T: Head> {
impl<T: Head> Message<T> { impl<T: Head> Message<T> {
/// Get new message from the pool of objects /// Get new message from the pool of objects
pub fn new() -> Self { pub fn new() -> Self {
T::pool().get_message() T::with_pool(|p| p.get_message())
} }
} }
@ -373,7 +378,7 @@ impl<T: Head> std::ops::DerefMut for Message<T> {
impl<T: Head> Drop for Message<T> { impl<T: Head> Drop for Message<T> {
fn drop(&mut self) { fn drop(&mut self) {
if Rc::strong_count(&self.head) == 1 { if Rc::strong_count(&self.head) == 1 {
T::pool().release(self.head.clone()); T::with_pool(|p| p.release(self.head.clone()))
} }
} }
} }
@ -426,18 +431,17 @@ pub struct MessagePool<T: Head>(RefCell<Vec<Rc<T>>>);
/// Request's objects pool /// Request's objects pool
pub struct BoxedResponsePool(RefCell<Vec<Box<ResponseHead>>>); pub struct BoxedResponsePool(RefCell<Vec<Box<ResponseHead>>>);
thread_local!(static REQUEST_POOL: &'static MessagePool<RequestHead> = MessagePool::<RequestHead>::create()); thread_local!(static REQUEST_POOL: MessagePool<RequestHead> = MessagePool::<RequestHead>::create());
thread_local!(static RESPONSE_POOL: &'static BoxedResponsePool = BoxedResponsePool::create()); thread_local!(static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create());
impl<T: Head> MessagePool<T> { impl<T: Head> MessagePool<T> {
fn create() -> &'static MessagePool<T> { fn create() -> MessagePool<T> {
let pool = MessagePool(RefCell::new(Vec::with_capacity(128))); MessagePool(RefCell::new(Vec::with_capacity(128)))
Box::leak(Box::new(pool))
} }
/// Get message from the pool /// Get message from the pool
#[inline] #[inline]
fn get_message(&'static self) -> Message<T> { fn get_message(&self) -> Message<T> {
if let Some(mut msg) = self.0.borrow_mut().pop() { if let Some(mut msg) = self.0.borrow_mut().pop() {
// Message is put in pool only when it's the last copy. // Message is put in pool only when it's the last copy.
// which means it's guaranteed to be unique when popped out. // which means it's guaranteed to be unique when popped out.
@ -463,14 +467,13 @@ impl<T: Head> MessagePool<T> {
} }
impl BoxedResponsePool { impl BoxedResponsePool {
fn create() -> &'static BoxedResponsePool { fn create() -> BoxedResponsePool {
let pool = BoxedResponsePool(RefCell::new(Vec::with_capacity(128))); BoxedResponsePool(RefCell::new(Vec::with_capacity(128)))
Box::leak(Box::new(pool))
} }
/// Get message from the pool /// Get message from the pool
#[inline] #[inline]
fn get_message(&'static self, status: StatusCode) -> BoxedResponseHead { fn get_message(&self, status: StatusCode) -> BoxedResponseHead {
if let Some(mut head) = self.0.borrow_mut().pop() { if let Some(mut head) = self.0.borrow_mut().pop() {
head.reason = None; head.reason = None;
head.status = status; head.status = status;

View File

@ -481,15 +481,14 @@ impl ResponseBuilder {
self self
} }
/// Set response content type /// Set response content type.
#[inline] #[inline]
pub fn content_type<V>(&mut self, value: V) -> &mut Self pub fn content_type<V>(&mut self, value: V) -> &mut Self
where where
HeaderValue: TryFrom<V>, V: IntoHeaderValue,
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
{ {
if let Some(parts) = parts(&mut self.head, &self.err) { if let Some(parts) = parts(&mut self.head, &self.err) {
match HeaderValue::try_from(value) { match value.try_into() {
Ok(value) => { Ok(value) => {
parts.headers.insert(header::CONTENT_TYPE, value); parts.headers.insert(header::CONTENT_TYPE, value);
} }
@ -802,7 +801,7 @@ impl From<ResponseBuilder> for Response {
impl From<&'static str> for Response { impl From<&'static str> for Response {
fn from(val: &'static str) -> Self { fn from(val: &'static str) -> Self {
Response::Ok() Response::Ok()
.content_type("text/plain; charset=utf-8") .content_type(mime::TEXT_PLAIN_UTF_8)
.body(val) .body(val)
} }
} }
@ -810,7 +809,7 @@ impl From<&'static str> for Response {
impl From<&'static [u8]> for Response { impl From<&'static [u8]> for Response {
fn from(val: &'static [u8]) -> Self { fn from(val: &'static [u8]) -> Self {
Response::Ok() Response::Ok()
.content_type("application/octet-stream") .content_type(mime::APPLICATION_OCTET_STREAM)
.body(val) .body(val)
} }
} }
@ -818,7 +817,7 @@ impl From<&'static [u8]> for Response {
impl From<String> for Response { impl From<String> for Response {
fn from(val: String) -> Self { fn from(val: String) -> Self {
Response::Ok() Response::Ok()
.content_type("text/plain; charset=utf-8") .content_type(mime::TEXT_PLAIN_UTF_8)
.body(val) .body(val)
} }
} }
@ -826,7 +825,7 @@ impl From<String> for Response {
impl<'a> From<&'a String> for Response { impl<'a> From<&'a String> for Response {
fn from(val: &'a String) -> Self { fn from(val: &'a String) -> Self {
Response::Ok() Response::Ok()
.content_type("text/plain; charset=utf-8") .content_type(mime::TEXT_PLAIN_UTF_8)
.body(val) .body(val)
} }
} }
@ -834,7 +833,7 @@ impl<'a> From<&'a String> for Response {
impl From<Bytes> for Response { impl From<Bytes> for Response {
fn from(val: Bytes) -> Self { fn from(val: Bytes) -> Self {
Response::Ok() Response::Ok()
.content_type("application/octet-stream") .content_type(mime::APPLICATION_OCTET_STREAM)
.body(val) .body(val)
} }
} }
@ -842,7 +841,7 @@ impl From<Bytes> for Response {
impl From<BytesMut> for Response { impl From<BytesMut> for Response {
fn from(val: BytesMut) -> Self { fn from(val: BytesMut) -> Self {
Response::Ok() Response::Ok()
.content_type("application/octet-stream") .content_type(mime::APPLICATION_OCTET_STREAM)
.body(val) .body(val)
} }
} }

View File

@ -89,7 +89,7 @@ impl Codec {
/// Set max frame size. /// Set max frame size.
/// ///
/// By default max size is set to 64kb. /// By default max size is set to 64kB.
pub fn max_size(mut self, size: usize) -> Self { pub fn max_size(mut self, size: usize) -> Self {
self.max_size = size; self.max_size = size;
self self

View File

@ -28,5 +28,5 @@ mime = "0.3"
twoway = "0.2" twoway = "0.2"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.0.0-beta.1" actix-rt = "2.0.0-beta.2"
actix-http = "3.0.0-beta.1" actix-http = "3.0.0-beta.1"

View File

@ -28,6 +28,6 @@ pin-project = "1.0.0"
tokio = { version = "1", features = ["sync"] } tokio = { version = "1", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
actix-rt = "2.0.0-beta.1" actix-rt = "2.0.0-beta.2"
env_logger = "0.7" env_logger = "0.7"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }

View File

@ -19,7 +19,7 @@ syn = { version = "1", features = ["full", "parsing"] }
proc-macro2 = "1" proc-macro2 = "1"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.0.0-beta.1" actix-rt = "2.0.0-beta.2"
actix-web = "4.0.0-beta.1" actix-web = "4.0.0-beta.1"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
trybuild = "1" trybuild = "1"

View File

@ -38,9 +38,9 @@ compress = ["actix-http/compress"]
[dependencies] [dependencies]
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-service = "2.0.0-beta.2" actix-service = "2.0.0-beta.3"
actix-http = "3.0.0-beta.1" actix-http = "3.0.0-beta.1"
actix-rt = "2.0.0-beta.1" actix-rt = "2.0.0-beta.2"
base64 = "0.13" base64 = "0.13"
bytes = "1" bytes = "1"

View File

@ -2,7 +2,7 @@
//! //!
//! ## Making a GET request //! ## Making a GET request
//! //!
//! ```rust //! ```no_run
//! # #[actix_rt::main] //! # #[actix_rt::main]
//! # async fn main() -> Result<(), awc::error::SendRequestError> { //! # async fn main() -> Result<(), awc::error::SendRequestError> {
//! let mut client = awc::Client::default(); //! let mut client = awc::Client::default();
@ -20,7 +20,7 @@
//! //!
//! ### Raw body contents //! ### Raw body contents
//! //!
//! ```rust //! ```no_run
//! # #[actix_rt::main] //! # #[actix_rt::main]
//! # async fn main() -> Result<(), awc::error::SendRequestError> { //! # async fn main() -> Result<(), awc::error::SendRequestError> {
//! let mut client = awc::Client::default(); //! let mut client = awc::Client::default();
@ -33,7 +33,7 @@
//! //!
//! ### Forms //! ### Forms
//! //!
//! ```rust //! ```no_run
//! # #[actix_rt::main] //! # #[actix_rt::main]
//! # async fn main() -> Result<(), awc::error::SendRequestError> { //! # async fn main() -> Result<(), awc::error::SendRequestError> {
//! let params = [("foo", "bar"), ("baz", "quux")]; //! let params = [("foo", "bar"), ("baz", "quux")];
@ -48,7 +48,7 @@
//! //!
//! ### JSON //! ### JSON
//! //!
//! ```rust //! ```no_run
//! # #[actix_rt::main] //! # #[actix_rt::main]
//! # async fn main() -> Result<(), awc::error::SendRequestError> { //! # async fn main() -> Result<(), awc::error::SendRequestError> {
//! let request = serde_json::json!({ //! let request = serde_json::json!({
@ -66,7 +66,7 @@
//! //!
//! ## WebSocket support //! ## WebSocket support
//! //!
//! ``` //! ```no_run
//! # #[actix_rt::main] //! # #[actix_rt::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> { //! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use futures_util::{sink::SinkExt, stream::StreamExt}; //! use futures_util::{sink::SinkExt, stream::StreamExt};

View File

@ -184,7 +184,7 @@ where
} }
} }
/// Change max size of payload. By default max size is 256Kb /// Change max size of payload. By default max size is 256kB
pub fn limit(mut self, limit: usize) -> Self { pub fn limit(mut self, limit: usize) -> Self {
if let Some(ref mut fut) = self.fut { if let Some(ref mut fut) = self.fut {
fut.limit = limit; fut.limit = limit;
@ -276,7 +276,7 @@ where
} }
} }
/// Change max size of payload. By default max size is 64Kb /// Change max size of payload. By default max size is 64kB
pub fn limit(mut self, limit: usize) -> Self { pub fn limit(mut self, limit: usize) -> Self {
if let Some(ref mut fut) = self.fut { if let Some(ref mut fut) = self.fut {
fut.limit = limit; fut.limit = limit;

View File

@ -4,7 +4,7 @@
//! //!
//! # Example //! # Example
//! //!
//! ``` //! ```no_run
//! use awc::{Client, ws}; //! use awc::{Client, ws};
//! use futures_util::{sink::SinkExt, stream::StreamExt}; //! use futures_util::{sink::SinkExt, stream::StreamExt};
//! //!
@ -147,7 +147,7 @@ impl WebsocketsRequest {
/// Set max frame size /// Set max frame size
/// ///
/// By default max size is set to 64kb /// By default max size is set to 64kB
pub fn max_frame_size(mut self, size: usize) -> Self { pub fn max_frame_size(mut self, size: usize) -> Self {
self.max_size = size; self.max_size = size;
self self

113
benches/responder.rs Normal file
View File

@ -0,0 +1,113 @@
use std::future::Future;
use std::time::Instant;
use actix_http::Response;
use actix_web::http::StatusCode;
use actix_web::test::TestRequest;
use actix_web::{error, Error, HttpRequest, HttpResponse, Responder};
use criterion::{criterion_group, criterion_main, Criterion};
use futures_util::future::{ready, Either, Ready};
// responder simulate the old responder trait.
trait FutureResponder {
type Error;
type Future: Future<Output = Result<HttpResponse, Self::Error>>;
fn future_respond_to(self, req: &HttpRequest) -> Self::Future;
}
// a simple option responder type.
struct OptionResponder<T>(Option<T>);
// a simple wrapper type around string
struct StringResponder(String);
impl FutureResponder for StringResponder {
type Error = Error;
type Future = Ready<Result<Response, Self::Error>>;
fn future_respond_to(self, _: &HttpRequest) -> Self::Future {
// this is default builder for string response in both new and old responder trait.
ready(Ok(Response::build(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self.0)))
}
}
impl<T> FutureResponder for OptionResponder<T>
where
T: FutureResponder,
T::Future: Future<Output = Result<Response, Error>>,
{
type Error = Error;
type Future = Either<T::Future, Ready<Result<HttpResponse, Self::Error>>>;
fn future_respond_to(self, req: &HttpRequest) -> Self::Future {
match self.0 {
Some(t) => Either::Left(t.future_respond_to(req)),
None => Either::Right(ready(Err(error::ErrorInternalServerError("err")))),
}
}
}
impl Responder for StringResponder {
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
Response::build(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self.0)
}
}
impl<T: Responder> Responder for OptionResponder<T> {
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
match self.0 {
Some(t) => t.respond_to(req),
None => Response::from_error(error::ErrorInternalServerError("err")),
}
}
}
fn future_responder(c: &mut Criterion) {
let rt = actix_rt::System::new("test");
let req = TestRequest::default().to_http_request();
c.bench_function("future_responder", move |b| {
b.iter_custom(|_| {
let futs = (0..100_000).map(|_| async {
StringResponder(String::from("Hello World!!"))
.future_respond_to(&req)
.await
});
let futs = futures_util::future::join_all(futs);
let start = Instant::now();
let _res = rt.block_on(async { futs.await });
start.elapsed()
})
});
}
fn responder(c: &mut Criterion) {
let rt = actix_rt::System::new("test");
let req = TestRequest::default().to_http_request();
c.bench_function("responder", move |b| {
b.iter_custom(|_| {
let responders =
(0..100_000).map(|_| StringResponder(String::from("Hello World!!")));
let start = Instant::now();
let _res = rt.block_on(async {
// don't need runtime block on but to be fair.
responders.map(|r| r.respond_to(&req)).collect::<Vec<_>>()
});
start.elapsed()
})
});
}
criterion_group!(responder_bench, future_responder, responder);
criterion_main!(responder_bench);

View File

@ -1,6 +1,6 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll}; use std::task::Poll;
use actix_http::{Extensions, Request, Response}; use actix_http::{Extensions, Request, Response};
use actix_router::{Path, ResourceDef, Router, Url}; use actix_router::{Path, ResourceDef, Router, Url};
@ -62,7 +62,8 @@ where
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>; type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, config: AppConfig) -> Self::Future { fn new_service(&self, config: AppConfig) -> Self::Future {
// update resource default service // set AppService's default service to 404 NotFound
// if no user defined default service exists.
let default = self.default.clone().unwrap_or_else(|| { let default = self.default.clone().unwrap_or_else(|| {
Rc::new(boxed::factory(fn_service(|req: ServiceRequest| async { Rc::new(boxed::factory(fn_service(|req: ServiceRequest| async {
Ok(req.into_response(Response::NotFound().finish())) Ok(req.into_response(Response::NotFound().finish()))
@ -141,10 +142,8 @@ where
Ok(AppInitService { Ok(AppInitService {
service, service,
rmap,
config,
app_data: Rc::new(app_data), app_data: Rc::new(app_data),
pool: HttpRequestPool::create(), app_state: AppInitServiceState::new(rmap, config),
}) })
}) })
} }
@ -156,10 +155,42 @@ where
T: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>, T: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{ {
service: T, service: T,
app_data: Rc<Extensions>,
app_state: Rc<AppInitServiceState>,
}
// a collection of AppInitService state that shared between HttpRequests.
pub(crate) struct AppInitServiceState {
rmap: Rc<ResourceMap>, rmap: Rc<ResourceMap>,
config: AppConfig, config: AppConfig,
app_data: Rc<Extensions>, pool: HttpRequestPool,
pool: &'static HttpRequestPool, }
impl AppInitServiceState {
pub(crate) fn new(rmap: Rc<ResourceMap>, config: AppConfig) -> Rc<Self> {
Rc::new(AppInitServiceState {
rmap,
config,
// TODO: AppConfig can be used to pass user defined HttpRequestPool
// capacity.
pool: HttpRequestPool::default(),
})
}
#[inline]
pub(crate) fn rmap(&self) -> &ResourceMap {
&*self.rmap
}
#[inline]
pub(crate) fn config(&self) -> &AppConfig {
&self.config
}
#[inline]
pub(crate) fn pool(&self) -> &HttpRequestPool {
&self.pool
}
} }
impl<T, B> Service<Request> for AppInitService<T, B> impl<T, B> Service<Request> for AppInitService<T, B>
@ -170,32 +201,26 @@ where
type Error = T::Error; type Error = T::Error;
type Future = T::Future; type Future = T::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::forward_ready!(service);
self.service.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future { fn call(&mut self, req: Request) -> Self::Future {
let (head, payload) = req.into_parts(); let (head, payload) = req.into_parts();
let req = if let Some(mut req) = self.pool.get_request() { let req = if let Some(mut req) = self.app_state.pool().pop() {
let inner = Rc::get_mut(&mut req.inner).unwrap(); let inner = Rc::get_mut(&mut req.inner).unwrap();
inner.path.get_mut().update(&head.uri); inner.path.get_mut().update(&head.uri);
inner.path.reset(); inner.path.reset();
inner.head = head; inner.head = head;
inner.payload = payload;
req req
} else { } else {
HttpRequest::new( HttpRequest::new(
Path::new(Url::new(head.uri.clone())), Path::new(Url::new(head.uri.clone())),
head, head,
payload, self.app_state.clone(),
self.rmap.clone(),
self.config.clone(),
self.app_data.clone(), self.app_data.clone(),
self.pool,
) )
}; };
self.service.call(ServiceRequest::new(req)) self.service.call(ServiceRequest::new(req, payload))
} }
} }
@ -204,7 +229,7 @@ where
T: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>, T: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{ {
fn drop(&mut self) { fn drop(&mut self) {
self.pool.clear(); self.app_state.pool().clear();
} }
} }

View File

@ -125,9 +125,7 @@ impl AppService {
/// Application connection config /// Application connection config
#[derive(Clone)] #[derive(Clone)]
pub struct AppConfig(Rc<AppConfigInner>); pub struct AppConfig {
struct AppConfigInner {
secure: bool, secure: bool,
host: String, host: String,
addr: SocketAddr, addr: SocketAddr,
@ -135,7 +133,7 @@ struct AppConfigInner {
impl AppConfig { impl AppConfig {
pub(crate) fn new(secure: bool, addr: SocketAddr, host: String) -> Self { pub(crate) fn new(secure: bool, addr: SocketAddr, host: String) -> Self {
AppConfig(Rc::new(AppConfigInner { secure, addr, host })) AppConfig { secure, addr, host }
} }
/// Server host name. /// Server host name.
@ -146,17 +144,17 @@ impl AppConfig {
/// ///
/// By default host name is set to a "localhost" value. /// By default host name is set to a "localhost" value.
pub fn host(&self) -> &str { pub fn host(&self) -> &str {
&self.0.host &self.host
} }
/// Returns true if connection is secure(https) /// Returns true if connection is secure(https)
pub fn secure(&self) -> bool { pub fn secure(&self) -> bool {
self.0.secure self.secure
} }
/// Returns the socket address of the local half of this TCP connection /// Returns the socket address of the local half of this TCP connection
pub fn local_addr(&self) -> SocketAddr { pub fn local_addr(&self) -> SocketAddr {
self.0.addr self.addr
} }
} }

View File

@ -1,12 +1,11 @@
//! Error and Result module //! Error and Result module
pub use actix_http::error::*; pub use actix_http::error::*;
use derive_more::{Display, From}; use derive_more::{Display, Error, From};
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use url::ParseError as UrlParseError; use url::ParseError as UrlParseError;
use crate::http::StatusCode; use crate::{http::StatusCode, HttpResponse};
use crate::HttpResponse;
/// Errors which can occur when attempting to generate resource uri. /// Errors which can occur when attempting to generate resource uri.
#[derive(Debug, PartialEq, Display, From)] #[derive(Debug, PartialEq, Display, From)]
@ -28,34 +27,37 @@ impl std::error::Error for UrlGenerationError {}
impl ResponseError for UrlGenerationError {} impl ResponseError for UrlGenerationError {}
/// A set of errors that can occur during parsing urlencoded payloads /// A set of errors that can occur during parsing urlencoded payloads
#[derive(Debug, Display, From)] #[derive(Debug, Display, Error, From)]
pub enum UrlencodedError { pub enum UrlencodedError {
/// Can not decode chunked transfer encoding /// Can not decode chunked transfer encoding.
#[display(fmt = "Can not decode chunked transfer encoding")] #[display(fmt = "Can not decode chunked transfer encoding.")]
Chunked, Chunked,
/// Payload size is bigger than allowed. (default: 256kB)
/// Payload size is larger than allowed. (default limit: 256kB).
#[display( #[display(
fmt = "Urlencoded payload size is bigger ({} bytes) than allowed (default: {} bytes)", fmt = "URL encoded payload is larger ({} bytes) than allowed (limit: {} bytes).",
size, size,
limit limit
)] )]
Overflow { size: usize, limit: usize }, Overflow { size: usize, limit: usize },
/// Payload size is now known
#[display(fmt = "Payload size is now known")] /// Payload size is now known.
#[display(fmt = "Payload size is now known.")]
UnknownLength, UnknownLength,
/// Content type error
#[display(fmt = "Content type error")] /// Content type error.
#[display(fmt = "Content type error.")]
ContentType, ContentType,
/// Parse error
#[display(fmt = "Parse error")] /// Parse error.
#[display(fmt = "Parse error.")]
Parse, Parse,
/// Payload error
#[display(fmt = "Error that occur during reading payload: {}", _0)] /// Payload error.
#[display(fmt = "Error that occur during reading payload: {}.", _0)]
Payload(PayloadError), Payload(PayloadError),
} }
impl std::error::Error for UrlencodedError {}
/// Return `BadRequest` for `UrlencodedError` /// Return `BadRequest` for `UrlencodedError`
impl ResponseError for UrlencodedError { impl ResponseError for UrlencodedError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
@ -115,16 +117,14 @@ impl ResponseError for PathError {
} }
} }
/// A set of errors that can occur during parsing query strings /// A set of errors that can occur during parsing query strings.
#[derive(Debug, Display, From)] #[derive(Debug, Display, Error, From)]
pub enum QueryPayloadError { pub enum QueryPayloadError {
/// Deserialize error /// Query deserialize error.
#[display(fmt = "Query deserialize error: {}", _0)] #[display(fmt = "Query deserialize error: {}", _0)]
Deserialize(serde::de::value::Error), Deserialize(serde::de::value::Error),
} }
impl std::error::Error for QueryPayloadError {}
/// Return `BadRequest` for `QueryPayloadError` /// Return `BadRequest` for `QueryPayloadError`
impl ResponseError for QueryPayloadError { impl ResponseError for QueryPayloadError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {

View File

@ -1,34 +1,37 @@
//! Request extractors //! Request extractors
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_http::error::Error; use std::{
use futures_util::future::{ready, Ready}; future::Future,
use futures_util::ready; pin::Pin,
task::{Context, Poll},
};
use crate::dev::Payload; use futures_util::{
use crate::request::HttpRequest; future::{ready, Ready},
ready,
};
use crate::{dev::Payload, Error, HttpRequest};
/// Trait implemented by types that can be extracted from request. /// Trait implemented by types that can be extracted from request.
/// ///
/// Types that implement this trait can be used with `Route` handlers. /// Types that implement this trait can be used with `Route` handlers.
pub trait FromRequest: Sized { pub trait FromRequest: Sized {
/// Configuration for this extractor.
type Config: Default + 'static;
/// The associated error which can be returned. /// The associated error which can be returned.
type Error: Into<Error>; type Error: Into<Error>;
/// Future that resolves to a Self /// Future that resolves to a Self.
type Future: Future<Output = Result<Self, Self::Error>>; type Future: Future<Output = Result<Self, Self::Error>>;
/// Configuration for this extractor /// Create a Self from request parts asynchronously.
type Config: Default + 'static;
/// Convert request to a Self
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future; fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future;
/// Convert request to a Self /// Create a Self from request head asynchronously.
/// ///
/// This method uses `Payload::None` as payload stream. /// This method is short for `T::from_request(req, &mut Payload::None)`.
fn extract(req: &HttpRequest) -> Self::Future { fn extract(req: &HttpRequest) -> Self::Future {
Self::from_request(req, &mut Payload::None) Self::from_request(req, &mut Payload::None)
} }

View File

@ -135,7 +135,6 @@ where
{ {
Extract(#[pin] T::Future, Option<HttpRequest>, F), Extract(#[pin] T::Future, Option<HttpRequest>, F),
Handle(#[pin] R, Option<HttpRequest>), Handle(#[pin] R, Option<HttpRequest>),
Respond(#[pin] <R::Output as Responder>::Future, Option<HttpRequest>),
} }
impl<F, T, R> Future for HandlerServiceFuture<F, T, R> impl<F, T, R> Future for HandlerServiceFuture<F, T, R>
@ -168,13 +167,8 @@ where
} }
HandlerProj::Handle(fut, req) => { HandlerProj::Handle(fut, req) => {
let res = ready!(fut.poll(cx)); let res = ready!(fut.poll(cx));
let fut = res.respond_to(req.as_ref().unwrap());
let state = HandlerServiceFuture::Respond(fut, req.take());
self.as_mut().set(state);
}
HandlerProj::Respond(fut, req) => {
let res = ready!(fut.poll(cx)).unwrap_or_else(|e| e.into().into());
let req = req.take().unwrap(); let req = req.take().unwrap();
let res = res.respond_to(&req);
return Poll::Ready(Ok(ServiceResponse::new(req, res))); return Poll::Ready(Ok(ServiceResponse::new(req, res)));
} }
} }

View File

@ -6,7 +6,8 @@
//! use actix_web::{get, web, App, HttpServer, Responder}; //! use actix_web::{get, web, App, HttpServer, Responder};
//! //!
//! #[get("/{id}/{name}/index.html")] //! #[get("/{id}/{name}/index.html")]
//! async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder { //! async fn index(path: web::Path<(u32, String)>) -> impl Responder {
//! let (id, name) = path.into_inner();
//! format!("Hello {}! id:{}", name, id) //! format!("Hello {}! id:{}", name, id)
//! } //! }
//! //!
@ -90,7 +91,7 @@ mod scope;
mod server; mod server;
mod service; mod service;
pub mod test; pub mod test;
mod types; pub(crate) mod types;
pub mod web; pub mod web;
pub use actix_http::Response as HttpResponse; pub use actix_http::Response as HttpResponse;
@ -106,6 +107,7 @@ pub use crate::responder::Responder;
pub use crate::route::Route; pub use crate::route::Route;
pub use crate::scope::Scope; pub use crate::scope::Scope;
pub use crate::server::HttpServer; pub use crate::server::HttpServer;
// TODO: is exposing the error directly really needed
pub use crate::types::{Either, EitherExtractError}; pub use crate::types::{Either, EitherExtractError};
pub mod dev { pub mod dev {

View File

@ -15,7 +15,7 @@ use std::{
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use bytes::Bytes; use bytes::Bytes;
use futures_util::future::{ok, Ready}; use futures_util::future::{ok, Ready};
use log::debug; use log::{debug, warn};
use regex::{Regex, RegexSet}; use regex::{Regex, RegexSet};
use time::OffsetDateTime; use time::OffsetDateTime;
@ -188,9 +188,8 @@ where
for unit in &self.0.format.0 { for unit in &self.0.format.0 {
// missing request replacement function diagnostic // missing request replacement function diagnostic
if let FormatText::CustomRequest(label, None) = unit { if let FormatText::CustomRequest(label, None) = unit {
debug!( warn!(
"No custom request replacement function was registered for label {} in\ "No custom request replacement function was registered for label \"{}\".",
logger format.",
label label
); );
} }

View File

@ -8,6 +8,7 @@ use actix_router::{Path, Url};
use futures_util::future::{ok, Ready}; use futures_util::future::{ok, Ready};
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::app_service::AppInitServiceState;
use crate::config::AppConfig; use crate::config::AppConfig;
use crate::error::UrlGenerationError; use crate::error::UrlGenerationError;
use crate::extract::FromRequest; use crate::extract::FromRequest;
@ -17,20 +18,18 @@ use crate::rmap::ResourceMap;
#[derive(Clone)] #[derive(Clone)]
/// An HTTP Request /// An HTTP Request
pub struct HttpRequest { pub struct HttpRequest {
// *. Rc<HttpRequestInner> is used exclusively and NO Weak<HttpRequestInner> /// # Panics
// is allowed anywhere in the code. Weak pointer is purposely ignored when /// `Rc<HttpRequestInner>` is used exclusively and NO `Weak<HttpRequestInner>`
// doing Rc's ref counter check. /// is allowed anywhere in the code. Weak pointer is purposely ignored when
/// doing `Rc`'s ref counter check. Expect panics if this invariant is violated.
pub(crate) inner: Rc<HttpRequestInner>, pub(crate) inner: Rc<HttpRequestInner>,
} }
pub(crate) struct HttpRequestInner { pub(crate) struct HttpRequestInner {
pub(crate) head: Message<RequestHead>, pub(crate) head: Message<RequestHead>,
pub(crate) path: Path<Url>, pub(crate) path: Path<Url>,
pub(crate) payload: Payload,
pub(crate) app_data: SmallVec<[Rc<Extensions>; 4]>, pub(crate) app_data: SmallVec<[Rc<Extensions>; 4]>,
rmap: Rc<ResourceMap>, app_state: Rc<AppInitServiceState>,
config: AppConfig,
pool: &'static HttpRequestPool,
} }
impl HttpRequest { impl HttpRequest {
@ -38,11 +37,8 @@ impl HttpRequest {
pub(crate) fn new( pub(crate) fn new(
path: Path<Url>, path: Path<Url>,
head: Message<RequestHead>, head: Message<RequestHead>,
payload: Payload, app_state: Rc<AppInitServiceState>,
rmap: Rc<ResourceMap>,
config: AppConfig,
app_data: Rc<Extensions>, app_data: Rc<Extensions>,
pool: &'static HttpRequestPool,
) -> HttpRequest { ) -> HttpRequest {
let mut data = SmallVec::<[Rc<Extensions>; 4]>::new(); let mut data = SmallVec::<[Rc<Extensions>; 4]>::new();
data.push(app_data); data.push(app_data);
@ -51,11 +47,8 @@ impl HttpRequest {
inner: Rc::new(HttpRequestInner { inner: Rc::new(HttpRequestInner {
head, head,
path, path,
payload, app_state,
rmap,
config,
app_data: data, app_data: data,
pool,
}), }),
} }
} }
@ -141,7 +134,7 @@ impl HttpRequest {
/// Returns a None when no resource is fully matched, including default services. /// Returns a None when no resource is fully matched, including default services.
#[inline] #[inline]
pub fn match_pattern(&self) -> Option<String> { pub fn match_pattern(&self) -> Option<String> {
self.inner.rmap.match_pattern(self.path()) self.resource_map().match_pattern(self.path())
} }
/// The resource name that matched the path. Useful for logging and metrics. /// The resource name that matched the path. Useful for logging and metrics.
@ -149,7 +142,7 @@ impl HttpRequest {
/// Returns a None when no resource is fully matched, including default services. /// Returns a None when no resource is fully matched, including default services.
#[inline] #[inline]
pub fn match_name(&self) -> Option<&str> { pub fn match_name(&self) -> Option<&str> {
self.inner.rmap.match_name(self.path()) self.resource_map().match_name(self.path())
} }
/// Request extensions /// Request extensions
@ -191,7 +184,7 @@ impl HttpRequest {
U: IntoIterator<Item = I>, U: IntoIterator<Item = I>,
I: AsRef<str>, I: AsRef<str>,
{ {
self.inner.rmap.url_for(&self, name, elements) self.resource_map().url_for(&self, name, elements)
} }
/// Generate url for named resource /// Generate url for named resource
@ -206,7 +199,7 @@ impl HttpRequest {
#[inline] #[inline]
/// Get a reference to a `ResourceMap` of current application. /// Get a reference to a `ResourceMap` of current application.
pub fn resource_map(&self) -> &ResourceMap { pub fn resource_map(&self) -> &ResourceMap {
&self.inner.rmap &self.app_state().rmap()
} }
/// Peer socket address /// Peer socket address
@ -226,13 +219,13 @@ impl HttpRequest {
/// borrowed. /// borrowed.
#[inline] #[inline]
pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> { pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
ConnectionInfo::get(self.head(), &*self.app_config()) ConnectionInfo::get(self.head(), self.app_config())
} }
/// App config /// App config
#[inline] #[inline]
pub fn app_config(&self) -> &AppConfig { pub fn app_config(&self) -> &AppConfig {
&self.inner.config self.app_state().config()
} }
/// Get an application data object stored with `App::data` or `App::app_data` /// Get an application data object stored with `App::data` or `App::app_data`
@ -252,6 +245,11 @@ impl HttpRequest {
None None
} }
#[inline]
fn app_state(&self) -> &AppInitServiceState {
&*self.inner.app_state
}
} }
impl HttpMessage for HttpRequest { impl HttpMessage for HttpRequest {
@ -287,14 +285,16 @@ impl Drop for HttpRequest {
// This relies on no Weak<HttpRequestInner> exists anywhere.(There is none) // This relies on no Weak<HttpRequestInner> exists anywhere.(There is none)
if let Some(inner) = Rc::get_mut(&mut self.inner) { if let Some(inner) = Rc::get_mut(&mut self.inner) {
let v = &mut inner.pool.0.borrow_mut(); if inner.app_state.pool().is_available() {
if v.len() < 128 {
// clear additional app_data and keep the root one for reuse. // clear additional app_data and keep the root one for reuse.
inner.app_data.truncate(1); inner.app_data.truncate(1);
// inner is borrowed mut here. get head's Extension mutably // inner is borrowed mut here. get head's Extension mutably
// to reduce borrow check // to reduce borrow check
inner.head.extensions.get_mut().clear(); inner.head.extensions.get_mut().clear();
v.push(self.inner.clone());
// a re-borrow of pool is necessary here.
let req = self.inner.clone();
self.app_state().pool().push(req);
} }
} }
} }
@ -363,25 +363,50 @@ impl fmt::Debug for HttpRequest {
/// Request objects are added when they are dropped (see `<HttpRequest as Drop>::drop`) and re-used /// Request objects are added when they are dropped (see `<HttpRequest as Drop>::drop`) and re-used
/// in `<AppInitService as Service>::call` when there are available objects in the list. /// in `<AppInitService as Service>::call` when there are available objects in the list.
/// ///
/// The pool's initial capacity is 128 items. /// The pool's default capacity is 128 items.
pub(crate) struct HttpRequestPool(RefCell<Vec<Rc<HttpRequestInner>>>); pub(crate) struct HttpRequestPool {
inner: RefCell<Vec<Rc<HttpRequestInner>>>,
cap: usize,
}
impl Default for HttpRequestPool {
fn default() -> Self {
Self::with_capacity(128)
}
}
impl HttpRequestPool { impl HttpRequestPool {
/// Allocates a slab of memory for pool use. pub(crate) fn with_capacity(cap: usize) -> Self {
pub(crate) fn create() -> &'static HttpRequestPool { HttpRequestPool {
let pool = HttpRequestPool(RefCell::new(Vec::with_capacity(128))); inner: RefCell::new(Vec::with_capacity(cap)),
Box::leak(Box::new(pool)) cap,
}
} }
/// Re-use a previously allocated (but now completed/discarded) HttpRequest object. /// Re-use a previously allocated (but now completed/discarded) HttpRequest object.
#[inline] #[inline]
pub(crate) fn get_request(&self) -> Option<HttpRequest> { pub(crate) fn pop(&self) -> Option<HttpRequest> {
self.0.borrow_mut().pop().map(|inner| HttpRequest { inner }) self.inner
.borrow_mut()
.pop()
.map(|inner| HttpRequest { inner })
}
/// Check if the pool still has capacity for request storage.
#[inline]
pub(crate) fn is_available(&self) -> bool {
self.inner.borrow_mut().len() < self.cap
}
/// Push a request to pool.
#[inline]
pub(crate) fn push(&self, req: Rc<HttpRequestInner>) {
self.inner.borrow_mut().push(req);
} }
/// Clears all allocated HttpRequest objects. /// Clears all allocated HttpRequest objects.
pub(crate) fn clear(&self) { pub(crate) fn clear(&self) {
self.0.borrow_mut().clear() self.inner.borrow_mut().clear()
} }
} }
@ -527,6 +552,25 @@ mod tests {
); );
} }
#[actix_rt::test]
async fn test_drop_http_request_pool() {
let mut srv = init_service(App::new().service(web::resource("/").to(
|req: HttpRequest| {
HttpResponse::Ok()
.set_header("pool_cap", req.app_state().pool().cap)
.finish()
},
)))
.await;
let req = TestRequest::default().to_request();
let resp = call_service(&mut srv, req).await;
drop(srv);
assert_eq!(resp.headers().get("pool_cap").unwrap(), "128");
}
#[actix_rt::test] #[actix_rt::test]
async fn test_data() { async fn test_data() {
let mut srv = init_service(App::new().app_data(10usize).service( let mut srv = init_service(App::new().app_data(10usize).service(

View File

@ -1,18 +1,18 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt; use std::fmt;
use std::future::Future; use std::future::Future;
use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll}; use std::task::Poll;
use actix_http::{Error, Extensions, Response}; use actix_http::{Error, Extensions, Response};
use actix_router::IntoPattern; use actix_router::IntoPattern;
use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::boxed::{self, BoxService, BoxServiceFactory};
use actix_service::{ use actix_service::{
apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, apply, apply_fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory,
ServiceFactoryExt, Transform, ServiceFactoryExt, Transform,
}; };
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use futures_util::future::join_all;
use crate::data::Data; use crate::data::Data;
use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef};
@ -20,7 +20,7 @@ use crate::extract::FromRequest;
use crate::guard::Guard; use crate::guard::Guard;
use crate::handler::Handler; use crate::handler::Handler;
use crate::responder::Responder; use crate::responder::Responder;
use crate::route::{CreateRouteService, Route, RouteService}; use crate::route::{Route, RouteService};
use crate::service::{ServiceRequest, ServiceResponse}; use crate::service::{ServiceRequest, ServiceResponse};
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>; type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
@ -53,9 +53,9 @@ pub struct Resource<T = ResourceEndpoint> {
rdef: Vec<String>, rdef: Vec<String>,
name: Option<String>, name: Option<String>,
routes: Vec<Route>, routes: Vec<Route>,
data: Option<Extensions>, app_data: Option<Extensions>,
guards: Vec<Box<dyn Guard>>, guards: Vec<Box<dyn Guard>>,
default: Rc<RefCell<Option<Rc<HttpNewService>>>>, default: HttpNewService,
factory_ref: Rc<RefCell<Option<ResourceFactory>>>, factory_ref: Rc<RefCell<Option<ResourceFactory>>>,
} }
@ -70,8 +70,10 @@ impl Resource {
endpoint: ResourceEndpoint::new(fref.clone()), endpoint: ResourceEndpoint::new(fref.clone()),
factory_ref: fref, factory_ref: fref,
guards: Vec::new(), guards: Vec::new(),
data: None, app_data: None,
default: Rc::new(RefCell::new(None)), default: boxed::factory(fn_service(|req: ServiceRequest| async {
Ok(req.into_response(Response::MethodNotAllowed().finish()))
})),
} }
} }
} }
@ -201,10 +203,10 @@ where
/// ///
/// Data of different types from parent contexts will still be accessible. /// Data of different types from parent contexts will still be accessible.
pub fn app_data<U: 'static>(mut self, data: U) -> Self { pub fn app_data<U: 'static>(mut self, data: U) -> Self {
if self.data.is_none() { if self.app_data.is_none() {
self.data = Some(Extensions::new()); self.app_data = Some(Extensions::new());
} }
self.data.as_mut().unwrap().insert(data); self.app_data.as_mut().unwrap().insert(data);
self self
} }
@ -274,7 +276,7 @@ where
guards: self.guards, guards: self.guards,
routes: self.routes, routes: self.routes,
default: self.default, default: self.default,
data: self.data, app_data: self.app_data,
factory_ref: self.factory_ref, factory_ref: self.factory_ref,
} }
} }
@ -336,7 +338,7 @@ where
guards: self.guards, guards: self.guards,
routes: self.routes, routes: self.routes,
default: self.default, default: self.default,
data: self.data, app_data: self.app_data,
factory_ref: self.factory_ref, factory_ref: self.factory_ref,
} }
} }
@ -356,11 +358,9 @@ where
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
// create and configure default resource // create and configure default resource
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory( self.default = boxed::factory(f.into_factory().map_init_err(|e| {
f.into_factory().map_init_err(|e| { log::error!("Can not construct default service: {:?}", e)
log::error!("Can not construct default service: {:?}", e) }));
}),
)))));
self self
} }
@ -391,7 +391,7 @@ where
*rdef.name_mut() = name.clone(); *rdef.name_mut() = name.clone();
} }
// custom app data storage // custom app data storage
if let Some(ref mut ext) = self.data { if let Some(ref mut ext) = self.app_data {
config.set_service_data(ext); config.set_service_data(ext);
} }
@ -412,7 +412,7 @@ where
fn into_factory(self) -> T { fn into_factory(self) -> T {
*self.factory_ref.borrow_mut() = Some(ResourceFactory { *self.factory_ref.borrow_mut() = Some(ResourceFactory {
routes: self.routes, routes: self.routes,
data: self.data.map(Rc::new), app_data: self.app_data.map(Rc::new),
default: self.default, default: self.default,
}); });
@ -422,8 +422,8 @@ where
pub struct ResourceFactory { pub struct ResourceFactory {
routes: Vec<Route>, routes: Vec<Route>,
data: Option<Rc<Extensions>>, app_data: Option<Rc<Extensions>>,
default: Rc<RefCell<Option<Rc<HttpNewService>>>>, default: HttpNewService,
} }
impl ServiceFactory<ServiceRequest> for ResourceFactory { impl ServiceFactory<ServiceRequest> for ResourceFactory {
@ -432,126 +432,60 @@ impl ServiceFactory<ServiceRequest> for ResourceFactory {
type Config = (); type Config = ();
type Service = ResourceService; type Service = ResourceService;
type InitError = (); type InitError = ();
type Future = CreateResourceService; type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
let default_fut = if let Some(ref default) = *self.default.borrow() { // construct default service factory future.
Some(default.new_service(())) let default_fut = self.default.new_service(());
} else {
None
};
CreateResourceService { // construct route service factory futures
fut: self let factory_fut =
.routes join_all(self.routes.iter().map(|route| route.new_service(())));
.iter()
.map(|route| CreateRouteServiceItem::Future(route.new_service(())))
.collect(),
data: self.data.clone(),
default: None,
default_fut,
}
}
}
enum CreateRouteServiceItem { let app_data = self.app_data.clone();
Future(CreateRouteService),
Service(RouteService),
}
pub struct CreateResourceService { Box::pin(async move {
fut: Vec<CreateRouteServiceItem>, let default = default_fut.await?;
data: Option<Rc<Extensions>>, let routes = factory_fut
default: Option<HttpService>, .await
default_fut: Option<LocalBoxFuture<'static, Result<HttpService, ()>>>, .into_iter()
} .collect::<Result<Vec<_>, _>>()?;
impl Future for CreateResourceService { Ok(ResourceService {
type Output = Result<ResourceService, ()>; app_data,
default,
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 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 Pin::new(fut)
.poll(cx)?
{
Poll::Ready(route) => *item = CreateRouteServiceItem::Service(route),
Poll::Pending => {
done = false;
}
},
CreateRouteServiceItem::Service(_) => continue,
};
}
if done {
let routes = self
.fut
.drain(..)
.map(|item| match item {
CreateRouteServiceItem::Service(service) => service,
CreateRouteServiceItem::Future(_) => unreachable!(),
})
.collect();
Poll::Ready(Ok(ResourceService {
routes, routes,
data: self.data.clone(), })
default: self.default.take(), })
}))
} else {
Poll::Pending
}
} }
} }
pub struct ResourceService { pub struct ResourceService {
routes: Vec<RouteService>, routes: Vec<RouteService>,
data: Option<Rc<Extensions>>, app_data: Option<Rc<Extensions>>,
default: Option<HttpService>, default: HttpService,
} }
impl Service<ServiceRequest> for ResourceService { impl Service<ServiceRequest> for ResourceService {
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Future = LocalBoxFuture<'static, Result<ServiceResponse, Error>>; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, mut req: ServiceRequest) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
for route in self.routes.iter_mut() { for route in self.routes.iter_mut() {
if route.check(&mut req) { if route.check(&mut req) {
if let Some(ref data) = self.data { if let Some(ref app_data) = self.app_data {
req.add_data_container(data.clone()); req.add_data_container(app_data.clone());
} }
return route.call(req); return route.call(req);
} }
} }
if let Some(ref mut default) = self.default { if let Some(ref app_data) = self.app_data {
if let Some(ref data) = self.data { req.add_data_container(app_data.clone());
req.add_data_container(data.clone());
}
default.call(req)
} else {
let req = req.into_parts().0;
Box::pin(async {
Ok(ServiceResponse::new(
req,
Response::MethodNotAllowed().finish(),
))
})
} }
self.default.call(req)
} }
} }
@ -567,15 +501,15 @@ impl ResourceEndpoint {
} }
impl ServiceFactory<ServiceRequest> for ResourceEndpoint { impl ServiceFactory<ServiceRequest> for ResourceEndpoint {
type Config = ();
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type InitError = (); type Config = ();
type Service = ResourceService; type Service = ResourceService;
type Future = CreateResourceService; type InitError = ();
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
self.factory.borrow_mut().as_mut().unwrap().new_service(()) self.factory.borrow().as_ref().unwrap().new_service(())
} }
} }

View File

@ -1,33 +1,20 @@
use std::convert::TryFrom; use std::convert::TryFrom;
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::error::InternalError;
use actix_http::http::{ use actix_http::http::{
header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, StatusCode, header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, StatusCode,
}; };
use actix_http::{Error, Response, ResponseBuilder}; use actix_http::ResponseBuilder;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures_util::future::{err, ok, Either as EitherFuture, Ready};
use futures_util::ready;
use pin_project::pin_project;
use crate::request::HttpRequest; use crate::{Error, HttpRequest, HttpResponse};
/// Trait implemented by types that can be converted to a http response. /// Trait implemented by types that can be converted to a http response.
/// ///
/// Types that implement this trait can be used as the return type of a handler. /// Types that implement this trait can be used as the return type of a handler.
pub trait Responder { pub trait Responder {
/// The associated error which can be returned. /// Convert self to `HttpResponse`.
type Error: Into<Error>; fn respond_to(self, req: &HttpRequest) -> HttpResponse;
/// The future response value.
type Future: Future<Output = Result<Response, Self::Error>>;
/// Convert itself to `AsyncResult` or `Error`.
fn respond_to(self, req: &HttpRequest) -> Self::Future;
/// Override a status code for a Responder. /// Override a status code for a Responder.
/// ///
@ -76,29 +63,18 @@ pub trait Responder {
} }
} }
impl Responder for Response { impl Responder for HttpResponse {
type Error = Error;
type Future = Ready<Result<Response, Error>>;
#[inline] #[inline]
fn respond_to(self, _: &HttpRequest) -> Self::Future { fn respond_to(self, _: &HttpRequest) -> HttpResponse {
ok(self) self
} }
} }
impl<T> Responder for Option<T> impl<T: Responder> Responder for Option<T> {
where fn respond_to(self, req: &HttpRequest) -> HttpResponse {
T: Responder,
{
type Error = T::Error;
type Future = EitherFuture<T::Future, Ready<Result<Response, T::Error>>>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
match self { match self {
Some(t) => EitherFuture::Left(t.respond_to(req)), Some(t) => t.respond_to(req),
None => { None => HttpResponse::build(StatusCode::NOT_FOUND).finish(),
EitherFuture::Right(ok(Response::build(StatusCode::NOT_FOUND).finish()))
}
} }
} }
} }
@ -108,109 +84,74 @@ where
T: Responder, T: Responder,
E: Into<Error>, E: Into<Error>,
{ {
type Error = Error; fn respond_to(self, req: &HttpRequest) -> HttpResponse {
type Future = EitherFuture<
ResponseFuture<T::Future, T::Error>,
Ready<Result<Response, Error>>,
>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
match self { match self {
Ok(val) => EitherFuture::Left(ResponseFuture::new(val.respond_to(req))), Ok(val) => val.respond_to(req),
Err(e) => EitherFuture::Right(err(e.into())), Err(e) => HttpResponse::from_error(e.into()),
} }
} }
} }
impl Responder for ResponseBuilder { impl Responder for ResponseBuilder {
type Error = Error;
type Future = Ready<Result<Response, Error>>;
#[inline] #[inline]
fn respond_to(mut self, _: &HttpRequest) -> Self::Future { fn respond_to(mut self, _: &HttpRequest) -> HttpResponse {
ok(self.finish()) self.finish()
} }
} }
impl<T> Responder for (T, StatusCode) impl<T: Responder> Responder for (T, StatusCode) {
where fn respond_to(self, req: &HttpRequest) -> HttpResponse {
T: Responder, let mut res = self.0.respond_to(req);
{ *res.status_mut() = self.1;
type Error = T::Error; res
type Future = CustomResponderFut<T>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
CustomResponderFut {
fut: self.0.respond_to(req),
status: Some(self.1),
headers: None,
}
} }
} }
impl Responder for &'static str { impl Responder for &'static str {
type Error = Error; fn respond_to(self, _: &HttpRequest) -> HttpResponse {
type Future = Ready<Result<Response, Error>>; HttpResponse::Ok()
.content_type(mime::TEXT_PLAIN_UTF_8)
fn respond_to(self, _: &HttpRequest) -> Self::Future { .body(self)
ok(Response::build(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self))
} }
} }
impl Responder for &'static [u8] { impl Responder for &'static [u8] {
type Error = Error; fn respond_to(self, _: &HttpRequest) -> HttpResponse {
type Future = Ready<Result<Response, Error>>; HttpResponse::Ok()
.content_type(mime::APPLICATION_OCTET_STREAM)
fn respond_to(self, _: &HttpRequest) -> Self::Future { .body(self)
ok(Response::build(StatusCode::OK)
.content_type("application/octet-stream")
.body(self))
} }
} }
impl Responder for String { impl Responder for String {
type Error = Error; fn respond_to(self, _: &HttpRequest) -> HttpResponse {
type Future = Ready<Result<Response, Error>>; HttpResponse::Ok()
.content_type(mime::TEXT_PLAIN_UTF_8)
fn respond_to(self, _: &HttpRequest) -> Self::Future { .body(self)
ok(Response::build(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self))
} }
} }
impl<'a> Responder for &'a String { impl<'a> Responder for &'a String {
type Error = Error; fn respond_to(self, _: &HttpRequest) -> HttpResponse {
type Future = Ready<Result<Response, Error>>; HttpResponse::Ok()
.content_type(mime::TEXT_PLAIN_UTF_8)
fn respond_to(self, _: &HttpRequest) -> Self::Future { .body(self)
ok(Response::build(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self))
} }
} }
impl Responder for Bytes { impl Responder for Bytes {
type Error = Error; fn respond_to(self, _: &HttpRequest) -> HttpResponse {
type Future = Ready<Result<Response, Error>>; HttpResponse::Ok()
.content_type(mime::APPLICATION_OCTET_STREAM)
fn respond_to(self, _: &HttpRequest) -> Self::Future { .body(self)
ok(Response::build(StatusCode::OK)
.content_type("application/octet-stream")
.body(self))
} }
} }
impl Responder for BytesMut { impl Responder for BytesMut {
type Error = Error; fn respond_to(self, _: &HttpRequest) -> HttpResponse {
type Future = Ready<Result<Response, Error>>; HttpResponse::Ok()
.content_type(mime::APPLICATION_OCTET_STREAM)
fn respond_to(self, _: &HttpRequest) -> Self::Future { .body(self)
ok(Response::build(StatusCode::OK)
.content_type("application/octet-stream")
.body(self))
} }
} }
@ -290,45 +231,20 @@ impl<T: Responder> CustomResponder<T> {
} }
impl<T: Responder> Responder for CustomResponder<T> { impl<T: Responder> Responder for CustomResponder<T> {
type Error = T::Error; fn respond_to(self, req: &HttpRequest) -> HttpResponse {
type Future = CustomResponderFut<T>; let mut res = self.responder.respond_to(req);
fn respond_to(self, req: &HttpRequest) -> Self::Future { if let Some(status) = self.status {
CustomResponderFut {
fut: self.responder.respond_to(req),
status: self.status,
headers: self.headers,
}
}
}
#[pin_project]
pub struct CustomResponderFut<T: Responder> {
#[pin]
fut: T::Future,
status: Option<StatusCode>,
headers: Option<HeaderMap>,
}
impl<T: Responder> Future for CustomResponderFut<T> {
type Output = Result<Response, T::Error>;
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; *res.status_mut() = status;
} }
if let Some(ref headers) = this.headers {
if let Some(ref headers) = self.headers {
for (k, v) in headers { for (k, v) in headers {
res.headers_mut().insert(k.clone(), v.clone()); res.headers_mut().insert(k.clone(), v.clone());
} }
} }
Poll::Ready(Ok(res))
res
} }
} }
@ -336,40 +252,8 @@ impl<T> Responder for InternalError<T>
where where
T: std::fmt::Debug + std::fmt::Display + 'static, T: std::fmt::Debug + std::fmt::Display + 'static,
{ {
type Error = Error; fn respond_to(self, _: &HttpRequest) -> HttpResponse {
type Future = Ready<Result<Response, Error>>; HttpResponse::from_error(self.into())
fn respond_to(self, _: &HttpRequest) -> Self::Future {
let err: Error = self.into();
ok(err.into())
}
}
#[pin_project]
pub struct ResponseFuture<T, E> {
#[pin]
fut: T,
_phantom: PhantomData<E>,
}
impl<T, E> ResponseFuture<T, E> {
pub fn new(fut: T) -> Self {
ResponseFuture {
fut,
_phantom: PhantomData,
}
}
}
impl<T, E> Future for ResponseFuture<T, E>
where
T: Future<Output = Result<Response, E>>,
E: Into<Error>,
{
type Output = Result<Response, Error>;
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()))
} }
} }
@ -382,7 +266,7 @@ pub(crate) mod tests {
use crate::dev::{Body, ResponseBody}; use crate::dev::{Body, ResponseBody};
use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
use crate::test::{init_service, TestRequest}; use crate::test::{init_service, TestRequest};
use crate::{error, web, App, HttpResponse}; use crate::{error, web, App};
#[actix_rt::test] #[actix_rt::test]
async fn test_option_responder() { async fn test_option_responder() {
@ -441,7 +325,7 @@ pub(crate) mod tests {
async fn test_responder() { async fn test_responder() {
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
let resp: HttpResponse = "test".respond_to(&req).await.unwrap(); let resp = "test".respond_to(&req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!( assert_eq!(
@ -449,7 +333,7 @@ pub(crate) mod tests {
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
let resp: HttpResponse = b"test".respond_to(&req).await.unwrap(); let resp = b"test".respond_to(&req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!( assert_eq!(
@ -457,7 +341,7 @@ pub(crate) mod tests {
HeaderValue::from_static("application/octet-stream") HeaderValue::from_static("application/octet-stream")
); );
let resp: HttpResponse = "test".to_string().respond_to(&req).await.unwrap(); let resp = "test".to_string().respond_to(&req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!( assert_eq!(
@ -465,7 +349,7 @@ pub(crate) mod tests {
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
let resp: HttpResponse = (&"test".to_string()).respond_to(&req).await.unwrap(); let resp = (&"test".to_string()).respond_to(&req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!( assert_eq!(
@ -473,8 +357,7 @@ pub(crate) mod tests {
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
let resp: HttpResponse = let resp = Bytes::from_static(b"test").respond_to(&req);
Bytes::from_static(b"test").respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!( assert_eq!(
@ -482,10 +365,7 @@ pub(crate) mod tests {
HeaderValue::from_static("application/octet-stream") HeaderValue::from_static("application/octet-stream")
); );
let resp: HttpResponse = BytesMut::from(b"test".as_ref()) let resp = BytesMut::from(b"test".as_ref()).respond_to(&req);
.respond_to(&req)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!( assert_eq!(
@ -494,11 +374,8 @@ pub(crate) mod tests {
); );
// InternalError // InternalError
let resp: HttpResponse = let resp =
error::InternalError::new("err", StatusCode::BAD_REQUEST) error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req);
.respond_to(&req)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
} }
@ -507,10 +384,7 @@ pub(crate) mod tests {
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
// Result<I, E> // Result<I, E>
let resp: HttpResponse = Ok::<_, Error>("test".to_string()) let resp = Ok::<_, Error>("test".to_string()).respond_to(&req);
.respond_to(&req)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!(resp.body().bin_ref(), b"test");
assert_eq!( assert_eq!(
@ -520,9 +394,9 @@ pub(crate) mod tests {
let res = let res =
Err::<String, _>(error::InternalError::new("err", StatusCode::BAD_REQUEST)) Err::<String, _>(error::InternalError::new("err", StatusCode::BAD_REQUEST))
.respond_to(&req) .respond_to(&req);
.await;
assert!(res.is_err()); assert_eq!(res.status(), StatusCode::BAD_REQUEST);
} }
#[actix_rt::test] #[actix_rt::test]
@ -531,18 +405,15 @@ pub(crate) mod tests {
let res = "test" let res = "test"
.to_string() .to_string()
.with_status(StatusCode::BAD_REQUEST) .with_status(StatusCode::BAD_REQUEST)
.respond_to(&req) .respond_to(&req);
.await
.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST); assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(res.body().bin_ref(), b"test"); assert_eq!(res.body().bin_ref(), b"test");
let res = "test" let res = "test"
.to_string() .to_string()
.with_header("content-type", "json") .with_header("content-type", "json")
.respond_to(&req) .respond_to(&req);
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.body().bin_ref(), b"test"); assert_eq!(res.body().bin_ref(), b"test");
@ -555,19 +426,14 @@ pub(crate) mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_tuple_responder_with_status_code() { async fn test_tuple_responder_with_status_code() {
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
let res = ("test".to_string(), StatusCode::BAD_REQUEST) let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req);
.respond_to(&req)
.await
.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST); assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(res.body().bin_ref(), b"test"); assert_eq!(res.body().bin_ref(), b"test");
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
let res = ("test".to_string(), StatusCode::OK) let res = ("test".to_string(), StatusCode::OK)
.with_header("content-type", "json") .with_header("content-type", "json")
.respond_to(&req) .respond_to(&req);
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.body().bin_ref(), b"test"); assert_eq!(res.body().bin_ref(), b"test");
assert_eq!( assert_eq!(

View File

@ -1,18 +1,18 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt; use std::fmt;
use std::future::Future; use std::future::Future;
use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll}; use std::task::Poll;
use actix_http::{Extensions, Response}; use actix_http::Extensions;
use actix_router::{ResourceDef, ResourceInfo, Router}; use actix_router::{ResourceDef, Router};
use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::boxed::{self, BoxService, BoxServiceFactory};
use actix_service::{ use actix_service::{
apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory,
ServiceFactoryExt, Transform, ServiceFactoryExt, Transform,
}; };
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use futures_util::future::join_all;
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::data::Data; use crate::data::Data;
@ -61,10 +61,10 @@ type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Err
pub struct Scope<T = ScopeEndpoint> { pub struct Scope<T = ScopeEndpoint> {
endpoint: T, endpoint: T,
rdef: String, rdef: String,
data: Option<Extensions>, app_data: Option<Extensions>,
services: Vec<Box<dyn AppServiceFactory>>, services: Vec<Box<dyn AppServiceFactory>>,
guards: Vec<Box<dyn Guard>>, guards: Vec<Box<dyn Guard>>,
default: Rc<RefCell<Option<Rc<HttpNewService>>>>, default: Option<Rc<HttpNewService>>,
external: Vec<ResourceDef>, external: Vec<ResourceDef>,
factory_ref: Rc<RefCell<Option<ScopeFactory>>>, factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
} }
@ -76,10 +76,10 @@ impl Scope {
Scope { Scope {
endpoint: ScopeEndpoint::new(fref.clone()), endpoint: ScopeEndpoint::new(fref.clone()),
rdef: path.to_string(), rdef: path.to_string(),
data: None, app_data: None,
guards: Vec::new(), guards: Vec::new(),
services: Vec::new(), services: Vec::new(),
default: Rc::new(RefCell::new(None)), default: None,
external: Vec::new(), external: Vec::new(),
factory_ref: fref, factory_ref: fref,
} }
@ -155,10 +155,10 @@ where
/// ///
/// Data of different types from parent contexts will still be accessible. /// Data of different types from parent contexts will still be accessible.
pub fn app_data<U: 'static>(mut self, data: U) -> Self { pub fn app_data<U: 'static>(mut self, data: U) -> Self {
if self.data.is_none() { if self.app_data.is_none() {
self.data = Some(Extensions::new()); self.app_data = Some(Extensions::new());
} }
self.data.as_mut().unwrap().insert(data); self.app_data.as_mut().unwrap().insert(data);
self self
} }
@ -201,15 +201,15 @@ where
self.external.extend(cfg.external); self.external.extend(cfg.external);
if !cfg.data.is_empty() { if !cfg.data.is_empty() {
let mut data = self.data.unwrap_or_else(Extensions::new); let mut data = self.app_data.unwrap_or_else(Extensions::new);
for value in cfg.data.iter() { for value in cfg.data.iter() {
value.create(&mut data); value.create(&mut data);
} }
self.data = Some(data); self.app_data = Some(data);
} }
self.data self.app_data
.get_or_insert_with(Extensions::new) .get_or_insert_with(Extensions::new)
.extend(cfg.extensions); .extend(cfg.extensions);
self self
@ -295,11 +295,9 @@ where
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
// create and configure default resource // create and configure default resource
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory( self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err(
f.into_factory().map_init_err(|e| { |e| log::error!("Can not construct default service: {:?}", e),
log::error!("Can not construct default service: {:?}", e) ))));
}),
)))));
self self
} }
@ -337,7 +335,7 @@ where
Scope { Scope {
endpoint: apply(mw, self.endpoint), endpoint: apply(mw, self.endpoint),
rdef: self.rdef, rdef: self.rdef,
data: self.data, app_data: self.app_data,
guards: self.guards, guards: self.guards,
services: self.services, services: self.services,
default: self.default, default: self.default,
@ -397,7 +395,7 @@ where
Scope { Scope {
endpoint: apply_fn_factory(self.endpoint, mw), endpoint: apply_fn_factory(self.endpoint, mw),
rdef: self.rdef, rdef: self.rdef,
data: self.data, app_data: self.app_data,
guards: self.guards, guards: self.guards,
services: self.services, services: self.services,
default: self.default, default: self.default,
@ -419,9 +417,7 @@ where
{ {
fn register(mut self, config: &mut AppService) { fn register(mut self, config: &mut AppService) {
// update default resource if needed // update default resource if needed
if self.default.borrow().is_none() { let default = self.default.unwrap_or_else(|| config.default_service());
*self.default.borrow_mut() = Some(config.default_service());
}
// register nested services // register nested services
let mut cfg = config.clone_config(); let mut cfg = config.clone_config();
@ -437,14 +433,14 @@ where
} }
// custom app data storage // custom app data storage
if let Some(ref mut ext) = self.data { if let Some(ref mut ext) = self.app_data {
config.set_service_data(ext); config.set_service_data(ext);
} }
// complete scope pipeline creation // complete scope pipeline creation
*self.factory_ref.borrow_mut() = Some(ScopeFactory { *self.factory_ref.borrow_mut() = Some(ScopeFactory {
data: self.data.take().map(Rc::new), app_data: self.app_data.take().map(Rc::new),
default: self.default.clone(), default,
services: cfg services: cfg
.into_services() .into_services()
.1 .1
@ -476,129 +472,65 @@ where
} }
pub struct ScopeFactory { pub struct ScopeFactory {
data: Option<Rc<Extensions>>, app_data: Option<Rc<Extensions>>,
services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>, services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>,
default: Rc<RefCell<Option<Rc<HttpNewService>>>>, default: Rc<HttpNewService>,
} }
impl ServiceFactory<ServiceRequest> for ScopeFactory { impl ServiceFactory<ServiceRequest> for ScopeFactory {
type Config = ();
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type InitError = (); type Config = ();
type Service = ScopeService; type Service = ScopeService;
type Future = ScopeFactoryResponse; type InitError = ();
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
let default_fut = if let Some(ref default) = *self.default.borrow() { // construct default service factory future
Some(default.new_service(())) let default_fut = self.default.new_service(());
} else {
None
};
ScopeFactoryResponse { // construct all services factory future with it's resource def and guards.
fut: self let factory_fut =
.services join_all(self.services.iter().map(|(path, factory, guards)| {
.iter() let path = path.clone();
.map(|(path, service, guards)| { let guards = guards.borrow_mut().take();
CreateScopeServiceItem::Future( let factory_fut = factory.new_service(());
Some(path.clone()), async move {
guards.borrow_mut().take(), let service = factory_fut.await?;
service.new_service(()), Ok((path, guards, service))
) }
}) }));
.collect(),
default: None,
data: self.data.clone(),
default_fut,
}
}
}
/// Create scope service let app_data = self.app_data.clone();
#[doc(hidden)]
#[pin_project::pin_project]
pub struct ScopeFactoryResponse {
fut: Vec<CreateScopeServiceItem>,
data: Option<Rc<Extensions>>,
default: Option<HttpService>,
default_fut: Option<LocalBoxFuture<'static, Result<HttpService, ()>>>,
}
type HttpServiceFut = LocalBoxFuture<'static, Result<HttpService, ()>>; Box::pin(async move {
let default = default_fut.await?;
enum CreateScopeServiceItem { // build router from the factory future result.
Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut), let router = factory_fut
Service(ResourceDef, Option<Guards>, HttpService), .await
} .into_iter()
.collect::<Result<Vec<_>, _>>()?
impl Future for ScopeFactoryResponse {
type Output = Result<ScopeService, ()>;
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 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 {
let res = match item {
CreateScopeServiceItem::Future(
ref mut path,
ref mut guards,
ref mut fut,
) => match Pin::new(fut).poll(cx)? {
Poll::Ready(service) => {
Some((path.take().unwrap(), guards.take(), service))
}
Poll::Pending => {
done = false;
None
}
},
CreateScopeServiceItem::Service(_, _, _) => continue,
};
if let Some((path, guards, service)) = res {
*item = CreateScopeServiceItem::Service(path, guards, service);
}
}
if done {
let router = self
.fut
.drain(..) .drain(..)
.fold(Router::build(), |mut router, item| { .fold(Router::build(), |mut router, (path, guards, service)| {
match item { router.rdef(path, service).2 = guards;
CreateScopeServiceItem::Service(path, guards, service) => {
router.rdef(path, service).2 = guards;
}
CreateScopeServiceItem::Future(_, _, _) => unreachable!(),
}
router router
}); })
Poll::Ready(Ok(ScopeService { .finish();
data: self.data.clone(),
router: router.finish(), Ok(ScopeService {
default: self.default.take(), app_data,
_ready: None, router,
})) default,
} else { })
Poll::Pending })
}
} }
} }
pub struct ScopeService { pub struct ScopeService {
data: Option<Rc<Extensions>>, app_data: Option<Rc<Extensions>>,
router: Router<HttpService, Vec<Box<dyn Guard>>>, router: Router<HttpService, Vec<Box<dyn Guard>>>,
default: Option<HttpService>, default: HttpService,
_ready: Option<(ServiceRequest, ResourceInfo)>,
} }
impl Service<ServiceRequest> for ScopeService { impl Service<ServiceRequest> for ScopeService {
@ -606,9 +538,7 @@ impl Service<ServiceRequest> for ScopeService {
type Error = Error; type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, mut req: ServiceRequest) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let res = self.router.recognize_mut_checked(&mut req, |req, guards| { let res = self.router.recognize_mut_checked(&mut req, |req, guards| {
@ -622,21 +552,14 @@ impl Service<ServiceRequest> for ScopeService {
true true
}); });
if let Some(ref app_data) = self.app_data {
req.add_data_container(app_data.clone());
}
if let Some((srv, _info)) = res { if let Some((srv, _info)) = res {
if let Some(ref data) = self.data {
req.add_data_container(data.clone());
}
srv.call(req) srv.call(req)
} else if let Some(ref mut default) = self.default {
if let Some(ref data) = self.data {
req.add_data_container(data.clone());
}
default.call(req)
} else { } else {
let req = req.into_parts().0; self.default.call(req)
Box::pin(async {
Ok(ServiceResponse::new(req, Response::NotFound().finish()))
})
} }
} }
} }
@ -658,7 +581,7 @@ impl ServiceFactory<ServiceRequest> for ScopeEndpoint {
type Config = (); type Config = ();
type Service = ScopeService; type Service = ScopeService;
type InitError = (); type InitError = ();
type Future = ScopeFactoryResponse; type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
self.factory.borrow_mut().as_mut().unwrap().new_service(()) self.factory.borrow_mut().as_mut().unwrap().new_service(())

View File

@ -283,11 +283,7 @@ where
lst, lst,
move || { move || {
let c = cfg.lock().unwrap(); let c = cfg.lock().unwrap();
let cfg = AppConfig::new( let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
false,
addr,
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
);
let svc = HttpService::build() let svc = HttpService::build()
.keep_alive(c.keep_alive) .keep_alive(c.keep_alive)
@ -302,8 +298,10 @@ where
svc svc
}; };
svc.finish(map_config(factory(), move |_| cfg.clone())) svc.finish(map_config(factory(), move |_| {
.tcp() AppConfig::new(false, addr, host.clone())
}))
.tcp()
}, },
)?; )?;
Ok(self) Ok(self)
@ -342,11 +340,7 @@ where
lst, lst,
move || { move || {
let c = cfg.lock().unwrap(); let c = cfg.lock().unwrap();
let cfg = AppConfig::new( let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
true,
addr,
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
);
let svc = HttpService::build() let svc = HttpService::build()
.keep_alive(c.keep_alive) .keep_alive(c.keep_alive)
@ -361,8 +355,10 @@ where
svc svc
}; };
svc.finish(map_config(factory(), move |_| cfg.clone())) svc.finish(map_config(factory(), move |_| {
.openssl(acceptor.clone()) AppConfig::new(true, addr, host.clone())
}))
.openssl(acceptor.clone())
}, },
)?; )?;
Ok(self) Ok(self)
@ -401,11 +397,7 @@ where
lst, lst,
move || { move || {
let c = cfg.lock().unwrap(); let c = cfg.lock().unwrap();
let cfg = AppConfig::new( let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
true,
addr,
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
);
let svc = HttpService::build() let svc = HttpService::build()
.keep_alive(c.keep_alive) .keep_alive(c.keep_alive)
@ -420,8 +412,10 @@ where
svc svc
}; };
svc.finish(map_config(factory(), move |_| cfg.clone())) svc.finish(map_config(factory(), move |_| {
.rustls(config.clone()) AppConfig::new(true, addr, host.clone())
}))
.rustls(config.clone())
}, },
)?; )?;
Ok(self) Ok(self)

View File

@ -52,75 +52,61 @@ where
/// An service http request /// An service http request
/// ///
/// ServiceRequest allows mutable access to request's internal structures /// ServiceRequest allows mutable access to request's internal structures
pub struct ServiceRequest(HttpRequest); pub struct ServiceRequest {
req: HttpRequest,
payload: Payload,
}
impl ServiceRequest { impl ServiceRequest {
/// Construct service request /// Construct service request
pub(crate) fn new(req: HttpRequest) -> Self { pub(crate) fn new(req: HttpRequest, payload: Payload) -> Self {
ServiceRequest(req) Self { req, payload }
} }
/// Deconstruct request into parts /// Deconstruct request into parts
pub fn into_parts(mut self) -> (HttpRequest, Payload) { #[inline]
let pl = Rc::get_mut(&mut (self.0).inner).unwrap().payload.take(); pub fn into_parts(self) -> (HttpRequest, Payload) {
(self.0, pl) (self.req, self.payload)
} }
/// Construct request from parts. /// Construct request from parts.
/// pub fn from_parts(req: HttpRequest, payload: Payload) -> Self {
/// `ServiceRequest` can be re-constructed only if `req` hasn't been cloned. Self { req, payload }
pub fn from_parts(
mut req: HttpRequest,
pl: Payload,
) -> Result<Self, (HttpRequest, Payload)> {
match Rc::get_mut(&mut req.inner) {
Some(p) => {
p.payload = pl;
Ok(ServiceRequest(req))
}
None => Err((req, pl)),
}
} }
/// Construct request from request. /// Construct request from request.
/// ///
/// `HttpRequest` implements `Clone` trait via `Rc` type. `ServiceRequest` /// The returned `ServiceRequest` would have no payload.
/// can be re-constructed only if rc's strong pointers count eq 1 and pub fn from_request(req: HttpRequest) -> Self {
/// weak pointers count is 0. ServiceRequest {
pub fn from_request(req: HttpRequest) -> Result<Self, HttpRequest> { req,
// There is no weak pointer used on HttpRequest so intentionally payload: Payload::None,
// ignore the check.
if Rc::strong_count(&req.inner) == 1 {
debug_assert!(Rc::weak_count(&req.inner) == 0);
Ok(ServiceRequest(req))
} else {
Err(req)
} }
} }
/// Create service response /// Create service response
#[inline] #[inline]
pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> { pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> {
ServiceResponse::new(self.0, res.into()) ServiceResponse::new(self.req, res.into())
} }
/// Create service response for error /// Create service response for error
#[inline] #[inline]
pub fn error_response<B, E: Into<Error>>(self, err: E) -> ServiceResponse<B> { pub fn error_response<B, E: Into<Error>>(self, err: E) -> ServiceResponse<B> {
let res: Response = err.into().into(); let res: Response = err.into().into();
ServiceResponse::new(self.0, res.into_body()) ServiceResponse::new(self.req, res.into_body())
} }
/// This method returns reference to the request head /// This method returns reference to the request head
#[inline] #[inline]
pub fn head(&self) -> &RequestHead { pub fn head(&self) -> &RequestHead {
&self.0.head() &self.req.head()
} }
/// This method returns reference to the request head /// This method returns reference to the request head
#[inline] #[inline]
pub fn head_mut(&mut self) -> &mut RequestHead { pub fn head_mut(&mut self) -> &mut RequestHead {
self.0.head_mut() self.req.head_mut()
} }
/// Request's uri. /// Request's uri.
@ -196,42 +182,42 @@ impl ServiceRequest {
/// access the matched value for that segment. /// access the matched value for that segment.
#[inline] #[inline]
pub fn match_info(&self) -> &Path<Url> { pub fn match_info(&self) -> &Path<Url> {
self.0.match_info() self.req.match_info()
} }
/// Counterpart to [`HttpRequest::match_name`](super::HttpRequest::match_name()). /// Counterpart to [`HttpRequest::match_name`](super::HttpRequest::match_name()).
#[inline] #[inline]
pub fn match_name(&self) -> Option<&str> { pub fn match_name(&self) -> Option<&str> {
self.0.match_name() self.req.match_name()
} }
/// Counterpart to [`HttpRequest::match_pattern`](super::HttpRequest::match_pattern()). /// Counterpart to [`HttpRequest::match_pattern`](super::HttpRequest::match_pattern()).
#[inline] #[inline]
pub fn match_pattern(&self) -> Option<String> { pub fn match_pattern(&self) -> Option<String> {
self.0.match_pattern() self.req.match_pattern()
} }
#[inline] #[inline]
/// Get a mutable reference to the Path parameters. /// Get a mutable reference to the Path parameters.
pub fn match_info_mut(&mut self) -> &mut Path<Url> { pub fn match_info_mut(&mut self) -> &mut Path<Url> {
self.0.match_info_mut() self.req.match_info_mut()
} }
#[inline] #[inline]
/// Get a reference to a `ResourceMap` of current application. /// Get a reference to a `ResourceMap` of current application.
pub fn resource_map(&self) -> &ResourceMap { pub fn resource_map(&self) -> &ResourceMap {
self.0.resource_map() self.req.resource_map()
} }
/// Service configuration /// Service configuration
#[inline] #[inline]
pub fn app_config(&self) -> &AppConfig { pub fn app_config(&self) -> &AppConfig {
self.0.app_config() self.req.app_config()
} }
/// Counterpart to [`HttpRequest::app_data`](super::HttpRequest::app_data()). /// Counterpart to [`HttpRequest::app_data`](super::HttpRequest::app_data()).
pub fn app_data<T: 'static>(&self) -> Option<&T> { pub fn app_data<T: 'static>(&self) -> Option<&T> {
for container in (self.0).inner.app_data.iter().rev() { for container in self.req.inner.app_data.iter().rev() {
if let Some(data) = container.get::<T>() { if let Some(data) = container.get::<T>() {
return Some(data); return Some(data);
} }
@ -242,13 +228,13 @@ impl ServiceRequest {
/// Set request payload. /// Set request payload.
pub fn set_payload(&mut self, payload: Payload) { pub fn set_payload(&mut self, payload: Payload) {
Rc::get_mut(&mut (self.0).inner).unwrap().payload = payload; self.payload = payload;
} }
#[doc(hidden)] #[doc(hidden)]
/// Add app data container to request's resolution set. /// Add app data container to request's resolution set.
pub fn add_data_container(&mut self, extensions: Rc<Extensions>) { pub fn add_data_container(&mut self, extensions: Rc<Extensions>) {
Rc::get_mut(&mut (self.0).inner) Rc::get_mut(&mut (self.req).inner)
.unwrap() .unwrap()
.app_data .app_data
.push(extensions); .push(extensions);
@ -273,18 +259,18 @@ impl HttpMessage for ServiceRequest {
/// Request extensions /// Request extensions
#[inline] #[inline]
fn extensions(&self) -> Ref<'_, Extensions> { fn extensions(&self) -> Ref<'_, Extensions> {
self.0.extensions() self.req.extensions()
} }
/// Mutable reference to a the request's extensions /// Mutable reference to a the request's extensions
#[inline] #[inline]
fn extensions_mut(&self) -> RefMut<'_, Extensions> { fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.0.extensions_mut() self.req.extensions_mut()
} }
#[inline] #[inline]
fn take_payload(&mut self) -> Payload<Self::Stream> { fn take_payload(&mut self) -> Payload<Self::Stream> {
Rc::get_mut(&mut (self.0).inner).unwrap().payload.take() self.payload.take()
} }
} }
@ -552,27 +538,6 @@ mod tests {
use actix_service::Service; use actix_service::Service;
use futures_util::future::ok; use futures_util::future::ok;
#[test]
fn test_service_request() {
let req = TestRequest::default().to_srv_request();
let (r, pl) = req.into_parts();
assert!(ServiceRequest::from_parts(r, pl).is_ok());
let req = TestRequest::default().to_srv_request();
let (r, pl) = req.into_parts();
let _r2 = r.clone();
assert!(ServiceRequest::from_parts(r, pl).is_err());
let req = TestRequest::default().to_srv_request();
let (r, _pl) = req.into_parts();
assert!(ServiceRequest::from_request(r).is_ok());
let req = TestRequest::default().to_srv_request();
let (r, _pl) = req.into_parts();
let _r2 = r.clone();
assert!(ServiceRequest::from_request(r).is_err());
}
#[actix_rt::test] #[actix_rt::test]
async fn test_service() { async fn test_service() {
let mut srv = init_service( let mut srv = init_service(

View File

@ -27,10 +27,10 @@ use socket2::{Domain, Protocol, Socket, Type};
pub use actix_http::test::TestBuffer; pub use actix_http::test::TestBuffer;
use crate::app_service::AppInitServiceState;
use crate::config::AppConfig; use crate::config::AppConfig;
use crate::data::Data; use crate::data::Data;
use crate::dev::{Body, MessageBody, Payload, Server}; use crate::dev::{Body, MessageBody, Payload, Server};
use crate::request::HttpRequestPool;
use crate::rmap::ResourceMap; use crate::rmap::ResourceMap;
use crate::service::{ServiceRequest, ServiceResponse}; use crate::service::{ServiceRequest, ServiceResponse};
use crate::{Error, HttpRequest, HttpResponse}; use crate::{Error, HttpRequest, HttpResponse};
@ -542,15 +542,13 @@ impl TestRequest {
head.peer_addr = self.peer_addr; head.peer_addr = self.peer_addr;
self.path.get_mut().update(&head.uri); self.path.get_mut().update(&head.uri);
ServiceRequest::new(HttpRequest::new( let app_state =
self.path, AppInitServiceState::new(Rc::new(self.rmap), self.config.clone());
head,
ServiceRequest::new(
HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data)),
payload, payload,
Rc::new(self.rmap), )
self.config.clone(),
Rc::new(self.app_data),
HttpRequestPool::create(),
))
} }
/// Complete request creation and generate `ServiceResponse` instance /// Complete request creation and generate `ServiceResponse` instance
@ -560,19 +558,14 @@ impl TestRequest {
/// Complete request creation and generate `HttpRequest` instance /// Complete request creation and generate `HttpRequest` instance
pub fn to_http_request(mut self) -> HttpRequest { pub fn to_http_request(mut self) -> HttpRequest {
let (mut head, payload) = self.req.finish().into_parts(); let (mut head, _) = self.req.finish().into_parts();
head.peer_addr = self.peer_addr; head.peer_addr = self.peer_addr;
self.path.get_mut().update(&head.uri); self.path.get_mut().update(&head.uri);
HttpRequest::new( let app_state =
self.path, AppInitServiceState::new(Rc::new(self.rmap), self.config.clone());
head,
payload, HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data))
Rc::new(self.rmap),
self.config.clone(),
Rc::new(self.app_data),
HttpRequestPool::create(),
)
} }
/// Complete request creation and generate `HttpRequest` and `Payload` instances /// Complete request creation and generate `HttpRequest` and `Payload` instances
@ -581,15 +574,10 @@ impl TestRequest {
head.peer_addr = self.peer_addr; head.peer_addr = self.peer_addr;
self.path.get_mut().update(&head.uri); self.path.get_mut().update(&head.uri);
let req = HttpRequest::new( let app_state =
self.path, AppInitServiceState::new(Rc::new(self.rmap), self.config.clone());
head,
Payload::None, let req = HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data));
Rc::new(self.rmap),
self.config.clone(),
Rc::new(self.app_data),
HttpRequestPool::create(),
);
(req, payload) (req, payload)
} }

View File

@ -1,132 +1,170 @@
use std::{ //! For either helper, see [`Either`].
future::Future,
pin::Pin, use bytes::Bytes;
task::{Context, Poll}, use futures_util::{future::LocalBoxFuture, FutureExt, TryFutureExt};
use crate::{
dev,
web::{Form, Json},
Error, FromRequest, HttpRequest, HttpResponse, Responder,
}; };
use actix_http::{Error, Response}; /// Combines two extractor or responder types into a single type.
use bytes::Bytes;
use futures_util::{future::LocalBoxFuture, ready, FutureExt, TryFutureExt};
use pin_project::pin_project;
use crate::{dev, request::HttpRequest, FromRequest, Responder};
/// Combines two different responder types into a single type
/// ///
/// ```rust /// Can be converted to and from an [`either::Either`].
/// use actix_web::{Either, Error, HttpResponse};
/// ///
/// type RegisterResult = Either<HttpResponse, Result<HttpResponse, Error>>; /// # Extractor
/// Provides a mechanism for trying two extractors, a primary and a fallback. Useful for
/// "polymorphic payloads" where, for example, a form might be JSON or URL encoded.
/// ///
/// fn index() -> RegisterResult { /// It is important to note that this extractor, by necessity, buffers the entire request payload
/// if is_a_variant() { /// as part of its implementation. Though, it does respect any `PayloadConfig` maximum size limits.
/// // <- choose left variant ///
/// Either::A(HttpResponse::BadRequest().body("Bad data")) /// ```
/// use actix_web::{post, web, Either};
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Info {
/// name: String,
/// }
///
/// // handler that accepts form as JSON or form-urlencoded.
/// #[post("/")]
/// async fn index(form: Either<web::Json<Info>, web::Form<Info>>) -> String {
/// let name: String = match form {
/// Either::Left(json) => json.name.to_owned(),
/// Either::Right(form) => form.name.to_owned(),
/// };
///
/// format!("Welcome {}!", name)
/// }
/// ```
///
/// # Responder
/// It may be desireable to use a concrete type for a response with multiple branches. As long as
/// both types implement `Responder`, so will the `Either` type, enabling it to be used as a
/// handler's return type.
///
/// All properties of a response are determined by the Responder branch returned.
///
/// ```
/// use actix_web::{get, Either, Error, HttpResponse};
///
/// #[get("/")]
/// async fn index() -> Either<&'static str, Result<HttpResponse, Error>> {
/// if 1 == 2 {
/// // respond with Left variant
/// Either::Left("Bad data")
/// } else { /// } else {
/// Either::B( /// // respond with Right variant
/// // <- Right variant /// Either::Right(
/// Ok(HttpResponse::Ok() /// Ok(HttpResponse::Ok()
/// .content_type("text/html") /// .content_type(mime::TEXT_HTML)
/// .body("Hello!")) /// .body("<p>Hello!</p>"))
/// ) /// )
/// } /// }
/// } /// }
/// # fn is_a_variant() -> bool { true }
/// # fn main() {}
/// ``` /// ```
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Either<A, B> { pub enum Either<L, R> {
/// First branch of the type /// A value of type `L`.
A(A), Left(L),
/// Second branch of the type
B(B), /// A value of type `R`.
Right(R),
}
impl<T> Either<Form<T>, Json<T>> {
pub fn into_inner(self) -> T {
match self {
Either::Left(form) => form.into_inner(),
Either::Right(form) => form.into_inner(),
}
}
}
impl<T> Either<Json<T>, Form<T>> {
pub fn into_inner(self) -> T {
match self {
Either::Left(form) => form.into_inner(),
Either::Right(form) => form.into_inner(),
}
}
}
impl<L, R> From<either::Either<L, R>> for Either<L, R> {
fn from(val: either::Either<L, R>) -> Self {
match val {
either::Either::Left(l) => Either::Left(l),
either::Either::Right(r) => Either::Right(r),
}
}
}
impl<L, R> From<Either<L, R>> for either::Either<L, R> {
fn from(val: Either<L, R>) -> Self {
match val {
Either::Left(l) => either::Either::Left(l),
Either::Right(r) => either::Either::Right(r),
}
}
} }
#[cfg(test)] #[cfg(test)]
impl<A, B> Either<A, B> { impl<L, R> Either<L, R> {
pub(self) fn unwrap_left(self) -> A { pub(self) fn unwrap_left(self) -> L {
match self { match self {
Either::A(data) => data, Either::Left(data) => data,
Either::B(_) => { Either::Right(_) => {
panic!("Cannot unwrap left branch. Either contains a right branch.") panic!("Cannot unwrap Left branch. Either contains an `R` type.")
} }
} }
} }
pub(self) fn unwrap_right(self) -> B { pub(self) fn unwrap_right(self) -> R {
match self { match self {
Either::A(_) => { Either::Left(_) => {
panic!("Cannot unwrap right branch. Either contains a left branch.") panic!("Cannot unwrap Right branch. Either contains an `L` type.")
} }
Either::B(data) => data, Either::Right(data) => data,
} }
} }
} }
impl<A, B> Responder for Either<A, B> /// See [here](#responder) for example of usage as a handler return type.
impl<L, R> Responder for Either<L, R>
where where
A: Responder, L: Responder,
B: Responder, R: Responder,
{ {
type Error = Error; fn respond_to(self, req: &HttpRequest) -> HttpResponse {
type Future = EitherResponder<A, B>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
match self { match self {
Either::A(a) => EitherResponder::A(a.respond_to(req)), Either::Left(a) => a.respond_to(req),
Either::B(b) => EitherResponder::B(b.respond_to(req)), Either::Right(b) => b.respond_to(req),
} }
} }
} }
#[pin_project(project = EitherResponderProj)] /// A composite error resulting from failure to extract an `Either<L, R>`.
pub enum EitherResponder<A, B>
where
A: Responder,
B: Responder,
{
A(#[pin] A::Future),
B(#[pin] B::Future),
}
impl<A, B> Future for EitherResponder<A, B>
where
A: Responder,
B: Responder,
{
type Output = Result<Response, Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project() {
EitherResponderProj::A(fut) => {
Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into()))
}
EitherResponderProj::B(fut) => {
Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into())))
}
}
}
}
/// A composite error resulting from failure to extract an `Either<A, B>`.
/// ///
/// The implementation of `Into<actix_web::Error>` will return the payload buffering error or the /// The implementation of `Into<actix_web::Error>` will return the payload buffering error or the
/// error from the primary extractor. To access the fallback error, use a match clause. /// error from the primary extractor. To access the fallback error, use a match clause.
#[derive(Debug)] #[derive(Debug)]
pub enum EitherExtractError<A, B> { pub enum EitherExtractError<L, R> {
/// Error from payload buffering, such as exceeding payload max size limit. /// Error from payload buffering, such as exceeding payload max size limit.
Bytes(Error), Bytes(Error),
/// Error from primary extractor. /// Error from primary extractor.
Extract(A, B), Extract(L, R),
} }
impl<A, B> From<EitherExtractError<A, B>> for Error impl<L, R> From<EitherExtractError<L, R>> for Error
where where
A: Into<Error>, L: Into<Error>,
B: Into<Error>, R: Into<Error>,
{ {
fn from(err: EitherExtractError<A, B>) -> Error { fn from(err: EitherExtractError<L, R>) -> Error {
match err { match err {
EitherExtractError::Bytes(err) => err, EitherExtractError::Bytes(err) => err,
EitherExtractError::Extract(a_err, _b_err) => a_err.into(), EitherExtractError::Extract(a_err, _b_err) => a_err.into(),
@ -134,17 +172,13 @@ where
} }
} }
/// Provides a mechanism for trying two extractors, a primary and a fallback. Useful for /// See [here](#extractor) for example of usage as an extractor.
/// "polymorphic payloads" where, for example, a form might be JSON or URL encoded. impl<L, R> FromRequest for Either<L, R>
///
/// It is important to note that this extractor, by necessity, buffers the entire request payload
/// as part of its implementation. Though, it does respect a `PayloadConfig`'s maximum size limit.
impl<A, B> FromRequest for Either<A, B>
where where
A: FromRequest + 'static, L: FromRequest + 'static,
B: FromRequest + 'static, R: FromRequest + 'static,
{ {
type Error = EitherExtractError<A::Error, B::Error>; type Error = EitherExtractError<L::Error, R::Error>;
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>; type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
type Config = (); type Config = ();
@ -153,32 +187,32 @@ where
Bytes::from_request(req, payload) Bytes::from_request(req, payload)
.map_err(EitherExtractError::Bytes) .map_err(EitherExtractError::Bytes)
.and_then(|bytes| bytes_to_a_or_b(req2, bytes)) .and_then(|bytes| bytes_to_l_or_r(req2, bytes))
.boxed_local() .boxed_local()
} }
} }
async fn bytes_to_a_or_b<A, B>( async fn bytes_to_l_or_r<L, R>(
req: HttpRequest, req: HttpRequest,
bytes: Bytes, bytes: Bytes,
) -> Result<Either<A, B>, EitherExtractError<A::Error, B::Error>> ) -> Result<Either<L, R>, EitherExtractError<L::Error, R::Error>>
where where
A: FromRequest + 'static, L: FromRequest + 'static,
B: FromRequest + 'static, R: FromRequest + 'static,
{ {
let fallback = bytes.clone(); let fallback = bytes.clone();
let a_err; let a_err;
let mut pl = payload_from_bytes(bytes); let mut pl = payload_from_bytes(bytes);
match A::from_request(&req, &mut pl).await { match L::from_request(&req, &mut pl).await {
Ok(a_data) => return Ok(Either::A(a_data)), Ok(a_data) => return Ok(Either::Left(a_data)),
// store A's error for returning if B also fails // store A's error for returning if B also fails
Err(err) => a_err = err, Err(err) => a_err = err,
}; };
let mut pl = payload_from_bytes(fallback); let mut pl = payload_from_bytes(fallback);
match B::from_request(&req, &mut pl).await { match R::from_request(&req, &mut pl).await {
Ok(b_data) => return Ok(Either::B(b_data)), Ok(b_data) => return Ok(Either::Right(b_data)),
Err(b_err) => Err(EitherExtractError::Extract(a_err, b_err)), Err(b_err) => Err(EitherExtractError::Extract(a_err, b_err)),
} }
} }

View File

@ -1,72 +1,68 @@
//! Form extractor //! For URL encoded form helper documentation, see [`Form`].
use std::future::Future; use std::{
use std::pin::Pin; fmt,
use std::rc::Rc; future::Future,
use std::task::{Context, Poll}; ops,
use std::{fmt, ops}; pin::Pin,
rc::Rc,
task::{Context, Poll},
};
use actix_http::{Error, HttpMessage, Payload, Response}; use actix_http::Payload;
use bytes::BytesMut; use bytes::BytesMut;
use encoding_rs::{Encoding, UTF_8}; use encoding_rs::{Encoding, UTF_8};
use futures_util::future::{err, ok, FutureExt, LocalBoxFuture, Ready}; use futures_util::{
use futures_util::StreamExt; future::{FutureExt, LocalBoxFuture},
use serde::de::DeserializeOwned; StreamExt,
use serde::Serialize; };
use serde::{de::DeserializeOwned, Serialize};
#[cfg(feature = "compress")] #[cfg(feature = "compress")]
use crate::dev::Decompress; use crate::dev::Decompress;
use crate::error::UrlencodedError; use crate::{
use crate::extract::FromRequest; error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web,
use crate::http::{ Error, HttpMessage, HttpRequest, HttpResponse, Responder,
header::{ContentType, CONTENT_LENGTH},
StatusCode,
}; };
use crate::request::HttpRequest;
use crate::{responder::Responder, web};
/// Form data helper (`application/x-www-form-urlencoded`) /// URL encoded payload extractor and responder.
/// ///
/// Can be use to extract url-encoded data from the request body, /// `Form` has two uses: URL encoded responses, and extracting typed data from URL request payloads.
/// or send url-encoded data as the response.
/// ///
/// ## Extract /// # Extractor
/// To extract typed data from a request body, the inner type `T` must implement the
/// [`serde::Deserialize`] trait.
/// ///
/// To extract typed information from request's body, the type `T` must /// Use [`FormConfig`] to configure extraction process.
/// implement the `Deserialize` trait from *serde*.
/// ///
/// [**FormConfig**](FormConfig) allows to configure extraction /// ```
/// process. /// use actix_web::{post, web};
/// /// use serde::Deserialize;
/// ### Example
/// ```rust
/// use actix_web::web;
/// use serde_derive::Deserialize;
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct FormData { /// struct Info {
/// username: String, /// name: String,
/// } /// }
/// ///
/// /// Extract form data using serde. /// // This handler is only called if:
/// /// This handler get called only if content type is *x-www-form-urlencoded* /// // - request headers declare the content type as `application/x-www-form-urlencoded`
/// /// and content of the request could be deserialized to a `FormData` struct /// // - request payload is deserialized into a `Info` struct from the URL encoded format
/// fn index(form: web::Form<FormData>) -> String { /// #[post("/")]
/// format!("Welcome {}!", form.username) /// async fn index(form: web::Form<Info>) -> String {
/// format!("Welcome {}!", form.name)
/// } /// }
/// # fn main() {}
/// ``` /// ```
/// ///
/// ## Respond /// # Responder
/// The `Form` type also allows you to create URL encoded responses:
/// simply return a value of type Form<T> where T is the type to be URL encoded.
/// The type must implement [`serde::Serialize`].
/// ///
/// The `Form` type also allows you to respond with well-formed url-encoded data: /// Responses use
/// simply return a value of type Form<T> where T is the type to be url-encoded.
/// The type must implement `serde::Serialize`;
/// ///
/// ### Example /// ```
/// ```rust /// use actix_web::{get, web};
/// use actix_web::*; /// use serde::Serialize;
/// use serde_derive::Serialize;
/// ///
/// #[derive(Serialize)] /// #[derive(Serialize)]
/// struct SomeForm { /// struct SomeForm {
@ -74,22 +70,23 @@ use crate::{responder::Responder, web};
/// age: u8 /// age: u8
/// } /// }
/// ///
/// // Will return a 200 response with header /// // Response will have:
/// // `Content-Type: application/x-www-form-urlencoded` /// // - status: 200 OK
/// // and body "name=actix&age=123" /// // - header: `Content-Type: application/x-www-form-urlencoded`
/// fn index() -> web::Form<SomeForm> { /// // - body: `name=actix&age=123`
/// #[get("/")]
/// async fn index() -> web::Form<SomeForm> {
/// web::Form(SomeForm { /// web::Form(SomeForm {
/// name: "actix".into(), /// name: "actix".into(),
/// age: 123 /// age: 123
/// }) /// })
/// } /// }
/// # fn main() {}
/// ``` /// ```
#[derive(PartialEq, Eq, PartialOrd, Ord)] #[derive(PartialEq, Eq, PartialOrd, Ord)]
pub struct Form<T>(pub T); pub struct Form<T>(pub T);
impl<T> Form<T> { impl<T> Form<T> {
/// Deconstruct to an inner value /// Unwrap into inner `T` value.
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
self.0 self.0
} }
@ -109,6 +106,7 @@ impl<T> ops::DerefMut for Form<T> {
} }
} }
/// See [here](#extractor) for example of usage as an extractor.
impl<T> FromRequest for Form<T> impl<T> FromRequest for Form<T>
where where
T: DeserializeOwned + 'static, T: DeserializeOwned + 'static,
@ -120,7 +118,7 @@ where
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let req2 = req.clone(); let req2 = req.clone();
let (limit, err) = req let (limit, err_handler) = req
.app_data::<Self::Config>() .app_data::<Self::Config>()
.or_else(|| { .or_else(|| {
req.app_data::<web::Data<Self::Config>>() req.app_data::<web::Data<Self::Config>>()
@ -132,13 +130,10 @@ where
UrlEncoded::new(req, payload) UrlEncoded::new(req, payload)
.limit(limit) .limit(limit)
.map(move |res| match res { .map(move |res| match res {
Err(e) => { Err(err) => match err_handler {
if let Some(err) = err { Some(err_handler) => Err((err_handler)(err, &req2)),
Err((*err)(e, &req2)) None => Err(err.into()),
} else { },
Err(e.into())
}
}
Ok(item) => Ok(Form(item)), Ok(item) => Ok(Form(item)),
}) })
.boxed_local() .boxed_local()
@ -157,49 +152,39 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
} }
} }
/// See [here](#responder) for example of usage as a handler return type.
impl<T: Serialize> Responder for Form<T> { impl<T: Serialize> Responder for Form<T> {
type Error = Error; fn respond_to(self, _: &HttpRequest) -> HttpResponse {
type Future = Ready<Result<Response, Error>>; match serde_urlencoded::to_string(&self.0) {
Ok(body) => HttpResponse::Ok()
fn respond_to(self, _: &HttpRequest) -> Self::Future { .content_type(mime::APPLICATION_WWW_FORM_URLENCODED)
let body = match serde_urlencoded::to_string(&self.0) { .body(body),
Ok(body) => body, Err(err) => HttpResponse::from_error(err.into()),
Err(e) => return err(e.into()), }
};
ok(Response::build(StatusCode::OK)
.set(ContentType::form_url_encoded())
.body(body))
} }
} }
/// Form extractor configuration /// [`Form`] extractor configuration.
/// ///
/// ```rust /// ```
/// use actix_web::{web, App, FromRequest, Result}; /// use actix_web::{post, web, App, FromRequest, Result};
/// use serde_derive::Deserialize; /// use serde::Deserialize;
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct FormData { /// struct Info {
/// username: String, /// username: String,
/// } /// }
/// ///
/// /// Extract form data using serde. /// // Custom `FormConfig` is applied to App.
/// /// Custom configuration is used for this handler, max payload size is 4k /// // Max payload size for URL encoded forms is set to 4kB.
/// async fn index(form: web::Form<FormData>) -> Result<String> { /// #[post("/")]
/// async fn index(form: web::Form<Info>) -> Result<String> {
/// Ok(format!("Welcome {}!", form.username)) /// Ok(format!("Welcome {}!", form.username))
/// } /// }
/// ///
/// fn main() { /// App::new()
/// let app = App::new().service( /// .app_data(web::FormConfig::default().limit(4096))
/// web::resource("/index.html") /// .service(index);
/// // change `Form` extractor configuration
/// .app_data(
/// web::FormConfig::default().limit(4097)
/// )
/// .route(web::get().to(index))
/// );
/// }
/// ``` /// ```
#[derive(Clone)] #[derive(Clone)]
pub struct FormConfig { pub struct FormConfig {
@ -208,7 +193,7 @@ pub struct FormConfig {
} }
impl FormConfig { impl FormConfig {
/// Change max size of payload. By default max size is 16Kb /// Set maximum accepted payload size. By default this limit is 16kB.
pub fn limit(mut self, limit: usize) -> Self { pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit; self.limit = limit;
self self
@ -233,33 +218,30 @@ impl Default for FormConfig {
} }
} }
/// Future that resolves to a parsed urlencoded values. /// Future that resolves to some `T` when parsed from a URL encoded payload.
/// ///
/// Parse `application/x-www-form-urlencoded` encoded request's body. /// Form can be deserialized from any type `T` that implements [`serde::Deserialize`].
/// Return `UrlEncoded` future. Form can be deserialized to any type that
/// implements `Deserialize` trait from *serde*.
/// ///
/// Returns error: /// Returns error if:
/// /// - content type is not `application/x-www-form-urlencoded`
/// * content type is not `application/x-www-form-urlencoded` /// - content length is greater than [limit](UrlEncoded::limit())
/// * content-length is greater than 32k pub struct UrlEncoded<T> {
///
pub struct UrlEncoded<U> {
#[cfg(feature = "compress")] #[cfg(feature = "compress")]
stream: Option<Decompress<Payload>>, stream: Option<Decompress<Payload>>,
#[cfg(not(feature = "compress"))] #[cfg(not(feature = "compress"))]
stream: Option<Payload>, stream: Option<Payload>,
limit: usize, limit: usize,
length: Option<usize>, length: Option<usize>,
encoding: &'static Encoding, encoding: &'static Encoding,
err: Option<UrlencodedError>, err: Option<UrlencodedError>,
fut: Option<LocalBoxFuture<'static, Result<U, UrlencodedError>>>, fut: Option<LocalBoxFuture<'static, Result<T, UrlencodedError>>>,
} }
#[allow(clippy::borrow_interior_mutable_const)] #[allow(clippy::borrow_interior_mutable_const)]
impl<U> UrlEncoded<U> { impl<T> UrlEncoded<T> {
/// Create a new future to URL encode a request /// Create a new future to decode a URL encoded request payload.
pub fn new(req: &HttpRequest, payload: &mut Payload) -> UrlEncoded<U> { pub fn new(req: &HttpRequest, payload: &mut Payload) -> Self {
// check content type // check content type
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
return Self::err(UrlencodedError::ContentType); return Self::err(UrlencodedError::ContentType);
@ -297,29 +279,29 @@ impl<U> UrlEncoded<U> {
} }
} }
fn err(e: UrlencodedError) -> Self { fn err(err: UrlencodedError) -> Self {
UrlEncoded { UrlEncoded {
stream: None, stream: None,
limit: 32_768, limit: 32_768,
fut: None, fut: None,
err: Some(e), err: Some(err),
length: None, length: None,
encoding: UTF_8, encoding: UTF_8,
} }
} }
/// Change max size of payload. By default max size is 256Kb /// Set maximum accepted payload size. The default limit is 256kB.
pub fn limit(mut self, limit: usize) -> Self { pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit; self.limit = limit;
self self
} }
} }
impl<U> Future for UrlEncoded<U> impl<T> Future for UrlEncoded<T>
where where
U: DeserializeOwned + 'static, T: DeserializeOwned + 'static,
{ {
type Output = Result<U, UrlencodedError>; type Output = Result<T, UrlencodedError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(ref mut fut) = self.fut { if let Some(ref mut fut) = self.fut {
@ -348,6 +330,7 @@ where
while let Some(item) = stream.next().await { while let Some(item) = stream.next().await {
let chunk = item?; let chunk = item?;
if (body.len() + chunk.len()) > limit { if (body.len() + chunk.len()) > limit {
return Err(UrlencodedError::Overflow { return Err(UrlencodedError::Overflow {
size: body.len() + chunk.len(), size: body.len() + chunk.len(),
@ -359,19 +342,21 @@ where
} }
if encoding == UTF_8 { if encoding == UTF_8 {
serde_urlencoded::from_bytes::<U>(&body) serde_urlencoded::from_bytes::<T>(&body)
.map_err(|_| UrlencodedError::Parse) .map_err(|_| UrlencodedError::Parse)
} else { } else {
let body = encoding let body = encoding
.decode_without_bom_handling_and_without_replacement(&body) .decode_without_bom_handling_and_without_replacement(&body)
.map(|s| s.into_owned()) .map(|s| s.into_owned())
.ok_or(UrlencodedError::Parse)?; .ok_or(UrlencodedError::Parse)?;
serde_urlencoded::from_str::<U>(&body)
serde_urlencoded::from_str::<T>(&body)
.map_err(|_| UrlencodedError::Parse) .map_err(|_| UrlencodedError::Parse)
} }
} }
.boxed_local(), .boxed_local(),
); );
self.poll(cx) self.poll(cx)
} }
} }
@ -382,7 +367,10 @@ mod tests {
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::*; use super::*;
use crate::http::header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE}; use crate::http::{
header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
StatusCode,
};
use crate::test::TestRequest; use crate::test::TestRequest;
#[derive(Deserialize, Serialize, Debug, PartialEq)] #[derive(Deserialize, Serialize, Debug, PartialEq)]
@ -493,7 +481,7 @@ mod tests {
hello: "world".to_string(), hello: "world".to_string(),
counter: 123, counter: 123,
}); });
let resp = form.respond_to(&req).await.unwrap(); let resp = form.respond_to(&req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
@ -519,6 +507,6 @@ mod tests {
assert!(s.is_err()); assert!(s.is_err());
let err_str = s.err().unwrap().to_string(); let err_str = s.err().unwrap().to_string();
assert!(err_str.contains("Urlencoded payload size is bigger")); assert!(err_str.starts_with("URL encoded payload is larger"));
} }
} }

View File

@ -1,46 +1,44 @@
//! Json extractor/responder //! For JSON helper documentation, see [`Json`].
use std::future::Future; use std::{
use std::marker::PhantomData; fmt,
use std::pin::Pin; future::Future,
use std::sync::Arc; marker::PhantomData,
use std::task::{Context, Poll}; ops,
use std::{fmt, ops}; pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use bytes::BytesMut; use bytes::BytesMut;
use futures_util::future::{ready, Ready}; use futures_util::{ready, stream::Stream};
use futures_util::ready; use serde::{de::DeserializeOwned, Serialize};
use futures_util::stream::Stream;
use serde::de::DeserializeOwned;
use serde::Serialize;
use actix_http::http::{header::CONTENT_LENGTH, StatusCode}; use actix_http::Payload;
use actix_http::{HttpMessage, Payload, Response};
#[cfg(feature = "compress")] #[cfg(feature = "compress")]
use crate::dev::Decompress; use crate::dev::Decompress;
use crate::error::{Error, JsonPayloadError}; use crate::{
use crate::extract::FromRequest; error::{Error, JsonPayloadError},
use crate::request::HttpRequest; extract::FromRequest,
use crate::{responder::Responder, web}; http::header::CONTENT_LENGTH,
request::HttpRequest,
web, HttpMessage, HttpResponse, Responder,
};
/// Json helper /// JSON extractor and responder.
/// ///
/// Json can be used for two different purpose. First is for json response /// `Json` has two uses: JSON responses, and extracting typed data from JSON request payloads.
/// generation and second is for extracting typed information from request's
/// payload.
/// ///
/// To extract typed information from request's body, the type `T` must /// # Extractor
/// implement the `Deserialize` trait from *serde*. /// To extract typed data from a request body, the inner type `T` must implement the
/// [`serde::Deserialize`] trait.
/// ///
/// [**JsonConfig**](JsonConfig) allows to configure extraction /// Use [`JsonConfig`] to configure extraction process.
/// process.
/// ///
/// ## Example /// ```
/// /// use actix_web::{post, web, App};
/// ```rust /// use serde::Deserialize;
/// use actix_web::{web, App};
/// use serde_derive::Deserialize;
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct Info { /// struct Info {
@ -48,43 +46,37 @@ use crate::{responder::Responder, web};
/// } /// }
/// ///
/// /// deserialize `Info` from request's body /// /// deserialize `Info` from request's body
/// #[post("/")]
/// async fn index(info: web::Json<Info>) -> String { /// async fn index(info: web::Json<Info>) -> String {
/// format!("Welcome {}!", info.username) /// format!("Welcome {}!", info.username)
/// } /// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").route(
/// web::post().to(index))
/// );
/// }
/// ``` /// ```
/// ///
/// The `Json` type allows you to respond with well-formed JSON data: simply /// # Responder
/// return a value of type Json<T> where T is the type of a structure /// The `Json` type JSON formatted responses. A handler may return a value of type
/// to serialize into *JSON*. The type `T` must implement the `Serialize` /// `Json<T>` where `T` is the type of a structure to serialize into JSON. The type `T` must
/// trait from *serde*. /// implement [`serde::Serialize`].
/// ///
/// ```rust /// ```
/// use actix_web::*; /// use actix_web::{post, web, HttpRequest};
/// use serde_derive::Serialize; /// use serde::Serialize;
/// ///
/// #[derive(Serialize)] /// #[derive(Serialize)]
/// struct MyObj { /// struct Info {
/// name: String, /// name: String,
/// } /// }
/// ///
/// fn index(req: HttpRequest) -> Result<web::Json<MyObj>> { /// #[post("/{name}")]
/// Ok(web::Json(MyObj { /// async fn index(req: HttpRequest) -> web::Json<Info> {
/// name: req.match_info().get("name").unwrap().to_string(), /// web::Json(Info {
/// })) /// name: req.match_info().get("name").unwrap().to_owned(),
/// })
/// } /// }
/// # fn main() {}
/// ``` /// ```
pub struct Json<T>(pub T); pub struct Json<T>(pub T);
impl<T> Json<T> { impl<T> Json<T> {
/// Deconstruct to an inner value /// Unwrap into inner `T` value.
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
self.0 self.0
} }
@ -122,54 +114,21 @@ where
} }
} }
/// Creates response with OK status code, correct content type header, and serialized JSON payload.
///
/// If serialization failed
impl<T: Serialize> Responder for Json<T> { impl<T: Serialize> Responder for Json<T> {
type Error = Error; fn respond_to(self, _: &HttpRequest) -> HttpResponse {
type Future = Ready<Result<Response, Error>>; match serde_json::to_string(&self.0) {
Ok(body) => HttpResponse::Ok()
fn respond_to(self, _: &HttpRequest) -> Self::Future { .content_type(mime::APPLICATION_JSON)
let body = match serde_json::to_string(&self.0) { .body(body),
Ok(body) => body, Err(err) => HttpResponse::from_error(err.into()),
Err(e) => return ready(Err(e.into())), }
};
ready(Ok(Response::build(StatusCode::OK)
.content_type("application/json")
.body(body)))
} }
} }
/// Json extractor. Allow to extract typed information from request's /// See [here](#extractor) for example of usage as an extractor.
/// payload.
///
/// To extract typed information from request's body, the type `T` must
/// implement the `Deserialize` trait from *serde*.
///
/// [**JsonConfig**](JsonConfig) allows to configure extraction
/// process.
///
/// ## Example
///
/// ```rust
/// use actix_web::{web, App};
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// deserialize `Info` from request's body
/// async fn index(info: web::Json<Info>) -> String {
/// format!("Welcome {}!", info.username)
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").route(
/// web::post().to(index))
/// );
/// }
/// ```
impl<T> FromRequest for Json<T> impl<T> FromRequest for Json<T>
where where
T: DeserializeOwned + 'static, T: DeserializeOwned + 'static,
@ -215,7 +174,7 @@ where
let res = ready!(Pin::new(&mut this.fut).poll(cx)); let res = ready!(Pin::new(&mut this.fut).poll(cx));
let res = match res { let res = match res {
Err(e) => { Err(err) => {
let req = this.req.take().unwrap(); let req = this.req.take().unwrap();
log::debug!( log::debug!(
"Failed to deserialize Json from payload. \ "Failed to deserialize Json from payload. \
@ -223,10 +182,10 @@ where
req.path() req.path()
); );
if let Some(err) = this.err_handler.as_ref() { if let Some(err_handler) = this.err_handler.as_ref() {
Err((*err)(e, &req)) Err((*err_handler)(err, &req))
} else { } else {
Err(e.into()) Err(err.into())
} }
} }
Ok(data) => Ok(Json(data)), Ok(data) => Ok(Json(data)),
@ -236,44 +195,39 @@ where
} }
} }
/// Json extractor configuration /// `Json` extractor configuration.
/// ///
/// # Example /// # Usage
/// /// ```
/// ```rust /// use actix_web::{error, post, web, App, FromRequest, HttpResponse};
/// use actix_web::{error, web, App, FromRequest, HttpResponse}; /// use serde::Deserialize;
/// use serde_derive::Deserialize;
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct Info { /// struct Info {
/// username: String, /// name: String,
/// } /// }
/// ///
/// /// deserialize `Info` from request's body, max payload size is 4kb /// // `Json` extraction is bound by custom `JsonConfig` applied to App.
/// #[post("/")]
/// async fn index(info: web::Json<Info>) -> String { /// async fn index(info: web::Json<Info>) -> String {
/// format!("Welcome {}!", info.username) /// format!("Welcome {}!", info.name)
/// } /// }
/// ///
/// fn main() { /// // custom `Json` extractor configuration
/// let app = App::new().service( /// let json_cfg = web::JsonConfig::default()
/// web::resource("/index.html") /// // limit request payload size
/// .app_data( /// .limit(4096)
/// // Json extractor configuration for this resource. /// // only accept text/plain content type
/// web::JsonConfig::default() /// .content_type(|mime| mime == mime::TEXT_PLAIN)
/// .limit(4096) // Limit request payload size /// // use custom error handler
/// .content_type(|mime| { // <- accept text/plain content type /// .error_handler(|err, req| {
/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN /// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
/// }) /// });
/// .error_handler(|err, req| { // <- create custom error response ///
/// error::InternalError::from_response( /// App::new()
/// err, HttpResponse::Conflict().finish()).into() /// .app_data(json_cfg)
/// }) /// .service(index);
/// )
/// .route(web::post().to(index))
/// );
/// }
/// ``` /// ```
///
#[derive(Clone)] #[derive(Clone)]
pub struct JsonConfig { pub struct JsonConfig {
limit: usize, limit: usize,
@ -282,13 +236,13 @@ pub struct JsonConfig {
} }
impl JsonConfig { impl JsonConfig {
/// Change max size of payload. By default max size is 32Kb /// Set maximum accepted payload size. By default this limit is 32kB.
pub fn limit(mut self, limit: usize) -> Self { pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit; self.limit = limit;
self self
} }
/// Set custom error handler /// Set custom error handler.
pub fn error_handler<F>(mut self, f: F) -> Self pub fn error_handler<F>(mut self, f: F) -> Self
where where
F: Fn(JsonPayloadError, &HttpRequest) -> Error + Send + Sync + 'static, F: Fn(JsonPayloadError, &HttpRequest) -> Error + Send + Sync + 'static,
@ -297,7 +251,7 @@ impl JsonConfig {
self self
} }
/// Set predicate for allowed content types /// Set predicate for allowed content types.
pub fn content_type<F>(mut self, predicate: F) -> Self pub fn content_type<F>(mut self, predicate: F) -> Self
where where
F: Fn(mime::Mime) -> bool + Send + Sync + 'static, F: Fn(mime::Mime) -> bool + Send + Sync + 'static,
@ -328,15 +282,14 @@ impl Default for JsonConfig {
} }
} }
/// Request's payload json parser, it resolves to a deserialized `T` value. /// Future that resolves to some `T` when parsed from a JSON payload.
/// This future could be used with `ServiceRequest` and `ServiceFromRequest`.
/// ///
/// Returns error: /// Form can be deserialized from any type `T` that implements [`serde::Deserialize`].
/// ///
/// * content type is not `application/json` /// Returns error if:
/// (unless specified in [`JsonConfig`]) /// - content type is not `application/json`
/// * content length is greater than 256k /// - content length is greater than [limit](JsonBody::limit())
pub enum JsonBody<U> { pub enum JsonBody<T> {
Error(Option<JsonPayloadError>), Error(Option<JsonPayloadError>),
Body { Body {
limit: usize, limit: usize,
@ -346,17 +299,17 @@ pub enum JsonBody<U> {
#[cfg(not(feature = "compress"))] #[cfg(not(feature = "compress"))]
payload: Payload, payload: Payload,
buf: BytesMut, buf: BytesMut,
_res: PhantomData<U>, _res: PhantomData<T>,
}, },
} }
impl<U> Unpin for JsonBody<U> {} impl<T> Unpin for JsonBody<T> {}
impl<U> JsonBody<U> impl<T> JsonBody<T>
where where
U: DeserializeOwned + 'static, T: DeserializeOwned + 'static,
{ {
/// Create `JsonBody` for request. /// Create a new future to decode a JSON request payload.
#[allow(clippy::borrow_interior_mutable_const)] #[allow(clippy::borrow_interior_mutable_const)]
pub fn new( pub fn new(
req: &HttpRequest, req: &HttpRequest,
@ -400,7 +353,7 @@ where
} }
} }
/// Change max size of payload. By default max size is 256Kb /// Set maximum accepted payload size. The default limit is 256kB.
pub fn limit(self, limit: usize) -> Self { pub fn limit(self, limit: usize) -> Self {
match self { match self {
JsonBody::Body { JsonBody::Body {
@ -428,11 +381,11 @@ where
} }
} }
impl<U> Future for JsonBody<U> impl<T> Future for JsonBody<T>
where where
U: DeserializeOwned + 'static, T: DeserializeOwned + 'static,
{ {
type Output = Result<U, JsonPayloadError>; type Output = Result<T, JsonPayloadError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut(); let this = self.get_mut();
@ -455,7 +408,7 @@ where
} }
} }
None => { None => {
let json = serde_json::from_slice::<U>(&buf)?; let json = serde_json::from_slice::<T>(&buf)?;
return Poll::Ready(Ok(json)); return Poll::Ready(Ok(json));
} }
} }
@ -468,13 +421,17 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use bytes::Bytes; use bytes::Bytes;
use serde_derive::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::*; use super::*;
use crate::error::InternalError; use crate::{
use crate::http::header::{self, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE}; error::InternalError,
use crate::test::{load_stream, TestRequest}; http::{
use crate::HttpResponse; header::{self, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
StatusCode,
},
test::{load_stream, TestRequest},
};
#[derive(Serialize, Deserialize, PartialEq, Debug)] #[derive(Serialize, Deserialize, PartialEq, Debug)]
struct MyObject { struct MyObject {
@ -498,7 +455,7 @@ mod tests {
let j = Json(MyObject { let j = Json(MyObject {
name: "test".to_string(), name: "test".to_string(),
}); });
let resp = j.respond_to(&req).await.unwrap(); let resp = j.respond_to(&req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@ -532,7 +489,7 @@ mod tests {
.to_http_parts(); .to_http_parts();
let s = Json::<MyObject>::from_request(&req, &mut pl).await; let s = Json::<MyObject>::from_request(&req, &mut pl).await;
let mut resp = Response::from_error(s.err().unwrap()); let mut resp = HttpResponse::from_error(s.err().unwrap());
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let body = load_stream(resp.take_body()).await.unwrap(); let body = load_stream(resp.take_body()).await.unwrap();

View File

@ -1,5 +1,6 @@
//! Helper types //! Common extractors and responders.
// TODO: review visibility
mod either; mod either;
pub(crate) mod form; pub(crate) mod form;
pub(crate) mod json; pub(crate) mod json;

View File

@ -1,70 +1,55 @@
//! Path extractor //! For path segment extractor documentation, see [`Path`].
use std::sync::Arc;
use std::{fmt, ops}; use std::{fmt, ops, sync::Arc};
use actix_http::error::{Error, ErrorNotFound}; use actix_http::error::{Error, ErrorNotFound};
use actix_router::PathDeserializer; use actix_router::PathDeserializer;
use futures_util::future::{ready, Ready}; use futures_util::future::{ready, Ready};
use serde::de; use serde::de;
use crate::dev::Payload; use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest};
use crate::error::PathError;
use crate::request::HttpRequest;
use crate::FromRequest;
#[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed data from request path segments.
/// Extract typed information from the request's path.
/// ///
/// [**PathConfig**](PathConfig) allows to configure extraction process. /// Use [`PathConfig`] to configure extraction process.
/// ///
/// ## Example /// # Usage
/// ```
/// use actix_web::{get, web};
/// ///
/// ```rust /// // extract path info from "/{name}/{count}/index.html" into tuple
/// use actix_web::{web, App}; /// // {name} - deserialize a String
/// /// // {count} - deserialize a u32
/// /// extract path info from "/{username}/{count}/index.html" url /// #[get("/")]
/// /// {username} - deserializes to a String /// async fn index(path: web::Path<(String, u32)>) -> String {
/// /// {count} - - deserializes to a u32 /// let (name, count) = path.into_inner();
/// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String { /// format!("Welcome {}! {}", name, count)
/// format!("Welcome {}! {}", username, count)
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/{username}/{count}/index.html") // <- define path parameters
/// .route(web::get().to(index)) // <- register handler with `Path` extractor
/// );
/// } /// }
/// ``` /// ```
/// ///
/// It is possible to extract path information to a specific type that /// Path segments also can be deserialized into any type that implements [`serde::Deserialize`].
/// implements `Deserialize` trait from *serde*. /// Path segment labels will be matched with struct field names.
/// ///
/// ```rust /// ```
/// use actix_web::{web, App, Error}; /// use actix_web::{get, web};
/// use serde_derive::Deserialize; /// use serde::Deserialize;
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct Info { /// struct Info {
/// username: String, /// name: String,
/// } /// }
/// ///
/// /// extract `Info` from a path using serde /// // extract `Info` from a path using serde
/// async fn index(info: web::Path<Info>) -> Result<String, Error> { /// #[get("/")]
/// Ok(format!("Welcome {}!", info.username)) /// async fn index(info: web::Path<Info>) -> String {
/// } /// format!("Welcome {}!", info.name)
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/{username}/index.html") // <- define path parameters
/// .route(web::get().to(index)) // <- use handler with Path` extractor
/// );
/// } /// }
/// ``` /// ```
pub struct Path<T>(pub T); #[derive(PartialEq, Eq, PartialOrd, Ord)]
pub struct Path<T>(T);
impl<T> Path<T> { impl<T> Path<T> {
/// Deconstruct to an inner value /// Unwrap into inner `T` value.
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
self.0 self.0
} }
@ -108,52 +93,7 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
} }
} }
/// Extract typed information from the request's path. /// See [here](#usage) for example of usage as an extractor.
///
/// ## Example
///
/// ```rust
/// use actix_web::{web, App};
///
/// /// extract path info from "/{username}/{count}/index.html" url
/// /// {username} - deserializes to a String
/// /// {count} - - deserializes to a u32
/// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String {
/// format!("Welcome {}! {}", username, count)
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/{username}/{count}/index.html") // <- define path parameters
/// .route(web::get().to(index)) // <- register handler with `Path` extractor
/// );
/// }
/// ```
///
/// It is possible to extract path information to a specific type that
/// implements `Deserialize` trait from *serde*.
///
/// ```rust
/// use actix_web::{web, App, Error};
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract `Info` from a path using serde
/// async fn index(info: web::Path<Info>) -> Result<String, Error> {
/// Ok(format!("Welcome {}!", info.username))
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/{username}/index.html") // <- define path parameters
/// .route(web::get().to(index)) // <- use handler with Path` extractor
/// );
/// }
/// ```
impl<T> FromRequest for Path<T> impl<T> FromRequest for Path<T>
where where
T: de::DeserializeOwned, T: de::DeserializeOwned,
@ -191,10 +131,10 @@ where
/// Path extractor configuration /// Path extractor configuration
/// ///
/// ```rust /// ```
/// use actix_web::web::PathConfig; /// use actix_web::web::PathConfig;
/// use actix_web::{error, web, App, FromRequest, HttpResponse}; /// use actix_web::{error, web, App, FromRequest, HttpResponse};
/// use serde_derive::Deserialize; /// use serde::Deserialize;
/// ///
/// #[derive(Deserialize, Debug)] /// #[derive(Deserialize, Debug)]
/// enum Folder { /// enum Folder {
@ -249,7 +189,7 @@ impl Default for PathConfig {
mod tests { mod tests {
use actix_router::ResourceDef; use actix_router::ResourceDef;
use derive_more::Display; use derive_more::Display;
use serde_derive::Deserialize; use serde::Deserialize;
use super::*; use super::*;
use crate::test::TestRequest; use crate::test::TestRequest;

View File

@ -1,57 +1,51 @@
//! Payload/Bytes/String extractors //! Basic binary and string payload 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 std::{
use actix_http::HttpMessage; future::Future,
pin::Pin,
str,
task::{Context, Poll},
};
use actix_http::error::{ErrorBadRequest, PayloadError};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use encoding_rs::{Encoding, UTF_8}; use encoding_rs::{Encoding, UTF_8};
use futures_core::stream::Stream; use futures_core::stream::Stream;
use futures_util::{ use futures_util::{
future::{err, ok, Either, ErrInto, Ready, TryFutureExt as _}, future::{ready, Either, ErrInto, Ready, TryFutureExt as _},
ready, ready,
}; };
use mime::Mime; use mime::Mime;
use crate::extract::FromRequest; use crate::{dev, http::header, web, Error, FromRequest, HttpMessage, HttpRequest};
use crate::http::header;
use crate::request::HttpRequest;
use crate::{dev, web};
/// Payload extractor returns request 's payload stream. /// Extract a request's raw payload stream.
/// ///
/// ## Example /// See [`PayloadConfig`] for important notes when using this advanced extractor.
/// ///
/// ```rust /// # Usage
/// use actix_web::{web, error, App, Error, HttpResponse}; /// ```
/// use std::future::Future; /// use std::future::Future;
/// use futures_core::stream::Stream; /// use futures_util::stream::{Stream, StreamExt};
/// use futures_util::StreamExt; /// use actix_web::{post, web};
/// /// extract binary data from request ///
/// async fn index(mut body: web::Payload) -> Result<HttpResponse, Error> /// // `body: web::Payload` parameter extracts raw payload stream from request
/// { /// #[post("/")]
/// async fn index(mut body: web::Payload) -> actix_web::Result<String> {
/// // for demonstration only; in a normal case use the `Bytes` extractor
/// // collect payload stream into a bytes object
/// let mut bytes = web::BytesMut::new(); /// let mut bytes = web::BytesMut::new();
/// while let Some(item) = body.next().await { /// while let Some(item) = body.next().await {
/// bytes.extend_from_slice(&item?); /// bytes.extend_from_slice(&item?);
/// } /// }
/// ///
/// format!("Body {:?}!", bytes); /// Ok(format!("Request Body Bytes:\n{:?}", bytes))
/// Ok(HttpResponse::Ok().finish())
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").route(
/// web::get().to(index))
/// );
/// } /// }
/// ``` /// ```
pub struct Payload(pub crate::dev::Payload); pub struct Payload(pub crate::dev::Payload);
impl Payload { impl Payload {
/// Deconstruct to a inner value /// Unwrap to inner Payload type.
pub fn into_inner(self) -> crate::dev::Payload { pub fn into_inner(self) -> crate::dev::Payload {
self.0 self.0
} }
@ -69,35 +63,7 @@ impl Stream for Payload {
} }
} }
/// Get request's payload stream /// See [here](#usage) for example of usage as an extractor.
///
/// ## Example
///
/// ```rust
/// use actix_web::{web, error, App, Error, HttpResponse};
/// use std::future::Future;
/// use futures_core::stream::Stream;
/// use futures_util::StreamExt;
///
/// /// extract binary data from request
/// async fn index(mut body: web::Payload) -> Result<HttpResponse, Error>
/// {
/// 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() {
/// let app = App::new().service(
/// web::resource("/index.html").route(
/// web::get().to(index))
/// );
/// }
/// ```
impl FromRequest for Payload { impl FromRequest for Payload {
type Config = PayloadConfig; type Config = PayloadConfig;
type Error = Error; type Error = Error;
@ -105,34 +71,25 @@ impl FromRequest for Payload {
#[inline] #[inline]
fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
ok(Payload(payload.take())) ready(Ok(Payload(payload.take())))
} }
} }
/// Request binary data from a request's payload. /// Extract binary data from a request's payload.
/// ///
/// Loads request's payload and construct Bytes instance. /// Collects request payload stream into a [Bytes] instance.
/// ///
/// [**PayloadConfig**](PayloadConfig) allows to configure /// Use [`PayloadConfig`] to configure extraction process.
/// extraction process.
/// ///
/// ## Example /// # Usage
/// /// ```
/// ```rust /// use actix_web::{post, web};
/// use bytes::Bytes;
/// use actix_web::{web, App};
/// ///
/// /// extract binary data from request /// /// extract binary data from request
/// async fn index(body: Bytes) -> String { /// #[post("/")]
/// async fn index(body: web::Bytes) -> String {
/// format!("Body {:?}!", body) /// format!("Body {:?}!", body)
/// } /// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").route(
/// web::get().to(index))
/// );
/// }
/// ``` /// ```
impl FromRequest for Bytes { impl FromRequest for Bytes {
type Config = PayloadConfig; type Config = PayloadConfig;
@ -144,8 +101,8 @@ impl FromRequest for Bytes {
// allow both Config and Data<Config> // allow both Config and Data<Config>
let cfg = PayloadConfig::from_req(req); let cfg = PayloadConfig::from_req(req);
if let Err(e) = cfg.check_mimetype(req) { if let Err(err) = cfg.check_mimetype(req) {
return Either::Right(err(e)); return Either::Right(ready(Err(err)));
} }
let limit = cfg.limit; let limit = cfg.limit;
@ -161,26 +118,15 @@ impl FromRequest for Bytes {
/// [**PayloadConfig**](PayloadConfig) allows to configure /// [**PayloadConfig**](PayloadConfig) allows to configure
/// extraction process. /// extraction process.
/// ///
/// ## Example /// # Usage
/// ```
/// use actix_web::{post, web, FromRequest};
/// ///
/// ```rust /// // extract text data from request
/// use actix_web::{web, App, FromRequest}; /// #[post("/")]
///
/// /// extract text data from request
/// async fn index(text: String) -> String { /// async fn index(text: String) -> String {
/// format!("Body {}!", text) /// format!("Body {}!", text)
/// } /// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html")
/// .app_data(String::configure(|cfg| { // <- limit size of the payload
/// cfg.limit(4096)
/// }))
/// .route(web::get().to(index)) // <- register handler with extractor params
/// );
/// }
/// ```
impl FromRequest for String { impl FromRequest for String {
type Config = PayloadConfig; type Config = PayloadConfig;
type Error = Error; type Error = Error;
@ -191,14 +137,14 @@ impl FromRequest for String {
let cfg = PayloadConfig::from_req(req); let cfg = PayloadConfig::from_req(req);
// check content-type // check content-type
if let Err(e) = cfg.check_mimetype(req) { if let Err(err) = cfg.check_mimetype(req) {
return Either::Right(err(e)); return Either::Right(ready(Err(err)));
} }
// check charset // check charset
let encoding = match req.encoding() { let encoding = match req.encoding() {
Ok(enc) => enc, Ok(enc) => enc,
Err(e) => return Either::Right(err(e.into())), Err(err) => return Either::Right(ready(Err(err.into()))),
}; };
let limit = cfg.limit; let limit = cfg.limit;
let body_fut = HttpMessageBody::new(req, payload).limit(limit); let body_fut = HttpMessageBody::new(req, payload).limit(limit);
@ -238,11 +184,13 @@ fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result<String, E
} }
} }
/// Configuration for request's payload. /// Configuration for request payloads.
/// ///
/// Applies to the built-in `Bytes` and `String` extractors. Note that the Payload extractor does /// Applies to the built-in `Bytes` and `String` extractors. Note that the `Payload` extractor does
/// not automatically check conformance with this configuration to allow more flexibility when /// not automatically check conformance with this configuration to allow more flexibility when
/// building extractors on top of `Payload`. /// building extractors on top of `Payload`.
///
/// By default, the payload size limit is 256kB and there is no mime type condition.
#[derive(Clone)] #[derive(Clone)]
pub struct PayloadConfig { pub struct PayloadConfig {
limit: usize, limit: usize,
@ -250,7 +198,7 @@ pub struct PayloadConfig {
} }
impl PayloadConfig { impl PayloadConfig {
/// Create `PayloadConfig` instance and set max size of payload. /// Create new instance with a size limit and no mime type condition.
pub fn new(limit: usize) -> Self { pub fn new(limit: usize) -> Self {
Self { Self {
limit, limit,
@ -258,14 +206,13 @@ impl PayloadConfig {
} }
} }
/// Change max size of payload. By default max size is 256Kb /// Set maximum accepted payload size. The default limit is 256kB.
pub fn limit(mut self, limit: usize) -> Self { pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit; self.limit = limit;
self self
} }
/// Set required mime-type of the request. By default mime type is not /// Set required mime type of the request. By default mime type is not enforced.
/// enforced.
pub fn mimetype(mut self, mt: Mime) -> Self { pub fn mimetype(mut self, mt: Mime) -> Self {
self.mimetype = Some(mt); self.mimetype = Some(mt);
self self
@ -292,7 +239,7 @@ impl PayloadConfig {
} }
/// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall /// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
/// back to the default payload config. /// back to the default payload config if neither is found.
fn from_req(req: &HttpRequest) -> &Self { fn from_req(req: &HttpRequest) -> &Self {
req.app_data::<Self>() req.app_data::<Self>()
.or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref())) .or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
@ -314,13 +261,10 @@ impl Default for PayloadConfig {
} }
} }
/// Future that resolves to a complete http message body. /// Future that resolves to a complete HTTP body payload.
/// ///
/// Load http message body. /// By default only 256kB payload is accepted before `PayloadError::Overflow` is returned.
/// /// Use `MessageBody::limit()` method to change upper limit.
/// By default only 256Kb payload reads to a memory, then
/// `PayloadError::Overflow` get returned. Use `MessageBody::limit()`
/// method to change upper limit.
pub struct HttpMessageBody { pub struct HttpMessageBody {
limit: usize, limit: usize,
length: Option<usize>, length: Option<usize>,
@ -366,7 +310,7 @@ impl HttpMessageBody {
} }
} }
/// Change max size of payload. By default max size is 256Kb /// Change max size of payload. By default max size is 256kB
pub fn limit(mut self, limit: usize) -> Self { pub fn limit(mut self, limit: usize) -> Self {
if let Some(l) = self.length { if let Some(l) = self.length {
if l > limit { if l > limit {
@ -384,8 +328,8 @@ impl Future for HttpMessageBody {
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut(); let this = self.get_mut();
if let Some(e) = this.err.take() { if let Some(err) = this.err.take() {
return Poll::Ready(Err(e)); return Poll::Ready(Err(err));
} }
loop { loop {

View File

@ -1,30 +1,27 @@
//! Query extractor //! For query parameter extractor documentation, see [`Query`].
use std::sync::Arc; use std::{fmt, ops, sync::Arc};
use std::{fmt, ops};
use actix_http::error::Error;
use futures_util::future::{err, ok, Ready}; use futures_util::future::{err, ok, Ready};
use serde::de; use serde::de;
use crate::dev::Payload; use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequest};
use crate::error::QueryPayloadError;
use crate::extract::FromRequest;
use crate::request::HttpRequest;
/// Extract typed information from the request's query. /// Extract typed information from the request's query.
/// ///
/// **Note**: A query string consists of unordered `key=value` pairs, therefore it cannot /// To extract typed data from the URL query string, the inner type `T` must implement the
/// be decoded into any type which depends upon data ordering e.g. tuples or tuple-structs. /// [`serde::Deserialize`] trait.
/// Attempts to do so will *fail at runtime*.
/// ///
/// [**QueryConfig**](QueryConfig) allows to configure extraction process. /// Use [`QueryConfig`] to configure extraction process.
/// ///
/// ## Example /// # Panics
/// A query string consists of unordered `key=value` pairs, therefore it cannot be decoded into any
/// type which depends upon data ordering (eg. tuples). Trying to do so will result in a panic.
/// ///
/// ```rust /// # Usage
/// use actix_web::{web, App}; /// ```
/// use serde_derive::Deserialize; /// use actix_web::{get, web};
/// use serde::Deserialize;
/// ///
/// #[derive(Debug, Deserialize)] /// #[derive(Debug, Deserialize)]
/// pub enum ResponseType { /// pub enum ResponseType {
@ -38,35 +35,40 @@ use crate::request::HttpRequest;
/// response_type: ResponseType, /// response_type: ResponseType,
/// } /// }
/// ///
/// // Use `Query` extractor for query information (and destructure it within the signature). /// // Deserialize `AuthRequest` struct from query string.
/// // This handler gets called only if the request's query string contains `id` and `response_type` fields. /// // This handler gets called only if the request's query parameters contain both fields.
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`. /// // A valid request path for this handler would be `/?id=64&response_type=Code"`.
/// async fn index(web::Query(info): web::Query<AuthRequest>) -> String { /// #[get("/")]
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) /// async fn index(info: web::Query<AuthRequest>) -> String {
/// } /// format!("Authorization request for id={} and type={:?}!", info.id, info.response_type)
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor
/// } /// }
/// ``` /// ```
#[derive(PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Query<T>(pub T); pub struct Query<T>(T);
impl<T> Query<T> { impl<T> Query<T> {
/// Deconstruct to a inner value /// Unwrap into inner `T` value.
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
self.0 self.0
} }
/// Get query parameters from the path /// Deserialize `T` from a URL encoded query parameter string.
///
/// ```
/// # use std::collections::HashMap;
/// # use actix_web::web::Query;
/// let numbers = Query::<HashMap<String, u32>>::from_query("one=1&two=2").unwrap();
/// assert_eq!(numbers.get("one"), Some(&1));
/// assert_eq!(numbers.get("two"), Some(&2));
/// assert!(numbers.get("three").is_none());
/// ```
pub fn from_query(query_str: &str) -> Result<Self, QueryPayloadError> pub fn from_query(query_str: &str) -> Result<Self, QueryPayloadError>
where where
T: de::DeserializeOwned, T: de::DeserializeOwned,
{ {
serde_urlencoded::from_str::<T>(query_str) serde_urlencoded::from_str::<T>(query_str)
.map(|val| Ok(Query(val))) .map(Self)
.unwrap_or_else(move |e| Err(QueryPayloadError::Deserialize(e))) .map_err(QueryPayloadError::Deserialize)
} }
} }
@ -96,39 +98,7 @@ impl<T: fmt::Display> fmt::Display for Query<T> {
} }
} }
/// Extract typed information from the request's query. /// See [here](#usage) for example of usage as an extractor.
///
/// ## Example
///
/// ```rust
/// use actix_web::{web, App};
/// use serde_derive::Deserialize;
///
/// #[derive(Debug, Deserialize)]
/// pub enum ResponseType {
/// Token,
/// Code
/// }
///
/// #[derive(Deserialize)]
/// pub struct AuthRequest {
/// id: u64,
/// response_type: ResponseType,
/// }
///
/// // Use `Query` extractor for query information.
/// // This handler get called only if request's query contains `id` and `response_type` fields.
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`
/// async fn index(info: web::Query<AuthRequest>) -> String {
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html")
/// .route(web::get().to(index))); // <- use `Query` extractor
/// }
/// ```
impl<T> FromRequest for Query<T> impl<T> FromRequest for Query<T>
where where
T: de::DeserializeOwned, T: de::DeserializeOwned,
@ -141,7 +111,7 @@ where
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let error_handler = req let error_handler = req
.app_data::<Self::Config>() .app_data::<Self::Config>()
.map(|c| c.ehandler.clone()) .map(|c| c.err_handler.clone())
.unwrap_or(None); .unwrap_or(None);
serde_urlencoded::from_str::<T>(req.query_string()) serde_urlencoded::from_str::<T>(req.query_string())
@ -166,13 +136,12 @@ where
} }
} }
/// Query extractor configuration /// Query extractor configuration.
/// ///
/// ## Example /// # Usage
/// /// ```
/// ```rust /// use actix_web::{error, get, web, App, FromRequest, HttpResponse};
/// use actix_web::{error, web, App, FromRequest, HttpResponse}; /// use serde::Deserialize;
/// use serde_derive::Deserialize;
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct Info { /// struct Info {
@ -180,27 +149,25 @@ where
/// } /// }
/// ///
/// /// deserialize `Info` from request's querystring /// /// deserialize `Info` from request's querystring
/// #[get("/")]
/// async fn index(info: web::Query<Info>) -> String { /// async fn index(info: web::Query<Info>) -> String {
/// format!("Welcome {}!", info.username) /// format!("Welcome {}!", info.username)
/// } /// }
/// ///
/// fn main() { /// // custom `Query` extractor configuration
/// let app = App::new().service( /// let query_cfg = web::QueryConfig::default()
/// web::resource("/index.html").app_data( /// // use custom error handler
/// // change query extractor configuration /// .error_handler(|err, req| {
/// web::QueryConfig::default() /// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
/// .error_handler(|err, req| { // <- create custom error response /// });
/// error::InternalError::from_response( ///
/// err, HttpResponse::Conflict().finish()).into() /// App::new()
/// }) /// .app_data(query_cfg)
/// ) /// .service(index);
/// .route(web::post().to(index))
/// );
/// }
/// ``` /// ```
#[derive(Clone)] #[derive(Clone)]
pub struct QueryConfig { pub struct QueryConfig {
ehandler: err_handler:
Option<Arc<dyn Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync>>, Option<Arc<dyn Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync>>,
} }
@ -210,14 +177,14 @@ impl QueryConfig {
where where
F: Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync + 'static, F: Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync + 'static,
{ {
self.ehandler = Some(Arc::new(f)); self.err_handler = Some(Arc::new(f));
self self
} }
} }
impl Default for QueryConfig { impl Default for QueryConfig {
fn default() -> Self { fn default() -> Self {
QueryConfig { ehandler: None } QueryConfig { err_handler: None }
} }
} }
@ -225,7 +192,7 @@ impl Default for QueryConfig {
mod tests { mod tests {
use actix_http::http::StatusCode; use actix_http::http::StatusCode;
use derive_more::Display; use derive_more::Display;
use serde_derive::Deserialize; use serde::Deserialize;
use super::*; use super::*;
use crate::error::InternalError; use crate::error::InternalError;
@ -271,6 +238,17 @@ mod tests {
assert_eq!(s.id, "test1"); assert_eq!(s.id, "test1");
} }
#[actix_rt::test]
#[should_panic]
async fn test_tuple_panic() {
let req = TestRequest::with_uri("/?one=1&two=2").to_srv_request();
let (req, mut pl) = req.into_parts();
Query::<(u32, u32)>::from_request(&req, &mut pl)
.await
.unwrap();
}
#[actix_rt::test] #[actix_rt::test]
async fn test_custom_error_responder() { async fn test_custom_error_responder() {
let req = TestRequest::with_uri("/name/user1/") let req = TestRequest::with_uri("/name/user1/")

View File

@ -1,17 +1,23 @@
use std::borrow::Cow; //! For request line reader documentation, see [`Readlines`].
use std::pin::Pin;
use std::str; use std::{
use std::task::{Context, Poll}; borrow::Cow,
pin::Pin,
str,
task::{Context, Poll},
};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use encoding_rs::{Encoding, UTF_8}; use encoding_rs::{Encoding, UTF_8};
use futures_util::stream::Stream; use futures_core::{ready, stream::Stream};
use crate::dev::Payload; use crate::{
use crate::error::{PayloadError, ReadlinesError}; dev::Payload,
use crate::HttpMessage; error::{PayloadError, ReadlinesError},
HttpMessage,
};
/// Stream to read request line by line. /// Stream that reads request line by line.
pub struct Readlines<T: HttpMessage> { pub struct Readlines<T: HttpMessage> {
stream: Payload<T::Stream>, stream: Payload<T::Stream>,
buff: BytesMut, buff: BytesMut,
@ -43,7 +49,7 @@ where
} }
} }
/// Change max line size. By default max size is 256Kb /// Set maximum accepted payload size. The default limit is 256kB.
pub fn limit(mut self, limit: usize) -> Self { pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit; self.limit = limit;
self self
@ -108,9 +114,10 @@ where
} }
this.checked_buff = true; this.checked_buff = true;
} }
// poll req for more bytes // poll req for more bytes
match Pin::new(&mut this.stream).poll_next(cx) { match ready!(Pin::new(&mut this.stream).poll_next(cx)) {
Poll::Ready(Some(Ok(mut bytes))) => { Some(Ok(mut bytes)) => {
// check if there is a newline in bytes // check if there is a newline in bytes
let mut found: Option<usize> = None; let mut found: Option<usize> = None;
for (ind, b) in bytes.iter().enumerate() { for (ind, b) in bytes.iter().enumerate() {
@ -144,8 +151,8 @@ where
this.buff.extend_from_slice(&bytes); this.buff.extend_from_slice(&bytes);
Poll::Pending Poll::Pending
} }
Poll::Pending => Poll::Pending,
Poll::Ready(None) => { None => {
if this.buff.is_empty() { if this.buff.is_empty() {
return Poll::Ready(None); return Poll::Ready(None);
} }
@ -165,7 +172,8 @@ where
this.buff.clear(); this.buff.clear();
Poll::Ready(Some(Ok(line))) Poll::Ready(Some(Ok(line)))
} }
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ReadlinesError::from(e)))),
Some(Err(err)) => Poll::Ready(Some(Err(ReadlinesError::from(err)))),
} }
} }
} }

View File

@ -280,5 +280,8 @@ where
I: Send + 'static, I: Send + 'static,
E: Send + std::fmt::Debug + 'static, E: Send + std::fmt::Debug + 'static,
{ {
actix_threadpool::run(f).await match actix_rt::task::spawn_blocking(f).await {
Ok(res) => res.map_err(BlockingError::Error),
Err(_) => Err(BlockingError::Canceled),
}
} }