mirror of https://github.com/fafhrd91/actix-web
Merge remote-tracking branch 'github/master' into bump_json_limits
This commit is contained in:
commit
39bf19bcc0
|
@ -5,6 +5,7 @@
|
||||||
* Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162]
|
* Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162]
|
||||||
|
|
||||||
[#2162]: (https://github.com/actix/actix-web/pull/2162)
|
[#2162]: (https://github.com/actix/actix-web/pull/2162)
|
||||||
|
* Update `language-tags` to `0.3`.
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.6 - 2021-04-17
|
## 4.0.0-beta.6 - 2021-04-17
|
||||||
|
|
|
@ -78,7 +78,7 @@ 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 }
|
||||||
itoa = "0.4"
|
itoa = "0.4"
|
||||||
language-tags = "0.2"
|
language-tags = "0.3"
|
||||||
once_cell = "1.5"
|
once_cell = "1.5"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
## 0.6.0-beta.4 - 2021-04-02
|
## 0.6.0-beta.4 - 2021-04-02
|
||||||
* No notable changes.
|
* No notable changes.
|
||||||
|
|
||||||
|
* Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046]
|
||||||
|
|
||||||
|
[#2046]: https://github.com/actix/actix-web/pull/2046
|
||||||
|
|
||||||
## 0.6.0-beta.3 - 2021-03-09
|
## 0.6.0-beta.3 - 2021-03-09
|
||||||
* No notable changes.
|
* No notable changes.
|
||||||
|
|
|
@ -37,7 +37,8 @@ pub struct Files {
|
||||||
renderer: Rc<DirectoryRenderer>,
|
renderer: Rc<DirectoryRenderer>,
|
||||||
mime_override: Option<Rc<MimeOverride>>,
|
mime_override: Option<Rc<MimeOverride>>,
|
||||||
file_flags: named::Flags,
|
file_flags: named::Flags,
|
||||||
guards: Option<Rc<dyn Guard>>,
|
use_guards: Option<Rc<dyn Guard>>,
|
||||||
|
guards: Vec<Box<Rc<dyn Guard>>>,
|
||||||
hidden_files: bool,
|
hidden_files: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,12 +60,12 @@ impl Clone for Files {
|
||||||
file_flags: self.file_flags,
|
file_flags: self.file_flags,
|
||||||
path: self.path.clone(),
|
path: self.path.clone(),
|
||||||
mime_override: self.mime_override.clone(),
|
mime_override: self.mime_override.clone(),
|
||||||
|
use_guards: self.use_guards.clone(),
|
||||||
guards: self.guards.clone(),
|
guards: self.guards.clone(),
|
||||||
hidden_files: self.hidden_files,
|
hidden_files: self.hidden_files,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Files {
|
impl Files {
|
||||||
/// Create new `Files` instance for a specified base directory.
|
/// Create new `Files` instance for a specified base directory.
|
||||||
///
|
///
|
||||||
|
@ -80,10 +81,9 @@ impl Files {
|
||||||
/// If the mount path is set as the root path `/`, services registered after this one will
|
/// If the mount path is set as the root path `/`, services registered after this one will
|
||||||
/// 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` utilizes the existing Tokio thread-pool for blocking filesystem operations.
|
||||||
/// max number of threads equal to `512 * HttpServer::worker`. Real time thread count are
|
/// The number of running threads is adjusted over time as needed, up to a maximum of 512 times
|
||||||
/// adjusted with work load. More threads would spawn when need and threads goes idle for a
|
/// the number of server [workers](HttpServer::workers), by default.
|
||||||
/// 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() {
|
||||||
|
@ -104,7 +104,8 @@ impl Files {
|
||||||
renderer: Rc::new(directory_listing),
|
renderer: Rc::new(directory_listing),
|
||||||
mime_override: None,
|
mime_override: None,
|
||||||
file_flags: named::Flags::default(),
|
file_flags: named::Flags::default(),
|
||||||
guards: None,
|
use_guards: None,
|
||||||
|
guards: Vec::new(),
|
||||||
hidden_files: false,
|
hidden_files: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,7 +157,6 @@ impl Files {
|
||||||
/// Specifies whether to use ETag or not.
|
/// Specifies whether to use ETag or not.
|
||||||
///
|
///
|
||||||
/// Default is true.
|
/// Default is true.
|
||||||
#[inline]
|
|
||||||
pub fn use_etag(mut self, value: bool) -> Self {
|
pub fn use_etag(mut self, value: bool) -> Self {
|
||||||
self.file_flags.set(named::Flags::ETAG, value);
|
self.file_flags.set(named::Flags::ETAG, value);
|
||||||
self
|
self
|
||||||
|
@ -165,7 +165,6 @@ impl Files {
|
||||||
/// Specifies whether to use Last-Modified or not.
|
/// Specifies whether to use Last-Modified or not.
|
||||||
///
|
///
|
||||||
/// Default is true.
|
/// Default is true.
|
||||||
#[inline]
|
|
||||||
pub fn use_last_modified(mut self, value: bool) -> Self {
|
pub fn use_last_modified(mut self, value: bool) -> Self {
|
||||||
self.file_flags.set(named::Flags::LAST_MD, value);
|
self.file_flags.set(named::Flags::LAST_MD, value);
|
||||||
self
|
self
|
||||||
|
@ -174,25 +173,55 @@ impl Files {
|
||||||
/// Specifies whether text responses should signal a UTF-8 encoding.
|
/// Specifies whether text responses should signal a UTF-8 encoding.
|
||||||
///
|
///
|
||||||
/// Default is false (but will default to true in a future version).
|
/// Default is false (but will default to true in a future version).
|
||||||
#[inline]
|
|
||||||
pub fn prefer_utf8(mut self, value: bool) -> Self {
|
pub fn prefer_utf8(mut self, value: bool) -> Self {
|
||||||
self.file_flags.set(named::Flags::PREFER_UTF8, value);
|
self.file_flags.set(named::Flags::PREFER_UTF8, value);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specifies custom guards to use for directory listings and files.
|
/// Adds a routing guard.
|
||||||
///
|
///
|
||||||
/// Default behaviour allows GET and HEAD.
|
/// Use this to allow multiple chained file services that respond to strictly different
|
||||||
#[inline]
|
/// properties of a request. Due to the way routing works, if a guard check returns true and the
|
||||||
pub fn use_guards<G: Guard + 'static>(mut self, guards: G) -> Self {
|
/// request starts being handled by the file service, it will not be able to back-out and try
|
||||||
self.guards = Some(Rc::new(guards));
|
/// the next service, you will simply get a 404 (or 405) error response.
|
||||||
|
///
|
||||||
|
/// To allow `POST` requests to retrieve files, see [`Files::use_guards`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{guard::Header, App};
|
||||||
|
/// use actix_files::Files;
|
||||||
|
///
|
||||||
|
/// App::new().service(
|
||||||
|
/// Files::new("/","/my/site/files")
|
||||||
|
/// .guard(Header("Host", "example.com"))
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
|
||||||
|
self.guards.push(Box::new(Rc::new(guard)));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Specifies guard to check before fetching directory listings or files.
|
||||||
|
///
|
||||||
|
/// Note that this guard has no effect on routing; it's main use is to guard on the request's
|
||||||
|
/// method just before serving the file, only allowing `GET` and `HEAD` requests by default.
|
||||||
|
/// See [`Files::guard`] for routing guards.
|
||||||
|
pub fn method_guard<G: Guard + 'static>(mut self, guard: G) -> Self {
|
||||||
|
self.use_guards = Some(Rc::new(guard));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[deprecated(since = "0.6.0", note = "Renamed to `method_guard`.")]
|
||||||
|
/// See [`Files::method_guard`].
|
||||||
|
pub fn use_guards<G: Guard + 'static>(self, guard: G) -> Self {
|
||||||
|
self.method_guard(guard)
|
||||||
|
}
|
||||||
|
|
||||||
/// Disable `Content-Disposition` header.
|
/// Disable `Content-Disposition` header.
|
||||||
///
|
///
|
||||||
/// By default Content-Disposition` header is enabled.
|
/// By default Content-Disposition` header is enabled.
|
||||||
#[inline]
|
|
||||||
pub fn disable_content_disposition(mut self) -> Self {
|
pub fn disable_content_disposition(mut self) -> Self {
|
||||||
self.file_flags.remove(named::Flags::CONTENT_DISPOSITION);
|
self.file_flags.remove(named::Flags::CONTENT_DISPOSITION);
|
||||||
self
|
self
|
||||||
|
@ -200,8 +229,9 @@ impl Files {
|
||||||
|
|
||||||
/// Sets default handler which is used when no matched file could be found.
|
/// Sets default handler which is used when no matched file could be found.
|
||||||
///
|
///
|
||||||
/// For example, you could set a fall back static file handler:
|
/// # Examples
|
||||||
/// ```rust
|
/// Setting a fallback static file handler:
|
||||||
|
/// ```
|
||||||
/// use actix_files::{Files, NamedFile};
|
/// use actix_files::{Files, NamedFile};
|
||||||
///
|
///
|
||||||
/// # fn run() -> Result<(), actix_web::Error> {
|
/// # fn run() -> Result<(), actix_web::Error> {
|
||||||
|
@ -230,7 +260,6 @@ impl Files {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enables serving hidden files and directories, allowing a leading dots in url fragments.
|
/// Enables serving hidden files and directories, allowing a leading dots in url fragments.
|
||||||
#[inline]
|
|
||||||
pub fn use_hidden_files(mut self) -> Self {
|
pub fn use_hidden_files(mut self) -> Self {
|
||||||
self.hidden_files = true;
|
self.hidden_files = true;
|
||||||
self
|
self
|
||||||
|
@ -238,7 +267,19 @@ impl Files {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpServiceFactory for Files {
|
impl HttpServiceFactory for Files {
|
||||||
fn register(self, config: &mut AppService) {
|
fn register(mut self, config: &mut AppService) {
|
||||||
|
let guards = if self.guards.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let guards = std::mem::take(&mut self.guards);
|
||||||
|
Some(
|
||||||
|
guards
|
||||||
|
.into_iter()
|
||||||
|
.map(|guard| -> Box<dyn Guard> { guard })
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
if self.default.borrow().is_none() {
|
if self.default.borrow().is_none() {
|
||||||
*self.default.borrow_mut() = Some(config.default_service());
|
*self.default.borrow_mut() = Some(config.default_service());
|
||||||
}
|
}
|
||||||
|
@ -249,7 +290,7 @@ impl HttpServiceFactory for Files {
|
||||||
ResourceDef::prefix(&self.path)
|
ResourceDef::prefix(&self.path)
|
||||||
};
|
};
|
||||||
|
|
||||||
config.register_service(rdef, None, self, None)
|
config.register_service(rdef, guards, self, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +312,7 @@ impl ServiceFactory<ServiceRequest> for Files {
|
||||||
renderer: self.renderer.clone(),
|
renderer: self.renderer.clone(),
|
||||||
mime_override: self.mime_override.clone(),
|
mime_override: self.mime_override.clone(),
|
||||||
file_flags: self.file_flags,
|
file_flags: self.file_flags,
|
||||||
guards: self.guards.clone(),
|
guards: self.use_guards.clone(),
|
||||||
hidden_files: self.hidden_files,
|
hidden_files: self.hidden_files,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -532,7 +532,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_files_guards() {
|
async fn test_files_guards() {
|
||||||
let srv = test::init_service(
|
let srv = test::init_service(
|
||||||
App::new().service(Files::new("/", ".").use_guards(guard::Post())),
|
App::new().service(Files::new("/", ".").method_guard(guard::Post())),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
first
|
|
@ -0,0 +1 @@
|
||||||
|
second
|
|
@ -0,0 +1,36 @@
|
||||||
|
use actix_files::Files;
|
||||||
|
use actix_web::{
|
||||||
|
guard::Host,
|
||||||
|
http::StatusCode,
|
||||||
|
test::{self, TestRequest},
|
||||||
|
App,
|
||||||
|
};
|
||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_guard_filter() {
|
||||||
|
let srv = test::init_service(
|
||||||
|
App::new()
|
||||||
|
.service(Files::new("/", "./tests/fixtures/guards/first").guard(Host("first.com")))
|
||||||
|
.service(
|
||||||
|
Files::new("/", "./tests/fixtures/guards/second").guard(Host("second.com")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/index.txt")
|
||||||
|
.append_header(("Host", "first.com"))
|
||||||
|
.to_request();
|
||||||
|
let res = test::call_service(&srv, req).await;
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(test::read_body(res).await, Bytes::from("first"));
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/index.txt")
|
||||||
|
.append_header(("Host", "second.com"))
|
||||||
|
.to_request();
|
||||||
|
let res = test::call_service(&srv, req).await;
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(test::read_body(res).await, Bytes::from("second"));
|
||||||
|
}
|
|
@ -9,11 +9,14 @@
|
||||||
### Changed
|
### Changed
|
||||||
* `header` mod is now public. [#2171]
|
* `header` mod is now public. [#2171]
|
||||||
* `uri` mod is now public. [#2171]
|
* `uri` mod is now public. [#2171]
|
||||||
|
* Update `language-tags` to `0.3`.
|
||||||
|
* Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2196]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171]
|
* Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171]
|
||||||
|
|
||||||
[#2171]: https://github.com/actix/actix-web/pull/2171
|
[#2171]: https://github.com/actix/actix-web/pull/2171
|
||||||
|
[#2196]: https://github.com/actix/actix-web/pull/2196
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.6 - 2021-04-17
|
## 3.0.0-beta.6 - 2021-04-17
|
||||||
|
|
|
@ -57,7 +57,7 @@ h2 = "0.3.1"
|
||||||
http = "0.2.2"
|
http = "0.2.2"
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
itoa = "0.4"
|
itoa = "0.4"
|
||||||
language-tags = "0.2"
|
language-tags = "0.3"
|
||||||
local-channel = "0.1"
|
local-channel = "0.1"
|
||||||
once_cell = "1.5"
|
once_cell = "1.5"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -78,7 +78,7 @@ impl Response<Body> {
|
||||||
pub fn from_error(error: Error) -> Response<Body> {
|
pub fn from_error(error: Error) -> Response<Body> {
|
||||||
let mut resp = error.as_response_error().error_response();
|
let mut resp = error.as_response_error().error_response();
|
||||||
if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR {
|
if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR {
|
||||||
error!("Internal Server Error: {:?}", error);
|
debug!("Internal Server Error: {:?}", error);
|
||||||
}
|
}
|
||||||
resp.error = Some(error);
|
resp.error = Some(error);
|
||||||
resp
|
resp
|
||||||
|
|
24
src/error.rs
24
src/error.rs
|
@ -86,13 +86,17 @@ impl ResponseError for UrlencodedError {
|
||||||
#[derive(Debug, Display, Error)]
|
#[derive(Debug, Display, Error)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum JsonPayloadError {
|
pub enum JsonPayloadError {
|
||||||
/// Payload size is bigger than allowed. (default: 2MB)
|
/// Payload size is bigger than allowed & content length header set. (default: 2MB)
|
||||||
#[display(
|
#[display(
|
||||||
fmt = "JSON payload ({} bytes) is larger than allowed (limit: {} bytes).",
|
fmt = "JSON payload ({} bytes) is larger than allowed (limit: {} bytes).",
|
||||||
size,
|
length,
|
||||||
limit
|
limit
|
||||||
)]
|
)]
|
||||||
Overflow { size: usize, limit: usize },
|
OverflowKnownLength { length: usize, limit: usize },
|
||||||
|
|
||||||
|
/// Payload size is bigger than allowed but no content length header set. (default: 2MB)
|
||||||
|
#[display(fmt = "JSON payload has exceeded limit ({} bytes).", limit)]
|
||||||
|
Overflow { limit: usize },
|
||||||
|
|
||||||
/// Content type error
|
/// Content type error
|
||||||
#[display(fmt = "Content type error")]
|
#[display(fmt = "Content type error")]
|
||||||
|
@ -120,7 +124,11 @@ impl From<PayloadError> for JsonPayloadError {
|
||||||
impl ResponseError for JsonPayloadError {
|
impl ResponseError for JsonPayloadError {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
match self {
|
match self {
|
||||||
Self::Overflow { size: _, limit: _ } => StatusCode::PAYLOAD_TOO_LARGE,
|
Self::OverflowKnownLength {
|
||||||
|
length: _,
|
||||||
|
limit: _,
|
||||||
|
} => StatusCode::PAYLOAD_TOO_LARGE,
|
||||||
|
Self::Overflow { limit: _ } => StatusCode::PAYLOAD_TOO_LARGE,
|
||||||
Self::Serialize(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
Self::Serialize(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Self::Payload(err) => err.status_code(),
|
Self::Payload(err) => err.status_code(),
|
||||||
_ => StatusCode::BAD_REQUEST,
|
_ => StatusCode::BAD_REQUEST,
|
||||||
|
@ -207,7 +215,13 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_json_payload_error() {
|
fn test_json_payload_error() {
|
||||||
let resp = JsonPayloadError::Overflow { size: 0, limit: 0 }.error_response();
|
let resp = JsonPayloadError::OverflowKnownLength {
|
||||||
|
length: 0,
|
||||||
|
limit: 0,
|
||||||
|
}
|
||||||
|
.error_response();
|
||||||
|
assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE);
|
||||||
|
let resp = JsonPayloadError::Overflow { limit: 0 }.error_response();
|
||||||
assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE);
|
assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE);
|
||||||
let resp = JsonPayloadError::ContentType.error_response();
|
let resp = JsonPayloadError::ContentType.error_response();
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
//! ```
|
//! ```
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use actix_http::http::{self, header, uri::Uri};
|
use actix_http::http::{self, header, uri::Uri};
|
||||||
use actix_http::RequestHead;
|
use actix_http::RequestHead;
|
||||||
|
@ -40,6 +42,12 @@ pub trait Guard {
|
||||||
fn check(&self, request: &RequestHead) -> bool;
|
fn check(&self, request: &RequestHead) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Guard for Rc<dyn Guard> {
|
||||||
|
fn check(&self, request: &RequestHead) -> bool {
|
||||||
|
self.deref().check(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create guard object for supplied function.
|
/// Create guard object for supplied function.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
@ -24,14 +24,11 @@ crate::__define_common_header! {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use language_tags::langtag;
|
|
||||||
/// use actix_web::HttpResponse;
|
/// use actix_web::HttpResponse;
|
||||||
/// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem};
|
/// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem};
|
||||||
///
|
///
|
||||||
/// let mut builder = HttpResponse::Ok();
|
/// let mut builder = HttpResponse::Ok();
|
||||||
/// let mut langtag: LanguageTag = Default::default();
|
/// let langtag = LanguageTag::parse("en-US").unwrap();
|
||||||
/// langtag.language = Some("en".to_owned());
|
|
||||||
/// langtag.region = Some("US".to_owned());
|
|
||||||
/// builder.insert_header(
|
/// builder.insert_header(
|
||||||
/// AcceptLanguage(vec![
|
/// AcceptLanguage(vec![
|
||||||
/// qitem(langtag),
|
/// qitem(langtag),
|
||||||
|
@ -40,16 +37,15 @@ crate::__define_common_header! {
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use language_tags::langtag;
|
|
||||||
/// use actix_web::HttpResponse;
|
/// use actix_web::HttpResponse;
|
||||||
/// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem};
|
/// use actix_web::http::header::{AcceptLanguage, LanguageTag, QualityItem, q, qitem};
|
||||||
///
|
///
|
||||||
/// let mut builder = HttpResponse::Ok();
|
/// let mut builder = HttpResponse::Ok();
|
||||||
/// builder.insert_header(
|
/// builder.insert_header(
|
||||||
/// AcceptLanguage(vec![
|
/// AcceptLanguage(vec![
|
||||||
/// qitem(langtag!(da)),
|
/// qitem(LanguageTag::parse("da").unwrap()),
|
||||||
/// QualityItem::new(langtag!(en;;;GB), q(800)),
|
/// QualityItem::new(LanguageTag::parse("en-GB").unwrap(), q(800)),
|
||||||
/// QualityItem::new(langtag!(en), q(700)),
|
/// QualityItem::new(LanguageTag::parse("en").unwrap(), q(700)),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
@ -24,28 +24,26 @@ crate::__define_common_header! {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use language_tags::langtag;
|
|
||||||
/// use actix_web::HttpResponse;
|
/// use actix_web::HttpResponse;
|
||||||
/// use actix_web::http::header::{ContentLanguage, qitem};
|
/// use actix_web::http::header::{ContentLanguage, LanguageTag, qitem};
|
||||||
///
|
///
|
||||||
/// let mut builder = HttpResponse::Ok();
|
/// let mut builder = HttpResponse::Ok();
|
||||||
/// builder.insert_header(
|
/// builder.insert_header(
|
||||||
/// ContentLanguage(vec![
|
/// ContentLanguage(vec![
|
||||||
/// qitem(langtag!(en)),
|
/// qitem(LanguageTag::parse("en").unwrap()),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use language_tags::langtag;
|
|
||||||
/// use actix_web::HttpResponse;
|
/// use actix_web::HttpResponse;
|
||||||
/// use actix_web::http::header::{ContentLanguage, qitem};
|
/// use actix_web::http::header::{ContentLanguage, LanguageTag, qitem};
|
||||||
///
|
///
|
||||||
/// let mut builder = HttpResponse::Ok();
|
/// let mut builder = HttpResponse::Ok();
|
||||||
/// builder.insert_header(
|
/// builder.insert_header(
|
||||||
/// ContentLanguage(vec![
|
/// ContentLanguage(vec![
|
||||||
/// qitem(langtag!(da)),
|
/// qitem(LanguageTag::parse("da").unwrap()),
|
||||||
/// qitem(langtag!(en;;;GB)),
|
/// qitem(LanguageTag::parse("en-GB").unwrap()),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
//! For either helper, see [`Either`].
|
//! For either helper, see [`Either`].
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
future::Future,
|
||||||
|
mem,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::ready;
|
||||||
use futures_util::{FutureExt as _, TryFutureExt as _};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dev,
|
dev,
|
||||||
|
@ -180,41 +186,111 @@ where
|
||||||
R: FromRequest + 'static,
|
R: FromRequest + 'static,
|
||||||
{
|
{
|
||||||
type Error = EitherExtractError<L::Error, R::Error>;
|
type Error = EitherExtractError<L::Error, R::Error>;
|
||||||
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
type Future = EitherExtractFut<L, R>;
|
||||||
type Config = ();
|
type Config = ();
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||||
let req2 = req.clone();
|
EitherExtractFut {
|
||||||
|
req: req.clone(),
|
||||||
Bytes::from_request(req, payload)
|
state: EitherExtractState::Bytes {
|
||||||
.map_err(EitherExtractError::Bytes)
|
bytes: Bytes::from_request(req, payload),
|
||||||
.and_then(|bytes| bytes_to_l_or_r(req2, bytes))
|
},
|
||||||
.boxed_local()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn bytes_to_l_or_r<L, R>(
|
#[pin_project::pin_project]
|
||||||
req: HttpRequest,
|
pub struct EitherExtractFut<L, R>
|
||||||
bytes: Bytes,
|
|
||||||
) -> Result<Either<L, R>, EitherExtractError<L::Error, R::Error>>
|
|
||||||
where
|
where
|
||||||
L: FromRequest + 'static,
|
R: FromRequest,
|
||||||
R: FromRequest + 'static,
|
L: FromRequest,
|
||||||
{
|
{
|
||||||
let fallback = bytes.clone();
|
req: HttpRequest,
|
||||||
let a_err;
|
#[pin]
|
||||||
|
state: EitherExtractState<L, R>,
|
||||||
|
}
|
||||||
|
|
||||||
let mut pl = payload_from_bytes(bytes);
|
#[pin_project::pin_project(project = EitherExtractProj)]
|
||||||
match L::from_request(&req, &mut pl).await {
|
pub enum EitherExtractState<L, R>
|
||||||
Ok(a_data) => return Ok(Either::Left(a_data)),
|
where
|
||||||
// store A's error for returning if B also fails
|
L: FromRequest,
|
||||||
Err(err) => a_err = err,
|
R: FromRequest,
|
||||||
};
|
{
|
||||||
|
Bytes {
|
||||||
|
#[pin]
|
||||||
|
bytes: <Bytes as FromRequest>::Future,
|
||||||
|
},
|
||||||
|
Left {
|
||||||
|
#[pin]
|
||||||
|
left: L::Future,
|
||||||
|
fallback: Bytes,
|
||||||
|
},
|
||||||
|
Right {
|
||||||
|
#[pin]
|
||||||
|
right: R::Future,
|
||||||
|
left_err: Option<L::Error>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
let mut pl = payload_from_bytes(fallback);
|
impl<R, RF, RE, L, LF, LE> Future for EitherExtractFut<L, R>
|
||||||
match R::from_request(&req, &mut pl).await {
|
where
|
||||||
Ok(b_data) => return Ok(Either::Right(b_data)),
|
L: FromRequest<Future = LF, Error = LE>,
|
||||||
Err(b_err) => Err(EitherExtractError::Extract(a_err, b_err)),
|
R: FromRequest<Future = RF, Error = RE>,
|
||||||
|
LF: Future<Output = Result<L, LE>> + 'static,
|
||||||
|
RF: Future<Output = Result<R, RE>> + 'static,
|
||||||
|
LE: Into<Error>,
|
||||||
|
RE: Into<Error>,
|
||||||
|
{
|
||||||
|
type Output = Result<Either<L, R>, EitherExtractError<LE, RE>>;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let mut this = self.project();
|
||||||
|
let ready = loop {
|
||||||
|
let next = match this.state.as_mut().project() {
|
||||||
|
EitherExtractProj::Bytes { bytes } => {
|
||||||
|
let res = ready!(bytes.poll(cx));
|
||||||
|
match res {
|
||||||
|
Ok(bytes) => {
|
||||||
|
let fallback = bytes.clone();
|
||||||
|
let left =
|
||||||
|
L::from_request(&this.req, &mut payload_from_bytes(bytes));
|
||||||
|
EitherExtractState::Left { left, fallback }
|
||||||
|
}
|
||||||
|
Err(err) => break Err(EitherExtractError::Bytes(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EitherExtractProj::Left { left, fallback } => {
|
||||||
|
let res = ready!(left.poll(cx));
|
||||||
|
match res {
|
||||||
|
Ok(extracted) => break Ok(Either::Left(extracted)),
|
||||||
|
Err(left_err) => {
|
||||||
|
let right = R::from_request(
|
||||||
|
&this.req,
|
||||||
|
&mut payload_from_bytes(mem::take(fallback)),
|
||||||
|
);
|
||||||
|
EitherExtractState::Right {
|
||||||
|
left_err: Some(left_err),
|
||||||
|
right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EitherExtractProj::Right { right, left_err } => {
|
||||||
|
let res = ready!(right.poll(cx));
|
||||||
|
match res {
|
||||||
|
Ok(data) => break Ok(Either::Right(data)),
|
||||||
|
Err(err) => {
|
||||||
|
break Err(EitherExtractError::Extract(
|
||||||
|
left_err.take().unwrap(),
|
||||||
|
err,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.state.set(next);
|
||||||
|
};
|
||||||
|
Poll::Ready(ready)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ use std::{
|
||||||
use actix_http::Payload;
|
use actix_http::Payload;
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use encoding_rs::{Encoding, UTF_8};
|
use encoding_rs::{Encoding, UTF_8};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::{future::LocalBoxFuture, ready};
|
||||||
use futures_util::{FutureExt as _, StreamExt as _};
|
use futures_util::{FutureExt as _, StreamExt as _};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
|
@ -123,11 +123,10 @@ where
|
||||||
{
|
{
|
||||||
type Config = FormConfig;
|
type Config = FormConfig;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = LocalBoxFuture<'static, Result<Self, Error>>;
|
type Future = FormExtractFut<T>;
|
||||||
|
|
||||||
#[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 (limit, err_handler) = req
|
let (limit, err_handler) = req
|
||||||
.app_data::<Self::Config>()
|
.app_data::<Self::Config>()
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
|
@ -137,16 +136,42 @@ where
|
||||||
.map(|c| (c.limit, c.err_handler.clone()))
|
.map(|c| (c.limit, c.err_handler.clone()))
|
||||||
.unwrap_or((16384, None));
|
.unwrap_or((16384, None));
|
||||||
|
|
||||||
UrlEncoded::new(req, payload)
|
FormExtractFut {
|
||||||
.limit(limit)
|
fut: UrlEncoded::new(req, payload).limit(limit),
|
||||||
.map(move |res| match res {
|
req: req.clone(),
|
||||||
Err(err) => match err_handler {
|
err_handler,
|
||||||
Some(err_handler) => Err((err_handler)(err, &req2)),
|
}
|
||||||
None => Err(err.into()),
|
}
|
||||||
},
|
}
|
||||||
Ok(item) => Ok(Form(item)),
|
|
||||||
})
|
type FormErrHandler = Option<Rc<dyn Fn(UrlencodedError, &HttpRequest) -> Error>>;
|
||||||
.boxed_local()
|
|
||||||
|
pub struct FormExtractFut<T> {
|
||||||
|
fut: UrlEncoded<T>,
|
||||||
|
err_handler: FormErrHandler,
|
||||||
|
req: HttpRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Future for FormExtractFut<T>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned + 'static,
|
||||||
|
{
|
||||||
|
type Output = Result<Form<T>, Error>;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = self.get_mut();
|
||||||
|
|
||||||
|
let res = ready!(Pin::new(&mut this.fut).poll(cx));
|
||||||
|
|
||||||
|
let res = match res {
|
||||||
|
Err(err) => match &this.err_handler {
|
||||||
|
Some(err_handler) => Err((err_handler)(err, &this.req)),
|
||||||
|
None => Err(err.into()),
|
||||||
|
},
|
||||||
|
Ok(item) => Ok(Form(item)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Poll::Ready(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +218,7 @@ impl<T: Serialize> Responder for Form<T> {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FormConfig {
|
pub struct FormConfig {
|
||||||
limit: usize,
|
limit: usize,
|
||||||
err_handler: Option<Rc<dyn Fn(UrlencodedError, &HttpRequest) -> Error>>,
|
err_handler: FormErrHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormConfig {
|
impl FormConfig {
|
||||||
|
|
|
@ -370,8 +370,8 @@ where
|
||||||
} => {
|
} => {
|
||||||
if let Some(len) = length {
|
if let Some(len) = length {
|
||||||
if len > limit {
|
if len > limit {
|
||||||
return JsonBody::Error(Some(JsonPayloadError::Overflow {
|
return JsonBody::Error(Some(JsonPayloadError::OverflowKnownLength {
|
||||||
size: len,
|
length: len,
|
||||||
limit,
|
limit,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -404,7 +404,6 @@ where
|
||||||
limit,
|
limit,
|
||||||
buf,
|
buf,
|
||||||
payload,
|
payload,
|
||||||
length,
|
|
||||||
..
|
..
|
||||||
} => loop {
|
} => loop {
|
||||||
let res = ready!(Pin::new(&mut *payload).poll_next(cx));
|
let res = ready!(Pin::new(&mut *payload).poll_next(cx));
|
||||||
|
@ -414,7 +413,6 @@ where
|
||||||
let buf_len = buf.len() + chunk.len();
|
let buf_len = buf.len() + chunk.len();
|
||||||
if buf_len > *limit {
|
if buf_len > *limit {
|
||||||
return Poll::Ready(Err(JsonPayloadError::Overflow {
|
return Poll::Ready(Err(JsonPayloadError::Overflow {
|
||||||
size: length.unwrap_or(buf_len),
|
|
||||||
limit: *limit,
|
limit: *limit,
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
|
@ -458,6 +456,9 @@ mod tests {
|
||||||
JsonPayloadError::Overflow { .. } => {
|
JsonPayloadError::Overflow { .. } => {
|
||||||
matches!(other, JsonPayloadError::Overflow { .. })
|
matches!(other, JsonPayloadError::Overflow { .. })
|
||||||
}
|
}
|
||||||
|
JsonPayloadError::OverflowKnownLength { .. } => {
|
||||||
|
matches!(other, JsonPayloadError::OverflowKnownLength { .. })
|
||||||
|
}
|
||||||
JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType),
|
JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
@ -603,12 +604,29 @@ mod tests {
|
||||||
.await;
|
.await;
|
||||||
assert!(json_eq(
|
assert!(json_eq(
|
||||||
json.err().unwrap(),
|
json.err().unwrap(),
|
||||||
JsonPayloadError::Overflow {
|
JsonPayloadError::OverflowKnownLength {
|
||||||
size: 10000,
|
length: 10000,
|
||||||
limit: 100
|
limit: 100
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let (req, mut pl) = TestRequest::default()
|
||||||
|
.insert_header((
|
||||||
|
header::CONTENT_TYPE,
|
||||||
|
header::HeaderValue::from_static("application/json"),
|
||||||
|
))
|
||||||
|
.set_payload(Bytes::from_static(&[0u8; 1000]))
|
||||||
|
.to_http_parts();
|
||||||
|
|
||||||
|
let json = JsonBody::<MyObject>::new(&req, &mut pl, None)
|
||||||
|
.limit(100)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(json_eq(
|
||||||
|
json.err().unwrap(),
|
||||||
|
JsonPayloadError::Overflow { limit: 100 }
|
||||||
|
));
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::default()
|
let (req, mut pl) = TestRequest::default()
|
||||||
.insert_header((
|
.insert_header((
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
|
|
Loading…
Reference in New Issue