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:
parent
01329af1c2
commit
96477d42cb
@ -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"]
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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"]);
|
||||||
|
@ -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};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user