mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-28 01:32:57 +01:00
Merge branch 'master' of github.com:actix/actix-web
This commit is contained in:
commit
1a91854270
@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
* Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests.
|
||||||
|
|
||||||
|
* Add methods to `HttpResponse` to retrieve, add, and delete cookies
|
||||||
|
|
||||||
* Add `.set_content_type()` and `.set_content_disposition()` methods
|
* Add `.set_content_type()` and `.set_content_disposition()` methods
|
||||||
to `fs::NamedFile` to allow overriding the values inferred by default
|
to `fs::NamedFile` to allow overriding the values inferred by default
|
||||||
|
|
||||||
@ -22,6 +26,9 @@
|
|||||||
|
|
||||||
* Min rustc version is 1.26
|
* Min rustc version is 1.26
|
||||||
|
|
||||||
|
* `HttpResponse::into_builder()` now moves cookies into the builder
|
||||||
|
instead of dropping them
|
||||||
|
|
||||||
* Use tokio instead of tokio-core
|
* Use tokio instead of tokio-core
|
||||||
|
|
||||||
* Use `&mut self` instead of `&self` for Middleware trait
|
* Use `&mut self` instead of `&self` for Middleware trait
|
||||||
|
@ -10,6 +10,7 @@ use futures::Stream;
|
|||||||
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
|
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
use serde_urlencoded;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use super::body::ClientBody;
|
use super::body::ClientBody;
|
||||||
@ -658,6 +659,24 @@ impl ClientRequestBuilder {
|
|||||||
|
|
||||||
self.body(body)
|
self.body(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a urlencoded body and generate `ClientRequest`
|
||||||
|
///
|
||||||
|
/// `ClientRequestBuilder` can not be used after this call.
|
||||||
|
pub fn form<T: Serialize>(&mut self, value: T) -> Result<ClientRequest, Error> {
|
||||||
|
let body = serde_urlencoded::to_string(&value)?;
|
||||||
|
|
||||||
|
let contains = if let Some(parts) = parts(&mut self.request, &self.err) {
|
||||||
|
parts.headers.contains_key(header::CONTENT_TYPE)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
if !contains {
|
||||||
|
self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.body(body)
|
||||||
|
}
|
||||||
|
|
||||||
/// Set a streaming body and generate `ClientRequest`.
|
/// Set a streaming body and generate `ClientRequest`.
|
||||||
///
|
///
|
||||||
|
@ -16,6 +16,7 @@ use http_range::HttpRangeParseError;
|
|||||||
use httparse;
|
use httparse;
|
||||||
use serde::de::value::Error as DeError;
|
use serde::de::value::Error as DeError;
|
||||||
use serde_json::error::Error as JsonError;
|
use serde_json::error::Error as JsonError;
|
||||||
|
use serde_urlencoded::ser::Error as FormError;
|
||||||
use tokio_timer::Error as TimerError;
|
use tokio_timer::Error as TimerError;
|
||||||
pub use url::ParseError as UrlParseError;
|
pub use url::ParseError as UrlParseError;
|
||||||
|
|
||||||
@ -205,6 +206,9 @@ impl From<failure::Error> for Error {
|
|||||||
/// `InternalServerError` for `JsonError`
|
/// `InternalServerError` for `JsonError`
|
||||||
impl ResponseError for JsonError {}
|
impl ResponseError for JsonError {}
|
||||||
|
|
||||||
|
/// `InternalServerError` for `FormError`
|
||||||
|
impl ResponseError for FormError {}
|
||||||
|
|
||||||
/// `InternalServerError` for `TimerError`
|
/// `InternalServerError` for `TimerError`
|
||||||
impl ResponseError for TimerError {}
|
impl ResponseError for TimerError {}
|
||||||
|
|
||||||
|
@ -276,7 +276,7 @@ impl Responder for NamedFile {
|
|||||||
if self.status_code != StatusCode::OK {
|
if self.status_code != StatusCode::OK {
|
||||||
let mut resp = HttpResponse::build(self.status_code);
|
let mut resp = HttpResponse::build(self.status_code);
|
||||||
resp.set(header::ContentType(self.content_type.clone()))
|
resp.set(header::ContentType(self.content_type.clone()))
|
||||||
.header("Content-Disposition", format!("{}", &self.content_disposition));
|
.header(header::CONTENT_DISPOSITION, self.content_disposition.to_string());
|
||||||
|
|
||||||
if let Some(current_encoding) = self.encoding {
|
if let Some(current_encoding) = self.encoding {
|
||||||
resp.content_encoding(current_encoding);
|
resp.content_encoding(current_encoding);
|
||||||
@ -327,7 +327,7 @@ impl Responder for NamedFile {
|
|||||||
|
|
||||||
let mut resp = HttpResponse::build(self.status_code);
|
let mut resp = HttpResponse::build(self.status_code);
|
||||||
resp.set(header::ContentType(self.content_type.clone()))
|
resp.set(header::ContentType(self.content_type.clone()))
|
||||||
.header("Content-Disposition", format!("{}", &self.content_disposition));
|
.header(header::CONTENT_DISPOSITION, self.content_disposition.to_string());
|
||||||
|
|
||||||
if let Some(current_encoding) = self.encoding {
|
if let Some(current_encoding) = self.encoding {
|
||||||
resp.content_encoding(current_encoding);
|
resp.content_encoding(current_encoding);
|
||||||
|
@ -193,8 +193,9 @@ impl fmt::Display for ContentDisposition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if use_simple_format {
|
if use_simple_format {
|
||||||
|
use std::str;
|
||||||
try!(write!(f, "; filename=\"{}\"",
|
try!(write!(f, "; filename=\"{}\"",
|
||||||
match String::from_utf8(bytes.clone()) {
|
match str::from_utf8(bytes) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(_) => return Err(fmt::Error),
|
Err(_) => return Err(fmt::Error),
|
||||||
}));
|
}));
|
||||||
|
@ -97,14 +97,25 @@ impl HttpResponse {
|
|||||||
/// Convert `HttpResponse` to a `HttpResponseBuilder`
|
/// Convert `HttpResponse` to a `HttpResponseBuilder`
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_builder(mut self) -> HttpResponseBuilder {
|
pub fn into_builder(mut self) -> HttpResponseBuilder {
|
||||||
|
// If this response has cookies, load them into a jar
|
||||||
|
let mut jar: Option<CookieJar> = None;
|
||||||
|
for c in self.cookies() {
|
||||||
|
if let Some(ref mut j) = jar {
|
||||||
|
j.add_original(c.into_owned());
|
||||||
|
} else {
|
||||||
|
let mut j = CookieJar::new();
|
||||||
|
j.add_original(c.into_owned());
|
||||||
|
jar = Some(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let response = self.0.take();
|
let response = self.0.take();
|
||||||
let pool = Some(Rc::clone(&self.1));
|
let pool = Some(Rc::clone(&self.1));
|
||||||
|
|
||||||
HttpResponseBuilder {
|
HttpResponseBuilder {
|
||||||
response,
|
response,
|
||||||
pool,
|
pool,
|
||||||
err: None,
|
err: None,
|
||||||
cookies: None, // TODO: convert set-cookie headers
|
cookies: jar,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,6 +143,49 @@ impl HttpResponse {
|
|||||||
&mut self.get_mut().headers
|
&mut self.get_mut().headers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an iterator for the cookies set by this response
|
||||||
|
#[inline]
|
||||||
|
pub fn cookies(&self) -> CookieIter {
|
||||||
|
CookieIter {
|
||||||
|
iter: self.get_ref().headers.get_all(header::SET_COOKIE).iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a cookie to this response
|
||||||
|
#[inline]
|
||||||
|
pub fn add_cookie(&mut self, cookie: Cookie) -> Result<(), HttpError> {
|
||||||
|
let h = &mut self.get_mut().headers;
|
||||||
|
HeaderValue::from_str(&cookie.to_string())
|
||||||
|
.map(|c| { h.append(header::SET_COOKIE, c); })
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove all cookies with the given name from this response. Returns
|
||||||
|
/// the number of cookies removed.
|
||||||
|
#[inline]
|
||||||
|
pub fn del_cookie(&mut self, name: &str) -> usize {
|
||||||
|
let h = &mut self.get_mut().headers;
|
||||||
|
let vals: Vec<HeaderValue> = h.get_all(header::SET_COOKIE)
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.to_owned())
|
||||||
|
.collect();
|
||||||
|
h.remove(header::SET_COOKIE);
|
||||||
|
|
||||||
|
let mut count: usize = 0;
|
||||||
|
for v in vals {
|
||||||
|
if let Ok(s) = v.to_str() {
|
||||||
|
if let Ok(c) = Cookie::parse(s) {
|
||||||
|
if c.name() == name {
|
||||||
|
count += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.append(header::SET_COOKIE, v);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the response status code
|
/// Get the response status code
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn status(&self) -> StatusCode {
|
pub fn status(&self) -> StatusCode {
|
||||||
@ -269,6 +323,24 @@ impl fmt::Debug for HttpResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct CookieIter<'a> {
|
||||||
|
iter: header::ValueIter<'a, HeaderValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for CookieIter<'a> {
|
||||||
|
type Item = Cookie<'a>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<Cookie<'a>> {
|
||||||
|
for v in self.iter.by_ref() {
|
||||||
|
if let Some(c) = (|| Cookie::parse(v.to_str().ok()?).ok())() {
|
||||||
|
return Some(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An HTTP response builder
|
/// An HTTP response builder
|
||||||
///
|
///
|
||||||
/// This type can be used to construct an instance of `HttpResponse` through a
|
/// This type can be used to construct an instance of `HttpResponse` through a
|
||||||
@ -984,6 +1056,27 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_response_cookies() {
|
||||||
|
let mut r = HttpResponse::Ok()
|
||||||
|
.cookie(http::Cookie::new("original", "val100"))
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
r.add_cookie(http::Cookie::new("cookie2", "val200")).unwrap();
|
||||||
|
r.add_cookie(http::Cookie::new("cookie2", "val250")).unwrap();
|
||||||
|
r.add_cookie(http::Cookie::new("cookie3", "val300")).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(r.cookies().count(), 4);
|
||||||
|
r.del_cookie("cookie2");
|
||||||
|
|
||||||
|
let mut iter = r.cookies();
|
||||||
|
let v = iter.next().unwrap();
|
||||||
|
assert_eq!((v.name(), v.value()), ("original", "val100"));
|
||||||
|
let v = iter.next().unwrap();
|
||||||
|
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic_builder() {
|
fn test_basic_builder() {
|
||||||
let resp = HttpResponse::Ok()
|
let resp = HttpResponse::Ok()
|
||||||
@ -1191,11 +1284,16 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_into_builder() {
|
fn test_into_builder() {
|
||||||
let resp: HttpResponse = "test".into();
|
let mut resp: HttpResponse = "test".into();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
resp.add_cookie(http::Cookie::new("cookie1", "val100")).unwrap();
|
||||||
|
|
||||||
let mut builder = resp.into_builder();
|
let mut builder = resp.into_builder();
|
||||||
let resp = builder.status(StatusCode::BAD_REQUEST).finish();
|
let resp = builder.status(StatusCode::BAD_REQUEST).finish();
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
|
|
||||||
|
let cookie = resp.cookies().next().unwrap();
|
||||||
|
assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,35 @@ fn test_async_extractor_async() {
|
|||||||
assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}"));
|
assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
struct FormData {
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_form_extractor() {
|
||||||
|
let mut srv = test::TestServer::new(|app| {
|
||||||
|
app.resource("/{username}/index.html", |r| {
|
||||||
|
r.route().with(|form: Form<FormData>| {
|
||||||
|
format!("{}", form.username)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// client request
|
||||||
|
let request = srv
|
||||||
|
.post()
|
||||||
|
.uri(srv.url("/test1/index.html"))
|
||||||
|
.form(FormData{username: "test".to_string()})
|
||||||
|
.unwrap();
|
||||||
|
let response = srv.execute(request.send()).unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
// read response
|
||||||
|
let bytes = srv.execute(response.body()).unwrap();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(b"test"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_path_and_query_extractor() {
|
fn test_path_and_query_extractor() {
|
||||||
let mut srv = test::TestServer::new(|app| {
|
let mut srv = test::TestServer::new(|app| {
|
||||||
|
Loading…
Reference in New Issue
Block a user