mirror of
https://github.com/actix/actix-web.git
synced 2024-11-26 03:21:08 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
d3b12d885e
5 changed files with 206 additions and 9 deletions
|
@ -2,11 +2,16 @@
|
|||
|
||||
## [0.7.1] - 2018-07-21
|
||||
|
||||
### Added
|
||||
|
||||
* Add implementation of `FromRequest<S>` for `Option<T>` and `Result<T, Error>`
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed default_resource 'not yet implemented' panic #410
|
||||
* removed the timestamp from the default logger middleware
|
||||
|
||||
* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies
|
||||
|
||||
## [0.7.0] - 2018-07-21
|
||||
|
||||
|
|
|
@ -12,12 +12,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
|||
* Multipart streams
|
||||
* Static assets
|
||||
* SSL support with OpenSSL or `native-tls`
|
||||
* Middlewares ([Logger](https://actix.rs/book/actix-web/sec-9-middlewares.html#logging),
|
||||
[Session](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions),
|
||||
[Redis sessions](https://github.com/actix/actix-redis),
|
||||
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
|
||||
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
|
||||
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
|
||||
* Middlewares ([Logger,Session,CORS,CSRF,etc](https://actix.rs/docs/middleware/))
|
||||
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
|
||||
* Built on top of [Actix actor framework](https://github.com/actix/actix)
|
||||
|
||||
|
|
|
@ -610,7 +610,6 @@ impl<S: 'static> Iterator for App<S> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use body::{Binary, Body};
|
||||
use fs;
|
||||
use http::StatusCode;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
|
|
192
src/extractor.rs
192
src/extractor.rs
|
@ -6,7 +6,7 @@ use std::{fmt, str};
|
|||
use bytes::Bytes;
|
||||
use encoding::all::UTF_8;
|
||||
use encoding::types::{DecoderTrap, Encoding};
|
||||
use futures::{Async, Future, Poll};
|
||||
use futures::{Async, Future, Poll, future};
|
||||
use mime::Mime;
|
||||
use serde::de::{self, DeserializeOwned};
|
||||
use serde_urlencoded;
|
||||
|
@ -17,6 +17,7 @@ use handler::{AsyncResult, FromRequest};
|
|||
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
|
||||
use httprequest::HttpRequest;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Extract typed information from the request's path.
|
||||
///
|
||||
/// ## Example
|
||||
|
@ -128,6 +129,7 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Extract typed information from from the request's query.
|
||||
///
|
||||
/// ## Example
|
||||
|
@ -215,6 +217,7 @@ impl<T: fmt::Display> fmt::Display for Query<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Extract typed information from the request's body.
|
||||
///
|
||||
/// To extract typed information from request's body, the type `T` must
|
||||
|
@ -455,6 +458,124 @@ impl<S: 'static> FromRequest<S> for String {
|
|||
}
|
||||
}
|
||||
|
||||
/// Optionally extract a field from the request
|
||||
///
|
||||
/// If the FromRequest for T fails, return None rather than returning an error response
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// extern crate rand;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest};
|
||||
/// use actix_web::error::ErrorBadRequest;
|
||||
///
|
||||
/// #[derive(Debug, Deserialize)]
|
||||
/// struct Thing { name: String }
|
||||
///
|
||||
/// impl<S> FromRequest<S> for Thing {
|
||||
/// type Config = ();
|
||||
/// type Result = Result<Thing, Error>;
|
||||
///
|
||||
/// #[inline]
|
||||
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
|
||||
/// if rand::random() {
|
||||
/// Ok(Thing { name: "thingy".into() })
|
||||
/// } else {
|
||||
/// Err(ErrorBadRequest("no luck"))
|
||||
/// }
|
||||
///
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// /// extract text data from request
|
||||
/// fn index(supplied_thing: Option<Thing>) -> Result<String> {
|
||||
/// match supplied_thing {
|
||||
/// // Puns not intended
|
||||
/// Some(thing) => Ok(format!("Got something: {:?}", thing)),
|
||||
/// None => Ok(format!("No thing!"))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource("/users/:first", |r| {
|
||||
/// r.method(http::Method::POST).with(index)
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
impl<T: 'static, S: 'static> FromRequest<S> for Option<T> where T: FromRequest<S> {
|
||||
type Config = T::Config;
|
||||
type Result = Box<Future<Item = Option<T>, Error = Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
|
||||
Box::new(T::from_request(req, cfg).into().then( |r| {
|
||||
match r {
|
||||
Ok(v) => future::ok(Some(v)),
|
||||
Err(e) => {
|
||||
future::ok(None)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Optionally extract a field from the request or extract the Error if unsuccessful
|
||||
///
|
||||
/// If the FromRequest for T fails, inject Err into handler rather than returning an error response
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// extern crate rand;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest};
|
||||
/// use actix_web::error::ErrorBadRequest;
|
||||
///
|
||||
/// #[derive(Debug, Deserialize)]
|
||||
/// struct Thing { name: String }
|
||||
///
|
||||
/// impl<S> FromRequest<S> for Thing {
|
||||
/// type Config = ();
|
||||
/// type Result = Result<Thing, Error>;
|
||||
///
|
||||
/// #[inline]
|
||||
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
|
||||
/// if rand::random() {
|
||||
/// Ok(Thing { name: "thingy".into() })
|
||||
/// } else {
|
||||
/// Err(ErrorBadRequest("no luck"))
|
||||
/// }
|
||||
///
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// /// extract text data from request
|
||||
/// fn index(supplied_thing: Result<Thing>) -> Result<String> {
|
||||
/// match supplied_thing {
|
||||
/// Ok(thing) => Ok(format!("Got thing: {:?}", thing)),
|
||||
/// Err(e) => Ok(format!("Error extracting thing: {}", e))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource("/users/:first", |r| {
|
||||
/// r.method(http::Method::POST).with(index)
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
impl<T: 'static, S: 'static> FromRequest<S> for Result<T, Error> where T: FromRequest<S>{
|
||||
type Config = T::Config;
|
||||
type Result = Box<Future<Item = Result<T, Error>, Error = Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
|
||||
Box::new(T::from_request(req, cfg).into().then( |r| { future::ok(r) }))
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload configuration for request's payload.
|
||||
pub struct PayloadConfig {
|
||||
limit: usize,
|
||||
|
@ -680,6 +801,75 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_option() {
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
).finish();
|
||||
|
||||
let mut cfg = FormConfig::default();
|
||||
cfg.limit(4096);
|
||||
|
||||
match Option::<Form<Info>>::from_request(&req, &cfg).poll().unwrap() {
|
||||
Async::Ready(r) => assert_eq!(r, None),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
).header(header::CONTENT_LENGTH, "9")
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.finish();
|
||||
|
||||
match Option::<Form<Info>>::from_request(&req, &cfg).poll().unwrap() {
|
||||
Async::Ready(r) => assert_eq!(r, Some(Form(Info { hello: "world".into() }))),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
).header(header::CONTENT_LENGTH, "9")
|
||||
.set_payload(Bytes::from_static(b"bye=world"))
|
||||
.finish();
|
||||
|
||||
match Option::<Form<Info>>::from_request(&req, &cfg).poll().unwrap() {
|
||||
Async::Ready(r) => assert_eq!(r, None),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_result() {
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
).header(header::CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.finish();
|
||||
|
||||
match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default()).poll().unwrap() {
|
||||
Async::Ready(Ok(r)) => assert_eq!(r, Form(Info { hello: "world".into() })),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
).header(header::CONTENT_LENGTH, "9")
|
||||
.set_payload(Bytes::from_static(b"bye=world"))
|
||||
.finish();
|
||||
|
||||
match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default()).poll().unwrap() {
|
||||
Async::Ready(r) => assert!(r.is_err()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_payload_config() {
|
||||
let req = TestRequest::default().finish();
|
||||
|
|
|
@ -358,6 +358,7 @@ struct CookieSessionInner {
|
|||
path: String,
|
||||
domain: Option<String>,
|
||||
secure: bool,
|
||||
http_only: bool,
|
||||
max_age: Option<Duration>,
|
||||
same_site: Option<SameSite>,
|
||||
}
|
||||
|
@ -371,6 +372,7 @@ impl CookieSessionInner {
|
|||
path: "/".to_owned(),
|
||||
domain: None,
|
||||
secure: true,
|
||||
http_only: true,
|
||||
max_age: None,
|
||||
same_site: None,
|
||||
}
|
||||
|
@ -388,7 +390,7 @@ impl CookieSessionInner {
|
|||
let mut cookie = Cookie::new(self.name.clone(), value);
|
||||
cookie.set_path(self.path.clone());
|
||||
cookie.set_secure(self.secure);
|
||||
cookie.set_http_only(true);
|
||||
cookie.set_http_only(self.http_only);
|
||||
|
||||
if let Some(ref domain) = self.domain {
|
||||
cookie.set_domain(domain.clone());
|
||||
|
@ -532,6 +534,12 @@ impl CookieSessionBackend {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the `http_only` field in the session cookie being built.
|
||||
pub fn http_only(mut self, value: bool) -> CookieSessionBackend {
|
||||
Rc::get_mut(&mut self.0).unwrap().http_only = value;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `same_site` field in the session cookie being built.
|
||||
pub fn same_site(mut self, value: SameSite) -> CookieSessionBackend {
|
||||
Rc::get_mut(&mut self.0).unwrap().same_site = Some(value);
|
||||
|
|
Loading…
Reference in a new issue