mirror of
https://github.com/fafhrd91/actix-web
synced 2025-08-14 10:10:37 +02:00
Compare commits
7 Commits
web-v4.0.0
...
get-all-or
Author | SHA1 | Date | |
---|---|---|---|
|
e2a803d278 | ||
|
67d2845882 | ||
|
89c6d62656 | ||
|
52bbbd1d73 | ||
|
3e6e9779dc | ||
|
9bdd334bb4 | ||
|
bcbbc115aa |
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -159,7 +159,7 @@ jobs:
|
|||||||
with: { file: cobertura.xml }
|
with: { file: cobertura.xml }
|
||||||
|
|
||||||
rustdoc:
|
rustdoc:
|
||||||
name: rustdoc
|
name: doc tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -177,12 +177,6 @@ jobs:
|
|||||||
- name: Cache Dependencies
|
- name: Cache Dependencies
|
||||||
uses: Swatinem/rust-cache@v1.3.0
|
uses: Swatinem/rust-cache@v1.3.0
|
||||||
|
|
||||||
# - name: Install cargo-hack
|
|
||||||
# uses: actions-rs/cargo@v1
|
|
||||||
# with:
|
|
||||||
# command: install
|
|
||||||
# args: cargo-hack
|
|
||||||
|
|
||||||
- name: doc tests
|
- name: doc tests
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
### Changed
|
||||||
|
* Guarantee ordering of `header::GetAll` iterator to be same as insertion order. [#2467]
|
||||||
|
* Expose `header::{GetAll, Removed}` iterators. [#2467]
|
||||||
|
|
||||||
|
[#2467]: https://github.com/actix/actix-web/pull/2467
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.13 - 2021-11-22
|
## 3.0.0-beta.13 - 2021-11-22
|
||||||
|
@@ -288,7 +288,7 @@ impl HeaderMap {
|
|||||||
/// Returns an iterator over all values associated with a header name.
|
/// Returns an iterator over all values associated with a header name.
|
||||||
///
|
///
|
||||||
/// The returned iterator does not incur any allocations and will yield no items if there are no
|
/// The returned iterator does not incur any allocations and will yield no items if there are no
|
||||||
/// values associated with the key. Iteration order is **not** guaranteed to be the same as
|
/// values associated with the key. Iteration order is guaranteed to be the same as
|
||||||
/// insertion order.
|
/// insertion order.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
@@ -355,6 +355,19 @@ impl HeaderMap {
|
|||||||
///
|
///
|
||||||
/// assert_eq!(map.len(), 1);
|
/// assert_eq!(map.len(), 1);
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// A convenience method is provided on the returned iterator to check if the insertion replaced
|
||||||
|
/// any values.
|
||||||
|
/// ```
|
||||||
|
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||||
|
/// let mut map = HeaderMap::new();
|
||||||
|
///
|
||||||
|
/// let removed = map.insert(header::ACCEPT, HeaderValue::from_static("text/plain"));
|
||||||
|
/// assert!(removed.is_empty());
|
||||||
|
///
|
||||||
|
/// let removed = map.insert(header::ACCEPT, HeaderValue::from_static("text/html"));
|
||||||
|
/// assert!(!removed.is_empty());
|
||||||
|
/// ```
|
||||||
pub fn insert(&mut self, key: HeaderName, val: HeaderValue) -> Removed {
|
pub fn insert(&mut self, key: HeaderName, val: HeaderValue) -> Removed {
|
||||||
let value = self.inner.insert(key, Value::one(val));
|
let value = self.inner.insert(key, Value::one(val));
|
||||||
Removed::new(value)
|
Removed::new(value)
|
||||||
@@ -393,6 +406,9 @@ impl HeaderMap {
|
|||||||
|
|
||||||
/// Removes all headers for a particular header name from the map.
|
/// Removes all headers for a particular header name from the map.
|
||||||
///
|
///
|
||||||
|
/// Providing an invalid header names (as a string argument) will have no effect and return
|
||||||
|
/// without error.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||||
@@ -409,6 +425,21 @@ impl HeaderMap {
|
|||||||
/// assert!(removed.next().is_none());
|
/// assert!(removed.next().is_none());
|
||||||
///
|
///
|
||||||
/// assert!(map.is_empty());
|
/// assert!(map.is_empty());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// A convenience method is provided on the returned iterator to check if the `remove` call
|
||||||
|
/// actually removed any values.
|
||||||
|
/// ```
|
||||||
|
/// # use actix_http::http::{header, HeaderMap, HeaderValue};
|
||||||
|
/// let mut map = HeaderMap::new();
|
||||||
|
///
|
||||||
|
/// let removed = map.remove("accept");
|
||||||
|
/// assert!(removed.is_empty());
|
||||||
|
///
|
||||||
|
/// map.insert(header::ACCEPT, HeaderValue::from_static("text/html"));
|
||||||
|
/// let removed = map.remove("accept");
|
||||||
|
/// assert!(!removed.is_empty());
|
||||||
|
/// ```
|
||||||
pub fn remove(&mut self, key: impl AsHeaderName) -> Removed {
|
pub fn remove(&mut self, key: impl AsHeaderName) -> Removed {
|
||||||
let value = match key.try_as_name(super::as_name::Seal) {
|
let value = match key.try_as_name(super::as_name::Seal) {
|
||||||
Ok(Cow::Borrowed(name)) => self.inner.remove(name),
|
Ok(Cow::Borrowed(name)) => self.inner.remove(name),
|
||||||
@@ -571,7 +602,7 @@ impl<'a> IntoIterator for &'a HeaderMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterator for all values with the same header name.
|
/// Iterator for references of [`HeaderValue`]s with the same associated [`HeaderName`].
|
||||||
///
|
///
|
||||||
/// See [`HeaderMap::get_all`].
|
/// See [`HeaderMap::get_all`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -613,18 +644,32 @@ impl<'a> Iterator for GetAll<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterator for owned [`HeaderValue`]s with the same associated [`HeaderName`] returned from methods
|
/// Iterator for owned [`HeaderValue`]s with the same associated [`HeaderName`] returned from
|
||||||
/// on [`HeaderMap`] that remove or replace items.
|
/// methods that remove or replace items.
|
||||||
|
///
|
||||||
|
/// See [`HeaderMap::insert`] and [`HeaderMap::remove`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Removed {
|
pub struct Removed {
|
||||||
inner: Option<smallvec::IntoIter<[HeaderValue; 4]>>,
|
inner: Option<smallvec::IntoIter<[HeaderValue; 4]>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Removed {
|
impl Removed {
|
||||||
fn new(value: Option<Value>) -> Self {
|
fn new(value: Option<Value>) -> Self {
|
||||||
let inner = value.map(|value| value.inner.into_iter());
|
let inner = value.map(|value| value.inner.into_iter());
|
||||||
Self { inner }
|
Self { inner }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if iterator contains no elements, without consuming it.
|
||||||
|
///
|
||||||
|
/// If called immediately after [`HeaderMap::insert`] or [`HeaderMap::remove`], it will indicate
|
||||||
|
/// wether any items were actually replaced or removed, respectively.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
match self.inner {
|
||||||
|
// size hint lower bound of smallvec is the correct length
|
||||||
|
Some(ref iter) => iter.size_hint().0 == 0,
|
||||||
|
None => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for Removed {
|
impl Iterator for Removed {
|
||||||
@@ -945,6 +990,53 @@ mod tests {
|
|||||||
assert_eq!(vals.next(), removed.next().as_ref());
|
assert_eq!(vals.next(), removed.next().as_ref());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_all_iteration_order_matches_insertion_order() {
|
||||||
|
let mut map = HeaderMap::new();
|
||||||
|
|
||||||
|
let mut vals = map.get_all(header::COOKIE);
|
||||||
|
assert!(vals.next().is_none());
|
||||||
|
|
||||||
|
map.append(header::COOKIE, HeaderValue::from_static("1"));
|
||||||
|
let mut vals = map.get_all(header::COOKIE);
|
||||||
|
assert_eq!(vals.next().unwrap().as_bytes(), b"1");
|
||||||
|
assert!(vals.next().is_none());
|
||||||
|
|
||||||
|
map.append(header::COOKIE, HeaderValue::from_static("2"));
|
||||||
|
let mut vals = map.get_all(header::COOKIE);
|
||||||
|
assert_eq!(vals.next().unwrap().as_bytes(), b"1");
|
||||||
|
assert_eq!(vals.next().unwrap().as_bytes(), b"2");
|
||||||
|
assert!(vals.next().is_none());
|
||||||
|
|
||||||
|
map.append(header::COOKIE, HeaderValue::from_static("3"));
|
||||||
|
map.append(header::COOKIE, HeaderValue::from_static("4"));
|
||||||
|
map.append(header::COOKIE, HeaderValue::from_static("5"));
|
||||||
|
let mut vals = map.get_all(header::COOKIE);
|
||||||
|
assert_eq!(vals.next().unwrap().as_bytes(), b"1");
|
||||||
|
assert_eq!(vals.next().unwrap().as_bytes(), b"2");
|
||||||
|
assert_eq!(vals.next().unwrap().as_bytes(), b"3");
|
||||||
|
assert_eq!(vals.next().unwrap().as_bytes(), b"4");
|
||||||
|
assert_eq!(vals.next().unwrap().as_bytes(), b"5");
|
||||||
|
assert!(vals.next().is_none());
|
||||||
|
|
||||||
|
let _ = map.insert(header::COOKIE, HeaderValue::from_static("6"));
|
||||||
|
let mut vals = map.get_all(header::COOKIE);
|
||||||
|
assert_eq!(vals.next().unwrap().as_bytes(), b"6");
|
||||||
|
assert!(vals.next().is_none());
|
||||||
|
|
||||||
|
let _ = map.insert(header::COOKIE, HeaderValue::from_static("7"));
|
||||||
|
let _ = map.insert(header::COOKIE, HeaderValue::from_static("8"));
|
||||||
|
let mut vals = map.get_all(header::COOKIE);
|
||||||
|
assert_eq!(vals.next().unwrap().as_bytes(), b"8");
|
||||||
|
assert!(vals.next().is_none());
|
||||||
|
|
||||||
|
map.append(header::COOKIE, HeaderValue::from_static("9"));
|
||||||
|
let mut vals = map.get_all(header::COOKIE);
|
||||||
|
assert_eq!(vals.next().unwrap().as_bytes(), b"8");
|
||||||
|
assert_eq!(vals.next().unwrap().as_bytes(), b"9");
|
||||||
|
assert!(vals.next().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
fn owned_pair<'a>(
|
fn owned_pair<'a>(
|
||||||
(name, val): (&'a HeaderName, &'a HeaderValue),
|
(name, val): (&'a HeaderName, &'a HeaderValue),
|
||||||
) -> (HeaderName, HeaderValue) {
|
) -> (HeaderName, HeaderValue) {
|
||||||
|
@@ -29,16 +29,14 @@ pub use http::header::{
|
|||||||
X_FRAME_OPTIONS, X_XSS_PROTECTION,
|
X_FRAME_OPTIONS, X_XSS_PROTECTION,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::error::ParseError;
|
use crate::{error::ParseError, HttpMessage};
|
||||||
use crate::HttpMessage;
|
|
||||||
|
|
||||||
mod as_name;
|
mod as_name;
|
||||||
mod into_pair;
|
mod into_pair;
|
||||||
mod into_value;
|
mod into_value;
|
||||||
mod utils;
|
|
||||||
|
|
||||||
pub(crate) mod map;
|
pub(crate) mod map;
|
||||||
mod shared;
|
mod shared;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use self::shared::*;
|
pub use self::shared::*;
|
||||||
@@ -46,10 +44,10 @@ pub use self::shared::*;
|
|||||||
pub use self::as_name::AsHeaderName;
|
pub use self::as_name::AsHeaderName;
|
||||||
pub use self::into_pair::IntoHeaderPair;
|
pub use self::into_pair::IntoHeaderPair;
|
||||||
pub use self::into_value::IntoHeaderValue;
|
pub use self::into_value::IntoHeaderValue;
|
||||||
#[doc(hidden)]
|
pub use self::map::{GetAll, HeaderMap, Removed};
|
||||||
pub use self::map::GetAll;
|
pub use self::utils::{
|
||||||
pub use self::map::HeaderMap;
|
fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode,
|
||||||
pub use self::utils::*;
|
};
|
||||||
|
|
||||||
/// A trait for any object that already represents a valid header field and value.
|
/// A trait for any object that already represents a valid header field and value.
|
||||||
pub trait Header: IntoHeaderValue {
|
pub trait Header: IntoHeaderValue {
|
||||||
@@ -68,7 +66,7 @@ impl From<http::HeaderMap> for HeaderMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// This encode set is used for HTTP header values and is defined at
|
/// This encode set is used for HTTP header values and is defined at
|
||||||
/// https://tools.ietf.org/html/rfc5987#section-3.2.
|
/// <https://tools.ietf.org/html/rfc5987#section-3.2>.
|
||||||
pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS
|
pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS
|
||||||
.add(b' ')
|
.add(b' ')
|
||||||
.add(b'"')
|
.add(b'"')
|
||||||
|
@@ -88,7 +88,7 @@ impl Charset {
|
|||||||
Iso_8859_8_E => "ISO-8859-8-E",
|
Iso_8859_8_E => "ISO-8859-8-E",
|
||||||
Iso_8859_8_I => "ISO-8859-8-I",
|
Iso_8859_8_I => "ISO-8859-8-I",
|
||||||
Gb2312 => "GB2312",
|
Gb2312 => "GB2312",
|
||||||
Big5 => "big5",
|
Big5 => "Big5",
|
||||||
Koi8_R => "KOI8-R",
|
Koi8_R => "KOI8-R",
|
||||||
Ext(ref s) => s,
|
Ext(ref s) => s,
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ impl FromStr for Charset {
|
|||||||
"ISO-8859-8-E" => Iso_8859_8_E,
|
"ISO-8859-8-E" => Iso_8859_8_E,
|
||||||
"ISO-8859-8-I" => Iso_8859_8_I,
|
"ISO-8859-8-I" => Iso_8859_8_I,
|
||||||
"GB2312" => Gb2312,
|
"GB2312" => Gb2312,
|
||||||
"big5" => Big5,
|
"BIG5" => Big5,
|
||||||
"KOI8-R" => Koi8_R,
|
"KOI8-R" => Koi8_R,
|
||||||
s => Ext(s.to_owned()),
|
s => Ext(s.to_owned()),
|
||||||
})
|
})
|
||||||
|
@@ -56,6 +56,7 @@ where
|
|||||||
|
|
||||||
/// Percent encode a sequence of bytes with a character set defined in
|
/// Percent encode a sequence of bytes with a character set defined in
|
||||||
/// <https://tools.ietf.org/html/rfc5987#section-3.2>
|
/// <https://tools.ietf.org/html/rfc5987#section-3.2>
|
||||||
|
#[inline]
|
||||||
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
|
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
|
||||||
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
|
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
|
||||||
fmt::Display::fmt(&encoded, f)
|
fmt::Display::fmt(&encoded, f)
|
||||||
|
@@ -220,7 +220,7 @@ impl<T: Into<String>> From<(CloseCode, T)> for CloseReason {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The WebSocket GUID as stated in the spec. See https://tools.ietf.org/html/rfc6455#section-1.3.
|
/// The WebSocket GUID as stated in the spec. See <https://tools.ietf.org/html/rfc6455#section-1.3>.
|
||||||
static WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
static WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
|
||||||
/// Hashes the `Sec-WebSocket-Key` header according to the WebSocket spec.
|
/// Hashes the `Sec-WebSocket-Key` header according to the WebSocket spec.
|
||||||
|
@@ -20,7 +20,6 @@ actix-utils = "3.0.0"
|
|||||||
bytes = "1"
|
bytes = "1"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
local-waker = "0.1"
|
local-waker = "0.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
@@ -30,5 +29,6 @@ twoway = "0.2"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-http = "3.0.0-beta.13"
|
actix-http = "3.0.0-beta.13"
|
||||||
|
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
tokio = { version = "1", features = ["sync"] }
|
tokio = { version = "1", features = ["sync"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
|
@@ -17,7 +17,6 @@ use actix_web::{
|
|||||||
};
|
};
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures_core::stream::{LocalBoxStream, Stream};
|
use futures_core::stream::{LocalBoxStream, Stream};
|
||||||
use futures_util::stream::StreamExt as _;
|
|
||||||
use local_waker::LocalWaker;
|
use local_waker::LocalWaker;
|
||||||
|
|
||||||
use crate::error::MultipartError;
|
use crate::error::MultipartError;
|
||||||
@@ -33,7 +32,7 @@ const MAX_HEADERS: usize = 32;
|
|||||||
pub struct Multipart {
|
pub struct Multipart {
|
||||||
safety: Safety,
|
safety: Safety,
|
||||||
error: Option<MultipartError>,
|
error: Option<MultipartError>,
|
||||||
inner: Option<Rc<RefCell<InnerMultipart>>>,
|
inner: Option<InnerMultipart>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum InnerMultipartItem {
|
enum InnerMultipartItem {
|
||||||
@@ -67,7 +66,7 @@ impl Multipart {
|
|||||||
/// Create multipart instance for boundary.
|
/// Create multipart instance for boundary.
|
||||||
pub fn new<S>(headers: &HeaderMap, stream: S) -> Multipart
|
pub fn new<S>(headers: &HeaderMap, stream: S) -> Multipart
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin + 'static,
|
S: Stream<Item = Result<Bytes, PayloadError>> + 'static,
|
||||||
{
|
{
|
||||||
match Self::boundary(headers) {
|
match Self::boundary(headers) {
|
||||||
Ok(boundary) => Multipart::from_boundary(boundary, stream),
|
Ok(boundary) => Multipart::from_boundary(boundary, stream),
|
||||||
@@ -77,39 +76,32 @@ impl Multipart {
|
|||||||
|
|
||||||
/// Extract boundary info from headers.
|
/// Extract boundary info from headers.
|
||||||
pub(crate) fn boundary(headers: &HeaderMap) -> Result<String, MultipartError> {
|
pub(crate) fn boundary(headers: &HeaderMap) -> Result<String, MultipartError> {
|
||||||
if let Some(content_type) = headers.get(&header::CONTENT_TYPE) {
|
headers
|
||||||
if let Ok(content_type) = content_type.to_str() {
|
.get(&header::CONTENT_TYPE)
|
||||||
if let Ok(ct) = content_type.parse::<mime::Mime>() {
|
.ok_or(MultipartError::NoContentType)?
|
||||||
if let Some(boundary) = ct.get_param(mime::BOUNDARY) {
|
.to_str()
|
||||||
Ok(boundary.as_str().to_owned())
|
.ok()
|
||||||
} else {
|
.and_then(|content_type| content_type.parse::<mime::Mime>().ok())
|
||||||
Err(MultipartError::Boundary)
|
.ok_or(MultipartError::ParseContentType)?
|
||||||
}
|
.get_param(mime::BOUNDARY)
|
||||||
} else {
|
.map(|boundary| boundary.as_str().to_owned())
|
||||||
Err(MultipartError::ParseContentType)
|
.ok_or(MultipartError::Boundary)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(MultipartError::ParseContentType)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(MultipartError::NoContentType)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create multipart instance for given boundary and stream
|
/// Create multipart instance for given boundary and stream
|
||||||
pub(crate) fn from_boundary<S>(boundary: String, stream: S) -> Multipart
|
pub(crate) fn from_boundary<S>(boundary: String, stream: S) -> Multipart
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin + 'static,
|
S: Stream<Item = Result<Bytes, PayloadError>> + 'static,
|
||||||
{
|
{
|
||||||
Multipart {
|
Multipart {
|
||||||
error: None,
|
error: None,
|
||||||
safety: Safety::new(),
|
safety: Safety::new(),
|
||||||
inner: Some(Rc::new(RefCell::new(InnerMultipart {
|
inner: Some(InnerMultipart {
|
||||||
boundary,
|
boundary,
|
||||||
payload: PayloadRef::new(PayloadBuffer::new(Box::new(stream))),
|
payload: PayloadRef::new(PayloadBuffer::new(stream)),
|
||||||
state: InnerState::FirstBoundary,
|
state: InnerState::FirstBoundary,
|
||||||
item: InnerMultipartItem::None,
|
item: InnerMultipartItem::None,
|
||||||
}))),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,20 +118,27 @@ impl Multipart {
|
|||||||
impl Stream for Multipart {
|
impl Stream for Multipart {
|
||||||
type Item = Result<Field, MultipartError>;
|
type Item = Result<Field, MultipartError>;
|
||||||
|
|
||||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
if let Some(err) = self.error.take() {
|
let this = self.get_mut();
|
||||||
Poll::Ready(Some(Err(err)))
|
|
||||||
} else if self.safety.current() {
|
match this.inner.as_mut() {
|
||||||
let this = self.get_mut();
|
Some(inner) => {
|
||||||
let mut inner = this.inner.as_mut().unwrap().borrow_mut();
|
if let Some(mut buffer) = inner.payload.get_mut(&this.safety) {
|
||||||
if let Some(mut payload) = inner.payload.get_mut(&this.safety) {
|
// check safety and poll read payload to buffer.
|
||||||
payload.poll_stream(cx)?;
|
buffer.poll_stream(cx)?;
|
||||||
|
} else if !this.safety.is_clean() {
|
||||||
|
// safety violation
|
||||||
|
return Poll::Ready(Some(Err(MultipartError::NotConsumed)));
|
||||||
|
} else {
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.poll(&this.safety, cx)
|
||||||
}
|
}
|
||||||
inner.poll(&this.safety, cx)
|
None => Poll::Ready(Some(Err(this
|
||||||
} else if !self.safety.is_clean() {
|
.error
|
||||||
Poll::Ready(Some(Err(MultipartError::NotConsumed)))
|
.take()
|
||||||
} else {
|
.expect("Multipart polled after finish")))),
|
||||||
Poll::Pending
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,17 +159,15 @@ impl InnerMultipart {
|
|||||||
Ok(httparse::Status::Complete((_, hdrs))) => {
|
Ok(httparse::Status::Complete((_, hdrs))) => {
|
||||||
// convert headers
|
// convert headers
|
||||||
let mut headers = HeaderMap::with_capacity(hdrs.len());
|
let mut headers = HeaderMap::with_capacity(hdrs.len());
|
||||||
|
|
||||||
for h in hdrs {
|
for h in hdrs {
|
||||||
if let Ok(name) = HeaderName::try_from(h.name) {
|
let name =
|
||||||
if let Ok(value) = HeaderValue::try_from(h.value) {
|
HeaderName::try_from(h.name).map_err(|_| ParseError::Header)?;
|
||||||
headers.append(name, value);
|
let value = HeaderValue::try_from(h.value)
|
||||||
} else {
|
.map_err(|_| ParseError::Header)?;
|
||||||
return Err(ParseError::Header.into());
|
headers.append(name, value);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(ParseError::Header.into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(headers))
|
Ok(Some(headers))
|
||||||
}
|
}
|
||||||
Ok(httparse::Status::Partial) => Err(ParseError::Header.into()),
|
Ok(httparse::Status::Partial) => Err(ParseError::Header.into()),
|
||||||
@@ -466,17 +463,19 @@ impl Stream for Field {
|
|||||||
type Item = Result<Bytes, MultipartError>;
|
type Item = Result<Bytes, MultipartError>;
|
||||||
|
|
||||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
if self.safety.current() {
|
let this = self.get_mut();
|
||||||
let mut inner = self.inner.borrow_mut();
|
let mut inner = this.inner.borrow_mut();
|
||||||
if let Some(mut payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety) {
|
if let Some(mut buffer) = inner.payload.as_ref().unwrap().get_mut(&this.safety) {
|
||||||
payload.poll_stream(cx)?;
|
// check safety and poll read payload to buffer.
|
||||||
}
|
buffer.poll_stream(cx)?;
|
||||||
inner.poll(&self.safety)
|
} else if !this.safety.is_clean() {
|
||||||
} else if !self.safety.is_clean() {
|
// safety violation
|
||||||
Poll::Ready(Some(Err(MultipartError::NotConsumed)))
|
return Poll::Ready(Some(Err(MultipartError::NotConsumed)));
|
||||||
} else {
|
} else {
|
||||||
Poll::Pending
|
return Poll::Pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inner.poll(&this.safety)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,10 +689,7 @@ impl PayloadRef {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<RefMut<'a, PayloadBuffer>>
|
fn get_mut(&self, s: &Safety) -> Option<RefMut<'_, PayloadBuffer>> {
|
||||||
where
|
|
||||||
'a: 'b,
|
|
||||||
{
|
|
||||||
if s.current() {
|
if s.current() {
|
||||||
Some(self.payload.borrow_mut())
|
Some(self.payload.borrow_mut())
|
||||||
} else {
|
} else {
|
||||||
@@ -779,7 +775,7 @@ impl PayloadBuffer {
|
|||||||
PayloadBuffer {
|
PayloadBuffer {
|
||||||
eof: false,
|
eof: false,
|
||||||
buf: BytesMut::new(),
|
buf: BytesMut::new(),
|
||||||
stream: stream.boxed_local(),
|
stream: Box::pin(stream),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -860,7 +856,7 @@ mod tests {
|
|||||||
use actix_web::test::TestRequest;
|
use actix_web::test::TestRequest;
|
||||||
use actix_web::FromRequest;
|
use actix_web::FromRequest;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::future::lazy;
|
use futures_util::{future::lazy, StreamExt};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||||
|
|
||||||
|
@@ -1770,6 +1770,12 @@ mod tests {
|
|||||||
match_methods_agree!(["/v{v}", "/ver/{v}"] => "", "s/v", "/v1", "/v1/xx", "/ver/i3/5", "/ver/1");
|
match_methods_agree!(["/v{v}", "/ver/{v}"] => "", "s/v", "/v1", "/v1/xx", "/ver/i3/5", "/ver/1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn duplicate_segment_name() {
|
||||||
|
ResourceDef::new("/user/{id}/post/{id}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn invalid_dynamic_segment_delimiter() {
|
fn invalid_dynamic_segment_delimiter() {
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.11 - 2021-11-22
|
## 3.0.0-beta.11 - 2021-11-22
|
||||||
|
* No significant changes from `3.0.0-beta.10`.
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.10 - 2021-11-15
|
## 3.0.0-beta.10 - 2021-11-15
|
||||||
|
@@ -1,18 +1,16 @@
|
|||||||
use std::convert::TryFrom;
|
use std::{convert::TryFrom, fmt, net::IpAddr, rc::Rc, time::Duration};
|
||||||
use std::fmt;
|
|
||||||
use std::net::IpAddr;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use actix_http::http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri};
|
use actix_http::http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri};
|
||||||
use actix_rt::net::{ActixStream, TcpStream};
|
use actix_rt::net::{ActixStream, TcpStream};
|
||||||
use actix_service::{boxed, Service};
|
use actix_service::{boxed, Service};
|
||||||
|
|
||||||
use crate::client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection};
|
use crate::{
|
||||||
use crate::connect::DefaultConnector;
|
client::{Connector, ConnectorService, TcpConnect, TcpConnectError, TcpConnection},
|
||||||
use crate::error::SendRequestError;
|
connect::DefaultConnector,
|
||||||
use crate::middleware::{NestTransform, Redirect, Transform};
|
error::SendRequestError,
|
||||||
use crate::{Client, ClientConfig, ConnectRequest, ConnectResponse};
|
middleware::{NestTransform, Redirect, Transform},
|
||||||
|
Client, ClientConfig, ConnectRequest, ConnectResponse,
|
||||||
|
};
|
||||||
|
|
||||||
/// An HTTP Client builder
|
/// An HTTP Client builder
|
||||||
///
|
///
|
||||||
|
Reference in New Issue
Block a user