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"
[features]
default = ["session"]
# sessions feature, session require "ring" crate and c compiler
session = ["cookie/secure"]
default = []
# openssl
ssl = ["openssl", "actix-connector/ssl"]

View File

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

View File

@ -163,7 +163,7 @@ impl MessageType for Request {
}
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> {
@ -832,6 +832,7 @@ mod tests {
"GET /test HTTP/1.0\r\n\
connection: close\r\n\r\n",
);
let req = parse_ready!(&mut buf);
assert_eq!(req.head().ctype, Some(ConnectionType::Close));

View File

@ -1,6 +1,8 @@
use std::cell::{Ref, RefMut};
use std::str;
use bytes::{Bytes, BytesMut};
use cookie::Cookie;
use encoding::all::UTF_8;
use encoding::label::encoding_from_whatwg_label;
use encoding::types::{DecoderTrap, Encoding};
@ -12,12 +14,16 @@ use serde::de::DeserializeOwned;
use serde_urlencoded;
use crate::error::{
ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError,
ContentTypeError, CookieParseError, ParseError, PayloadError, ReadlinesError,
UrlencodedError,
};
use crate::extensions::Extensions;
use crate::header::Header;
use crate::json::JsonBody;
use crate::payload::Payload;
struct Cookies(Vec<Cookie<'static>>);
/// Trait that implements general purpose operations on http messages
pub trait HttpMessage: Sized {
/// Type of message payload stream
@ -26,9 +32,18 @@ pub trait HttpMessage: Sized {
/// Read the message headers.
fn headers(&self) -> &HeaderMap;
/// Mutable reference to the message's headers.
fn headers_mut(&mut self) -> &mut HeaderMap;
/// Message payload 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)]
/// Get a header
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.
///
/// 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 no_chunking: bool,
pub(crate) ctype: Option<ConnectionType>,
pub(crate) extensions: RefCell<Extensions>,
}
impl Default for ResponseHead {
@ -136,10 +137,25 @@ impl Default for ResponseHead {
reason: None,
no_chunking: false,
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 {
fn clear(&mut self) {
self.ctype = None;

View File

@ -17,10 +17,28 @@ pub struct Request<P = PayloadStream> {
impl<P> HttpMessage for Request<P> {
type Stream = P;
#[inline]
fn headers(&self) -> &HeaderMap {
&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> {
std::mem::replace(&mut self.payload, Payload::None)
}
@ -119,30 +137,6 @@ impl<P> Request<P> {
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
pub fn upgrade(&self) -> bool {
if let Some(conn) = self.head().headers.get(header::CONNECTION) {

View File

@ -766,12 +766,14 @@ 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 test::TestRequest;
use crate::httpmessage::HttpMessage;
use crate::test::TestRequest;
#[test]
fn test_debug() {
@ -783,38 +785,39 @@ mod tests {
assert!(dbg.contains("Response"));
}
// #[test]
// fn test_response_cookies() {
// let req = TestRequest::default()
// .header(COOKIE, "cookie1=value1")
// .header(COOKIE, "cookie2=value2")
// .finish();
// let cookies = req.cookies().unwrap();
#[test]
fn test_response_cookies() {
let req = TestRequest::default()
.header(COOKIE, "cookie1=value1")
.header(COOKIE, "cookie2=value2")
.finish();
let cookies = req.cookies().unwrap();
// let resp = Response::Ok()
// .cookie(
// http::Cookie::build("name", "value")
// .domain("www.rust-lang.org")
// .path("/test")
// .http_only(true)
// .max_age(Duration::days(1))
// .finish(),
// ).del_cookie(&cookies[0])
// .finish();
let resp = Response::Ok()
.cookie(
http::Cookie::build("name", "value")
.domain("www.rust-lang.org")
.path("/test")
.http_only(true)
.max_age(Duration::days(1))
.finish(),
)
.del_cookie(&cookies[0])
.finish();
// let mut val: Vec<_> = resp
// .headers()
// .get_all("Set-Cookie")
// .iter()
// .map(|v| v.to_str().unwrap().to_owned())
// .collect();
// val.sort();
// assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
// assert_eq!(
// val[1],
// "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"
// );
// }
let mut val: Vec<_> = resp
.headers()
.get_all("Set-Cookie")
.iter()
.map(|v| v.to_str().unwrap().to_owned())
.collect();
val.sort();
assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
assert_eq!(
val[1],
"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"
);
}
#[test]
fn test_update_response_cookies() {
@ -871,25 +874,6 @@ mod tests {
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]
fn test_json() {
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 crate::error::ResponseError;
use crate::httpmessage::HttpMessage;
use crate::request::Request;
use crate::response::{Response, ResponseBuilder};