mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-28 01:32:57 +01:00
extend HttpMessage trait, add api to work with requests cookies
This commit is contained in:
parent
01329af1c2
commit
96477d42cb
@ -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"]
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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"]);
|
||||
|
@ -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};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user