1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-01-18 05:41:50 +01:00

cookie is optional

This commit is contained in:
Nikolay Kim 2019-03-23 09:40:20 -07:00
parent 00b7dc7887
commit c5c7b244be
10 changed files with 151 additions and 94 deletions

View File

@ -31,8 +31,8 @@ before_cache: |
script:
- cargo clean
- cargo build --features="ssl"
- cargo test --features="ssl"
- cargo build --all-features
- cargo test --all-features
# Upload docs
after_success:

View File

@ -7,20 +7,20 @@ readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-http.git"
documentation = "https://actix.rs/api/actix-http/stable/actix_http/"
documentation = "https://docs.rs/actix-http/"
categories = ["network-programming", "asynchronous",
"web-programming::http-server",
"web-programming::websocket"]
license = "Apache-2.0"
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018"
[package.metadata.docs.rs]
features = ["session"]
features = ["ssl", "fail", "cookie"]
[badges]
travis-ci = { repository = "actix/actix-http", branch = "master" }
appveyor = { repository = "fafhrd91/actix-http-b1qsn" }
# appveyor = { repository = "fafhrd91/actix-http-b1qsn" }
codecov = { repository = "actix/actix-http", branch = "master", service = "github" }
[lib]
@ -28,13 +28,15 @@ name = "actix_http"
path = "src/lib.rs"
[features]
default = ["fail"]
default = []
# openssl
ssl = ["openssl", "actix-connect/ssl"]
# failure integration. it is on by default, it will be off in future versions
# actix itself does not use failure anymore
# cookies integration
cookies = ["cookie"]
# failure integration. actix does not use failure anymore
fail = ["failure"]
[dependencies]
@ -49,7 +51,6 @@ backtrace = "0.3"
bitflags = "1.0"
bytes = "0.4"
byteorder = "1.2"
cookie = { version="0.11", features=["percent-encode"] }
derive_more = "0.14"
encoding = "0.2"
futures = "0.1"
@ -76,11 +77,10 @@ tokio-timer = "0.2"
tokio-current-thread = "0.1"
trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false }
# openssl
openssl = { version="0.10", optional = true }
# failure is optional
# optional deps
cookie = { version="0.11", features=["percent-encode"], optional = true }
failure = { version = "0.1.5", optional = true }
openssl = { version="0.10", optional = true }
[dev-dependencies]
actix-rt = "0.2.0"

View File

@ -1,13 +1,12 @@
use std::fmt;
use std::fmt::Write as FmtWrite;
use std::io::Write;
use actix_service::Service;
use bytes::{BufMut, Bytes, BytesMut};
#[cfg(feature = "cookies")]
use cookie::{Cookie, CookieJar};
use futures::future::{err, Either};
use futures::{Future, Stream};
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use serde::Serialize;
use serde_json;
@ -58,6 +57,7 @@ impl ClientRequest<()> {
ClientRequestBuilder {
head: Some(RequestHead::default()),
err: None,
#[cfg(feature = "cookies")]
cookies: None,
default_headers: true,
}
@ -235,6 +235,7 @@ where
pub struct ClientRequestBuilder {
head: Option<RequestHead>,
err: Option<HttpError>,
#[cfg(feature = "cookies")]
cookies: Option<CookieJar>,
default_headers: bool,
}
@ -441,6 +442,7 @@ impl ClientRequestBuilder {
self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze())
}
#[cfg(feature = "cookies")]
/// Set a cookie
///
/// ```rust
@ -560,14 +562,21 @@ impl ClientRequestBuilder {
);
}
#[allow(unused_mut)]
let mut head = self.head.take().expect("cannot reuse request builder");
#[cfg(feature = "cookies")]
{
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use std::fmt::Write;
// set cookies
if let Some(ref mut jar) = self.cookies {
let mut cookie = String::new();
for c in jar.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
let value =
percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
let _ = write!(&mut cookie, "; {}={}", name, value);
}
head.headers.insert(
@ -575,6 +584,7 @@ impl ClientRequestBuilder {
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
}
}
Ok(ClientRequest { head, body })
}
@ -646,6 +656,7 @@ impl ClientRequestBuilder {
ClientRequestBuilder {
head: self.head.take(),
err: self.err.take(),
#[cfg(feature = "cookies")]
cookies: self.cookies.take(),
default_headers: self.default_headers,
}

View File

@ -8,6 +8,7 @@ use std::{fmt, io, result};
// use actix::MailboxError;
use actix_utils::timeout::TimeoutError;
use backtrace::Backtrace;
#[cfg(feature = "cookies")]
use cookie;
use derive_more::{Display, From};
use futures::Canceled;
@ -19,7 +20,8 @@ use serde_json::error::Error as JsonError;
use serde_urlencoded::ser::Error as FormError;
use tokio_timer::Error as TimerError;
// re-exports
// re-export for convinience
#[cfg(feature = "cookies")]
pub use cookie::ParseError as CookieParseError;
use crate::body::Body;
@ -322,6 +324,7 @@ impl ResponseError for PayloadError {
}
/// Return `BadRequest` for `cookie::ParseError`
#[cfg(feature = "cookies")]
impl ResponseError for cookie::ParseError {
fn error_response(&self) -> Response {
Response::new(StatusCode::BAD_REQUEST)
@ -889,7 +892,6 @@ mod failure_integration {
#[cfg(test)]
mod tests {
use super::*;
use cookie::ParseError as CookieParseError;
use http::{Error as HttpError, StatusCode};
use httparse;
use std::error::Error as StdError;
@ -900,14 +902,19 @@ mod tests {
let resp: Response = ParseError::Incomplete.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: Response = CookieParseError::EmptyName.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into();
let resp: Response = err.error_response();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[cfg(feature = "cookies")]
#[test]
fn test_cookie_parse() {
use cookie::ParseError as CookieParseError;
let resp: Response = CookieParseError::EmptyName.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_as_response() {
let orig = io::Error::new(io::ErrorKind::Other, "other");

View File

@ -613,13 +613,12 @@ mod tests {
let mut sys = actix_rt::System::new("test");
let _ = sys.block_on(lazy(|| {
let buf = Buffer::new("GET /test HTTP/1\r\n\r\n");
let readbuf = BytesMut::new();
let mut h1 = Dispatcher::new(
buf,
ServiceConfig::default(),
CloneableService::new(
(|req| ok::<_, Error>(Response::Ok().finish())).into_service(),
(|_| ok::<_, Error>(Response::Ok().finish())).into_service(),
),
);
assert!(h1.poll().is_ok());

View File

@ -1,18 +1,23 @@
use std::cell::{Ref, RefMut};
use std::str;
use cookie::Cookie;
use encoding::all::UTF_8;
use encoding::label::encoding_from_whatwg_label;
use encoding::EncodingRef;
use http::{header, HeaderMap};
use mime::Mime;
use crate::error::{ContentTypeError, CookieParseError, ParseError};
use crate::error::{ContentTypeError, ParseError};
use crate::extensions::Extensions;
use crate::header::Header;
use crate::payload::Payload;
#[cfg(feature = "cookies")]
use crate::error::CookieParseError;
#[cfg(feature = "cookies")]
use cookie::Cookie;
#[cfg(feature = "cookies")]
struct Cookies(Vec<Cookie<'static>>);
/// Trait that implements general purpose operations on http messages
@ -105,6 +110,7 @@ pub trait HttpMessage: Sized {
/// Load request cookies.
#[inline]
#[cfg(feature = "cookies")]
fn cookies(&self) -> Result<Ref<Vec<Cookie<'static>>>, CookieParseError> {
if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new();
@ -125,6 +131,7 @@ pub trait HttpMessage: Sized {
}
/// Return request cookie.
#[cfg(feature = "cookies")]
fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
if let Ok(cookies) = self.cookies() {
for cookie in cookies.iter() {

View File

@ -113,6 +113,7 @@ pub mod http {
#[doc(hidden)]
pub use http::uri::PathAndQuery;
#[cfg(feature = "cookies")]
pub use cookie::{Cookie, CookieBuilder};
/// Various http headers

View File

@ -3,6 +3,7 @@ use std::io::Write;
use std::{fmt, str};
use bytes::{BufMut, Bytes, BytesMut};
#[cfg(feature = "cookies")]
use cookie::{Cookie, CookieJar};
use futures::future::{ok, FutureResult, IntoFuture};
use futures::Stream;
@ -128,6 +129,7 @@ impl<B> Response<B> {
/// Get an iterator for the cookies set by this response
#[inline]
#[cfg(feature = "cookies")]
pub fn cookies(&self) -> CookieIter {
CookieIter {
iter: self.head.headers.get_all(header::SET_COOKIE).iter(),
@ -136,6 +138,7 @@ impl<B> Response<B> {
/// Add a cookie to this response
#[inline]
#[cfg(feature = "cookies")]
pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> {
let h = &mut self.head.headers;
HeaderValue::from_str(&cookie.to_string())
@ -148,6 +151,7 @@ impl<B> Response<B> {
/// Remove all cookies with the given name from this response. Returns
/// the number of cookies removed.
#[inline]
#[cfg(feature = "cookies")]
pub fn del_cookie(&mut self, name: &str) -> usize {
let h = &mut self.head.headers;
let vals: Vec<HeaderValue> = h
@ -267,10 +271,12 @@ impl IntoFuture for Response {
}
}
#[cfg(feature = "cookies")]
pub struct CookieIter<'a> {
iter: header::ValueIter<'a, HeaderValue>,
}
#[cfg(feature = "cookies")]
impl<'a> Iterator for CookieIter<'a> {
type Item = Cookie<'a>;
@ -292,6 +298,7 @@ impl<'a> Iterator for CookieIter<'a> {
pub struct ResponseBuilder {
head: Option<Message<ResponseHead>>,
err: Option<HttpError>,
#[cfg(feature = "cookies")]
cookies: Option<CookieJar>,
}
@ -304,6 +311,7 @@ impl ResponseBuilder {
ResponseBuilder {
head: Some(head),
err: None,
#[cfg(feature = "cookies")]
cookies: None,
}
}
@ -503,6 +511,7 @@ impl ResponseBuilder {
/// .finish()
/// }
/// ```
#[cfg(feature = "cookies")]
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
if self.cookies.is_none() {
let mut jar = CookieJar::new();
@ -530,6 +539,7 @@ impl ResponseBuilder {
/// builder.finish()
/// }
/// ```
#[cfg(feature = "cookies")]
pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self {
{
if self.cookies.is_none() {
@ -582,7 +592,11 @@ impl ResponseBuilder {
return Response::from(Error::from(e)).into_body();
}
#[allow(unused_mut)]
let mut response = self.head.take().expect("cannot reuse response builder");
#[cfg(feature = "cookies")]
{
if let Some(ref jar) = self.cookies {
for cookie in jar.delta() {
match HeaderValue::from_str(&cookie.to_string()) {
@ -593,6 +607,7 @@ impl ResponseBuilder {
};
}
}
}
Response {
head: response,
@ -654,6 +669,7 @@ impl ResponseBuilder {
ResponseBuilder {
head: self.head.take(),
err: self.err.take(),
#[cfg(feature = "cookies")]
cookies: self.cookies.take(),
}
}
@ -674,7 +690,9 @@ fn parts<'a>(
impl<B> From<Response<B>> for ResponseBuilder {
fn from(res: Response<B>) -> ResponseBuilder {
// If this response has cookies, load them into a jar
#[cfg(feature = "cookies")]
let mut jar: Option<CookieJar> = None;
#[cfg(feature = "cookies")]
for c in res.cookies() {
if let Some(ref mut j) = jar {
j.add_original(c.into_owned());
@ -688,6 +706,7 @@ impl<B> From<Response<B>> for ResponseBuilder {
ResponseBuilder {
head: Some(res.head),
err: None,
#[cfg(feature = "cookies")]
cookies: jar,
}
}
@ -697,7 +716,11 @@ impl<B> From<Response<B>> for ResponseBuilder {
impl<'a> From<&'a ResponseHead> for ResponseBuilder {
fn from(head: &'a ResponseHead) -> ResponseBuilder {
// If this response has cookies, load them into a jar
#[cfg(feature = "cookies")]
let mut jar: Option<CookieJar> = None;
#[cfg(feature = "cookies")]
{
let cookies = CookieIter {
iter: head.headers.get_all(header::SET_COOKIE).iter(),
};
@ -710,6 +733,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
jar = Some(j);
}
}
}
let mut msg: Message<ResponseHead> = Message::new();
msg.version = head.version;
@ -721,6 +745,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
ResponseBuilder {
head: Some(msg),
err: None,
#[cfg(feature = "cookies")]
cookies: jar,
}
}
@ -802,14 +827,9 @@ impl From<BytesMut> for Response {
#[cfg(test)]
mod tests {
use time::Duration;
use super::*;
use crate::body::Body;
use crate::http;
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE};
use crate::httpmessage::HttpMessage;
use crate::test::TestRequest;
#[test]
fn test_debug() {
@ -822,8 +842,11 @@ mod tests {
}
#[test]
#[cfg(feature = "cookies")]
fn test_response_cookies() {
let req = TestRequest::default()
use crate::httpmessage::HttpMessage;
let req = crate::test::TestRequest::default()
.header(COOKIE, "cookie1=value1")
.header(COOKIE, "cookie2=value2")
.finish();
@ -831,11 +854,11 @@ mod tests {
let resp = Response::Ok()
.cookie(
http::Cookie::build("name", "value")
crate::http::Cookie::build("name", "value")
.domain("www.rust-lang.org")
.path("/test")
.http_only(true)
.max_age(Duration::days(1))
.max_age(time::Duration::days(1))
.finish(),
)
.del_cookie(&cookies[0])
@ -856,16 +879,17 @@ mod tests {
}
#[test]
#[cfg(feature = "cookies")]
fn test_update_response_cookies() {
let mut r = Response::Ok()
.cookie(http::Cookie::new("original", "val100"))
.cookie(crate::http::Cookie::new("original", "val100"))
.finish();
r.add_cookie(&http::Cookie::new("cookie2", "val200"))
r.add_cookie(&crate::http::Cookie::new("cookie2", "val200"))
.unwrap();
r.add_cookie(&http::Cookie::new("cookie2", "val250"))
r.add_cookie(&crate::http::Cookie::new("cookie2", "val250"))
.unwrap();
r.add_cookie(&http::Cookie::new("cookie3", "val300"))
r.add_cookie(&crate::http::Cookie::new("cookie3", "val300"))
.unwrap();
assert_eq!(r.cookies().count(), 4);
@ -1016,11 +1040,14 @@ mod tests {
}
#[test]
#[cfg(feature = "cookies")]
fn test_into_builder() {
use crate::httpmessage::HttpMessage;
let mut resp: Response = "test".into();
assert_eq!(resp.status(), StatusCode::OK);
resp.add_cookie(&http::Cookie::new("cookie1", "val100"))
resp.add_cookie(&crate::http::Cookie::new("cookie1", "val100"))
.unwrap();
let mut builder: ResponseBuilder = resp.into();

View File

@ -1,12 +1,11 @@
//! Test Various helpers for Actix applications to use during testing.
use std::fmt::Write as FmtWrite;
use std::str::FromStr;
use bytes::Bytes;
#[cfg(feature = "cookies")]
use cookie::{Cookie, CookieJar};
use http::header::{self, HeaderName, HeaderValue};
use http::header::HeaderName;
use http::{HeaderMap, HttpTryFrom, Method, Uri, Version};
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use crate::header::{Header, IntoHeaderValue};
use crate::payload::Payload;
@ -46,6 +45,7 @@ struct Inner {
method: Method,
uri: Uri,
headers: HeaderMap,
#[cfg(feature = "cookies")]
cookies: CookieJar,
payload: Option<Payload>,
}
@ -57,6 +57,7 @@ impl Default for TestRequest {
uri: Uri::from_str("/").unwrap(),
version: Version::HTTP_11,
headers: HeaderMap::new(),
#[cfg(feature = "cookies")]
cookies: CookieJar::new(),
payload: None,
}))
@ -126,6 +127,7 @@ impl TestRequest {
}
/// Set cookie for this request
#[cfg(feature = "cookies")]
pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self {
parts(&mut self.0).cookies.add(cookie.into_owned());
self
@ -145,29 +147,29 @@ impl TestRequest {
/// Complete request creation and generate `Request` instance
pub fn finish(&mut self) -> Request {
let Inner {
method,
uri,
version,
headers,
payload,
cookies,
} = self.0.take().expect("cannot reuse test request builder");;
let inner = self.0.take().expect("cannot reuse test request builder");;
let mut req = if let Some(pl) = payload {
let mut req = if let Some(pl) = inner.payload {
Request::with_payload(pl)
} else {
Request::with_payload(crate::h1::Payload::empty().into())
};
let head = req.head_mut();
head.uri = uri;
head.method = method;
head.version = version;
head.headers = headers;
head.uri = inner.uri;
head.method = inner.method;
head.version = inner.version;
head.headers = inner.headers;
#[cfg(feature = "cookies")]
{
use std::fmt::Write as FmtWrite;
use http::header::{self, HeaderValue};
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
let mut cookie = String::new();
for c in cookies.delta() {
for c in inner.cookies.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
let _ = write!(&mut cookie, "; {}={}", name, value);
@ -178,6 +180,7 @@ impl TestRequest {
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
}
}
req
}

View File

@ -1,6 +1,7 @@
//! Http client request
use std::str;
#[cfg(feature = "cookies")]
use cookie::Cookie;
use http::header::{HeaderName, HeaderValue};
use http::{Error as HttpError, HttpTryFrom};
@ -50,6 +51,7 @@ impl Connect {
self
}
#[cfg(feature = "cookies")]
/// Set cookie for handshake request
pub fn cookie(mut self, cookie: Cookie) -> Self {
self.request.cookie(cookie);