1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-30 18:34:36 +01:00

extend HttpMessage trait, add api to work with requests cookies

This commit is contained in:
Nikolay Kim 2019-03-05 13:16:26 -08:00
parent 01329af1c2
commit 96477d42cb
8 changed files with 140 additions and 82 deletions

View File

@ -28,10 +28,7 @@ name = "actix_http"
path = "src/lib.rs" path = "src/lib.rs"
[features] [features]
default = ["session"] default = []
# sessions feature, session require "ring" crate and c compiler
session = ["cookie/secure"]
# openssl # openssl
ssl = ["openssl", "actix-connector/ssl"] ssl = ["openssl", "actix-connector/ssl"]

View File

@ -1,3 +1,4 @@
use std::cell::{Ref, RefMut};
use std::fmt; use std::fmt;
use bytes::Bytes; use bytes::Bytes;
@ -5,6 +6,7 @@ use futures::{Poll, Stream};
use http::{HeaderMap, StatusCode, Version}; use http::{HeaderMap, StatusCode, Version};
use crate::error::PayloadError; use crate::error::PayloadError;
use crate::extensions::Extensions;
use crate::httpmessage::HttpMessage; use crate::httpmessage::HttpMessage;
use crate::message::{Head, Message, ResponseHead}; use crate::message::{Head, Message, ResponseHead};
use crate::payload::{Payload, PayloadStream}; use crate::payload::{Payload, PayloadStream};
@ -22,6 +24,18 @@ impl HttpMessage for ClientResponse {
&self.head.headers &self.head.headers
} }
fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.head.headers
}
fn extensions(&self) -> Ref<Extensions> {
self.head.extensions()
}
fn extensions_mut(&self) -> RefMut<Extensions> {
self.head.extensions_mut()
}
fn take_payload(&mut self) -> Payload { fn take_payload(&mut self) -> Payload {
std::mem::replace(&mut self.payload, Payload::None) std::mem::replace(&mut self.payload, Payload::None)
} }
@ -30,8 +44,11 @@ impl HttpMessage for ClientResponse {
impl ClientResponse { impl ClientResponse {
/// Create new Request instance /// Create new Request instance
pub fn new() -> ClientResponse { pub fn new() -> ClientResponse {
let head: Message<ResponseHead> = Message::new();
head.extensions_mut().clear();
ClientResponse { ClientResponse {
head: Message::new(), head,
payload: Payload::None, payload: Payload::None,
} }
} }

View File

@ -163,7 +163,7 @@ impl MessageType for Request {
} }
fn headers_mut(&mut self) -> &mut HeaderMap { fn headers_mut(&mut self) -> &mut HeaderMap {
self.headers_mut() &mut self.head_mut().headers
} }
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> { fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> {
@ -832,6 +832,7 @@ mod tests {
"GET /test HTTP/1.0\r\n\ "GET /test HTTP/1.0\r\n\
connection: close\r\n\r\n", connection: close\r\n\r\n",
); );
let req = parse_ready!(&mut buf); let req = parse_ready!(&mut buf);
assert_eq!(req.head().ctype, Some(ConnectionType::Close)); assert_eq!(req.head().ctype, Some(ConnectionType::Close));

View File

@ -1,6 +1,8 @@
use std::cell::{Ref, RefMut};
use std::str; use std::str;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use cookie::Cookie;
use encoding::all::UTF_8; use encoding::all::UTF_8;
use encoding::label::encoding_from_whatwg_label; use encoding::label::encoding_from_whatwg_label;
use encoding::types::{DecoderTrap, Encoding}; use encoding::types::{DecoderTrap, Encoding};
@ -12,12 +14,16 @@ use serde::de::DeserializeOwned;
use serde_urlencoded; use serde_urlencoded;
use crate::error::{ use crate::error::{
ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError, ContentTypeError, CookieParseError, ParseError, PayloadError, ReadlinesError,
UrlencodedError,
}; };
use crate::extensions::Extensions;
use crate::header::Header; use crate::header::Header;
use crate::json::JsonBody; use crate::json::JsonBody;
use crate::payload::Payload; use crate::payload::Payload;
struct Cookies(Vec<Cookie<'static>>);
/// Trait that implements general purpose operations on http messages /// Trait that implements general purpose operations on http messages
pub trait HttpMessage: Sized { pub trait HttpMessage: Sized {
/// Type of message payload stream /// Type of message payload stream
@ -26,9 +32,18 @@ pub trait HttpMessage: Sized {
/// Read the message headers. /// Read the message headers.
fn headers(&self) -> &HeaderMap; fn headers(&self) -> &HeaderMap;
/// Mutable reference to the message's headers.
fn headers_mut(&mut self) -> &mut HeaderMap;
/// Message payload stream /// Message payload stream
fn take_payload(&mut self) -> Payload<Self::Stream>; fn take_payload(&mut self) -> Payload<Self::Stream>;
/// Request's extensions container
fn extensions(&self) -> Ref<Extensions>;
/// Mutable reference to a the request's extensions container
fn extensions_mut(&self) -> RefMut<Extensions>;
#[doc(hidden)] #[doc(hidden)]
/// Get a header /// Get a header
fn get_header<H: Header>(&self) -> Option<H> fn get_header<H: Header>(&self) -> Option<H>
@ -100,6 +115,39 @@ pub trait HttpMessage: Sized {
} }
} }
/// Load request cookies.
#[inline]
fn cookies(&self) -> Result<Ref<Vec<Cookie<'static>>>, CookieParseError> {
if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new();
for hdr in self.headers().get_all(header::COOKIE) {
let s =
str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
for cookie_str in s.split(';').map(|s| s.trim()) {
if !cookie_str.is_empty() {
cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned());
}
}
}
self.extensions_mut().insert(Cookies(cookies));
}
Ok(Ref::map(self.extensions(), |ext| {
&ext.get::<Cookies>().unwrap().0
}))
}
/// Return request cookie.
fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
if let Ok(cookies) = self.cookies() {
for cookie in cookies.iter() {
if cookie.name() == name {
return Some(cookie.to_owned());
}
}
}
None
}
/// Load http message body. /// Load http message body.
/// ///
/// By default only 256Kb payload reads to a memory, then /// By default only 256Kb payload reads to a memory, then

View File

@ -125,6 +125,7 @@ pub struct ResponseHead {
pub reason: Option<&'static str>, pub reason: Option<&'static str>,
pub no_chunking: bool, pub no_chunking: bool,
pub(crate) ctype: Option<ConnectionType>, pub(crate) ctype: Option<ConnectionType>,
pub(crate) extensions: RefCell<Extensions>,
} }
impl Default for ResponseHead { impl Default for ResponseHead {
@ -136,10 +137,25 @@ impl Default for ResponseHead {
reason: None, reason: None,
no_chunking: false, no_chunking: false,
ctype: None, ctype: None,
extensions: RefCell::new(Extensions::new()),
} }
} }
} }
impl ResponseHead {
/// Message extensions
#[inline]
pub fn extensions(&self) -> Ref<Extensions> {
self.extensions.borrow()
}
/// Mutable reference to a the message's extensions
#[inline]
pub fn extensions_mut(&self) -> RefMut<Extensions> {
self.extensions.borrow_mut()
}
}
impl Head for ResponseHead { impl Head for ResponseHead {
fn clear(&mut self) { fn clear(&mut self) {
self.ctype = None; self.ctype = None;

View File

@ -17,10 +17,28 @@ pub struct Request<P = PayloadStream> {
impl<P> HttpMessage for Request<P> { impl<P> HttpMessage for Request<P> {
type Stream = P; type Stream = P;
#[inline]
fn headers(&self) -> &HeaderMap { fn headers(&self) -> &HeaderMap {
&self.head().headers &self.head().headers
} }
#[inline]
fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.head_mut().headers
}
/// Request extensions
#[inline]
fn extensions(&self) -> Ref<Extensions> {
self.head.extensions()
}
/// Mutable reference to a the request's extensions
#[inline]
fn extensions_mut(&self) -> RefMut<Extensions> {
self.head.extensions_mut()
}
fn take_payload(&mut self) -> Payload<P> { fn take_payload(&mut self) -> Payload<P> {
std::mem::replace(&mut self.payload, Payload::None) std::mem::replace(&mut self.payload, Payload::None)
} }
@ -119,30 +137,6 @@ impl<P> Request<P> {
self.head().uri.path() self.head().uri.path()
} }
#[inline]
/// Returns Request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.head().headers
}
#[inline]
/// Returns mutable Request's headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.head_mut().headers
}
/// Request extensions
#[inline]
pub fn extensions(&self) -> Ref<Extensions> {
self.head.extensions()
}
/// Mutable reference to a the request's extensions
#[inline]
pub fn extensions_mut(&self) -> RefMut<Extensions> {
self.head.extensions_mut()
}
/// Check if request requires connection upgrade /// Check if request requires connection upgrade
pub fn upgrade(&self) -> bool { pub fn upgrade(&self) -> bool {
if let Some(conn) = self.head().headers.get(header::CONNECTION) { if let Some(conn) = self.head().headers.get(header::CONNECTION) {

View File

@ -766,12 +766,14 @@ impl From<BytesMut> for Response {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use time::Duration;
use super::*; use super::*;
use crate::body::Body; use crate::body::Body;
use crate::http; use crate::http;
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE};
use crate::httpmessage::HttpMessage;
// use test::TestRequest; use crate::test::TestRequest;
#[test] #[test]
fn test_debug() { fn test_debug() {
@ -783,38 +785,39 @@ mod tests {
assert!(dbg.contains("Response")); assert!(dbg.contains("Response"));
} }
// #[test] #[test]
// fn test_response_cookies() { fn test_response_cookies() {
// let req = TestRequest::default() let req = TestRequest::default()
// .header(COOKIE, "cookie1=value1") .header(COOKIE, "cookie1=value1")
// .header(COOKIE, "cookie2=value2") .header(COOKIE, "cookie2=value2")
// .finish(); .finish();
// let cookies = req.cookies().unwrap(); let cookies = req.cookies().unwrap();
// let resp = Response::Ok() let resp = Response::Ok()
// .cookie( .cookie(
// http::Cookie::build("name", "value") http::Cookie::build("name", "value")
// .domain("www.rust-lang.org") .domain("www.rust-lang.org")
// .path("/test") .path("/test")
// .http_only(true) .http_only(true)
// .max_age(Duration::days(1)) .max_age(Duration::days(1))
// .finish(), .finish(),
// ).del_cookie(&cookies[0]) )
// .finish(); .del_cookie(&cookies[0])
.finish();
// let mut val: Vec<_> = resp let mut val: Vec<_> = resp
// .headers() .headers()
// .get_all("Set-Cookie") .get_all("Set-Cookie")
// .iter() .iter()
// .map(|v| v.to_str().unwrap().to_owned()) .map(|v| v.to_str().unwrap().to_owned())
// .collect(); .collect();
// val.sort(); val.sort();
// assert!(val[0].starts_with("cookie1=; Max-Age=0;")); assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
// assert_eq!( assert_eq!(
// val[1], val[1],
// "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"
// ); );
// } }
#[test] #[test]
fn test_update_response_cookies() { fn test_update_response_cookies() {
@ -871,25 +874,6 @@ mod tests {
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
} }
// #[test]
// fn test_content_encoding() {
// let resp = Response::build(StatusCode::OK).finish();
// assert_eq!(resp.content_encoding(), None);
// #[cfg(feature = "brotli")]
// {
// let resp = Response::build(StatusCode::OK)
// .content_encoding(ContentEncoding::Br)
// .finish();
// assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br));
// }
// let resp = Response::build(StatusCode::OK)
// .content_encoding(ContentEncoding::Gzip)
// .finish();
// assert_eq!(resp.content_encoding(), Some(ContentEncoding::Gzip));
// }
#[test] #[test]
fn test_json() { fn test_json() {
let resp = Response::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); let resp = Response::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]);

View File

@ -9,6 +9,7 @@ use derive_more::{Display, From};
use http::{header, Method, StatusCode}; use http::{header, Method, StatusCode};
use crate::error::ResponseError; use crate::error::ResponseError;
use crate::httpmessage::HttpMessage;
use crate::request::Request; use crate::request::Request;
use crate::response::{Response, ResponseBuilder}; use crate::response::{Response, ResponseBuilder};