mirror of
https://github.com/actix/actix-web.git
synced 2024-11-22 17:41:11 +00:00
body ergonomics v3 (#2468)
This commit is contained in:
parent
a2d5c5a058
commit
c7c02ef99d
84 changed files with 2134 additions and 1685 deletions
|
@ -4,6 +4,8 @@
|
||||||
### 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` and `HttpResponse::map_into_boxed_body`. [#2468]
|
||||||
|
* `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Rename `Accept::{mime_precedence => ranked}`. [#2480]
|
* Rename `Accept::{mime_precedence => ranked}`. [#2480]
|
||||||
|
@ -11,8 +13,10 @@
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* Accept wildcard `*` items in `AcceptLanguage`. [#2480]
|
* Accept wildcard `*` items in `AcceptLanguage`. [#2480]
|
||||||
|
* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468]
|
||||||
* Typed headers containing lists that require one or more items now enforce this minimum. [#2482]
|
* Typed headers containing lists that require one or more items now enforce this minimum. [#2482]
|
||||||
|
|
||||||
|
[#2468]: https://github.com/actix/actix-web/pull/2468
|
||||||
[#2480]: https://github.com/actix/actix-web/pull/2480
|
[#2480]: https://github.com/actix/actix-web/pull/2480
|
||||||
[#2482]: https://github.com/actix/actix-web/pull/2482
|
[#2482]: https://github.com/actix/actix-web/pull/2482
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,7 @@ use std::{
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_web::error::Error;
|
use actix_web::{error::Error, web::Bytes};
|
||||||
use bytes::Bytes;
|
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,12 @@ 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_service::{Service, ServiceFactory};
|
use actix_service::{Service, ServiceFactory};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
|
body::{self, BoxBody, SizedStream},
|
||||||
dev::{
|
dev::{
|
||||||
AppService, BodyEncoding, HttpServiceFactory, ResourceDef, ServiceRequest,
|
AppService, BodyEncoding, HttpServiceFactory, ResourceDef, ServiceRequest,
|
||||||
ServiceResponse, SizedStream,
|
ServiceResponse,
|
||||||
},
|
},
|
||||||
http::{
|
http::{
|
||||||
header::{
|
header::{
|
||||||
|
@ -113,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.
|
||||||
///
|
///
|
||||||
|
@ -394,7 +396,7 @@ impl NamedFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an `HttpResponse` with file as a streaming body.
|
/// Creates an `HttpResponse` with file as a streaming body.
|
||||||
pub fn into_response(self, req: &HttpRequest) -> HttpResponse {
|
pub fn into_response(self, req: &HttpRequest) -> HttpResponse<BoxBody> {
|
||||||
if self.status_code != StatusCode::OK {
|
if self.status_code != StatusCode::OK {
|
||||||
let mut res = HttpResponse::build(self.status_code);
|
let mut res = HttpResponse::build(self.status_code);
|
||||||
|
|
||||||
|
@ -416,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);
|
||||||
}
|
}
|
||||||
|
@ -527,10 +529,13 @@ impl NamedFile {
|
||||||
if precondition_failed {
|
if precondition_failed {
|
||||||
return resp.status(StatusCode::PRECONDITION_FAILED).finish();
|
return resp.status(StatusCode::PRECONDITION_FAILED).finish();
|
||||||
} else if not_modified {
|
} else if not_modified {
|
||||||
return resp.status(StatusCode::NOT_MODIFIED).body(AnyBody::None);
|
return resp
|
||||||
|
.status(StatusCode::NOT_MODIFIED)
|
||||||
|
.body(body::None::new())
|
||||||
|
.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);
|
||||||
|
@ -595,7 +600,9 @@ impl DerefMut for NamedFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for NamedFile {
|
impl Responder for NamedFile {
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
type Body = BoxBody;
|
||||||
|
|
||||||
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
self.into_response(req)
|
self.into_response(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,30 @@
|
||||||
### Added
|
### Added
|
||||||
* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483]
|
* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483]
|
||||||
* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
|
* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
|
||||||
|
* `Response::map_into_boxed_body`. [#2468]
|
||||||
|
* `body::EitherBody` enum. [#2468]
|
||||||
|
* `body::None` struct. [#2468]
|
||||||
|
* Impl `MessageBody` for `bytestring::ByteString`. [#2468]
|
||||||
|
* `impl Clone for ws::HandshakeError`. [#2468]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Rename `body::BoxBody::{from_body => new}`. [#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]
|
||||||
|
* 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
|
||||||
|
* `ResponseBuilder::streaming`. [#2468]
|
||||||
|
* `impl Future` for `ResponseBuilder`. [#2468]
|
||||||
|
* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468]
|
||||||
|
* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468]
|
||||||
|
|
||||||
[#2483]: https://github.com/actix/actix-web/pull/2483
|
[#2483]: https://github.com/actix/actix-web/pull/2483
|
||||||
|
[#2468]: https://github.com/actix/actix-web/pull/2468
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.14 - 2021-11-30
|
## 3.0.0-beta.14 - 2021-11-30
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use actix_http::{body::AnyBody, http::HeaderValue, http::StatusCode};
|
use actix_http::{
|
||||||
use actix_http::{Error, HttpService, Request, Response};
|
body::MessageBody, http::HeaderValue, http::StatusCode, Error, HttpService, Request,
|
||||||
|
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<AnyBody>, 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?)
|
||||||
|
|
|
@ -1,333 +0,0 @@
|
||||||
use std::{
|
|
||||||
borrow::Cow,
|
|
||||||
error::Error as StdError,
|
|
||||||
fmt, mem,
|
|
||||||
pin::Pin,
|
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
|
||||||
use futures_core::Stream;
|
|
||||||
use pin_project::pin_project;
|
|
||||||
|
|
||||||
use crate::error::Error;
|
|
||||||
|
|
||||||
use super::{BodySize, BodyStream, MessageBody, MessageBodyMapErr, SizedStream};
|
|
||||||
|
|
||||||
#[deprecated(since = "4.0.0", note = "Renamed to `AnyBody`.")]
|
|
||||||
pub type Body = AnyBody;
|
|
||||||
|
|
||||||
/// Represents various types of HTTP message body.
|
|
||||||
#[pin_project(project = AnyBodyProj)]
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum AnyBody<B = BoxBody> {
|
|
||||||
/// Empty response. `Content-Length` header is not set.
|
|
||||||
None,
|
|
||||||
|
|
||||||
/// Complete, in-memory response body.
|
|
||||||
Bytes(Bytes),
|
|
||||||
|
|
||||||
/// Generic / Other message body.
|
|
||||||
Body(#[pin] B),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnyBody {
|
|
||||||
/// Constructs a "body" representing an empty response.
|
|
||||||
pub fn none() -> Self {
|
|
||||||
Self::None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Constructs a new, 0-length body.
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
Self::Bytes(Bytes::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create boxed body from generic message body.
|
|
||||||
pub fn new_boxed<B>(body: B) -> Self
|
|
||||||
where
|
|
||||||
B: MessageBody + 'static,
|
|
||||||
B::Error: Into<Box<dyn StdError + 'static>>,
|
|
||||||
{
|
|
||||||
Self::Body(BoxBody::from_body(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Constructs new `AnyBody` instance from a slice of bytes by copying it.
|
|
||||||
///
|
|
||||||
/// If your bytes container is owned, it may be cheaper to use a `From` impl.
|
|
||||||
pub fn copy_from_slice(s: &[u8]) -> Self {
|
|
||||||
Self::Bytes(Bytes::copy_from_slice(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[deprecated(since = "4.0.0", note = "Renamed to `copy_from_slice`.")]
|
|
||||||
pub fn from_slice(s: &[u8]) -> Self {
|
|
||||||
Self::Bytes(Bytes::copy_from_slice(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> AnyBody<B>
|
|
||||||
where
|
|
||||||
B: MessageBody + 'static,
|
|
||||||
B::Error: Into<Box<dyn StdError + 'static>>,
|
|
||||||
{
|
|
||||||
/// Create body from generic message body.
|
|
||||||
pub fn new(body: B) -> Self {
|
|
||||||
Self::Body(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_boxed(self) -> AnyBody {
|
|
||||||
match self {
|
|
||||||
Self::None => AnyBody::None,
|
|
||||||
Self::Bytes(bytes) => AnyBody::Bytes(bytes),
|
|
||||||
Self::Body(body) => AnyBody::new_boxed(body),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> MessageBody for AnyBody<B>
|
|
||||||
where
|
|
||||||
B: MessageBody,
|
|
||||||
B::Error: Into<Box<dyn StdError>> + 'static,
|
|
||||||
{
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn size(&self) -> BodySize {
|
|
||||||
match self {
|
|
||||||
AnyBody::None => BodySize::None,
|
|
||||||
AnyBody::Bytes(ref bin) => BodySize::Sized(bin.len() as u64),
|
|
||||||
AnyBody::Body(ref body) => body.size(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_next(
|
|
||||||
self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
|
||||||
match self.project() {
|
|
||||||
AnyBodyProj::None => Poll::Ready(None),
|
|
||||||
AnyBodyProj::Bytes(bin) => {
|
|
||||||
let len = bin.len();
|
|
||||||
if len == 0 {
|
|
||||||
Poll::Ready(None)
|
|
||||||
} else {
|
|
||||||
Poll::Ready(Some(Ok(mem::take(bin))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnyBodyProj::Body(body) => body
|
|
||||||
.poll_next(cx)
|
|
||||||
.map_err(|err| Error::new_body().with_cause(err)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for AnyBody {
|
|
||||||
fn eq(&self, other: &AnyBody) -> bool {
|
|
||||||
match *self {
|
|
||||||
AnyBody::None => matches!(*other, AnyBody::None),
|
|
||||||
AnyBody::Bytes(ref b) => match *other {
|
|
||||||
AnyBody::Bytes(ref b2) => b == b2,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
AnyBody::Body(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: fmt::Debug> fmt::Debug for AnyBody<S> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
AnyBody::None => write!(f, "AnyBody::None"),
|
|
||||||
AnyBody::Bytes(ref bytes) => write!(f, "AnyBody::Bytes({:?})", bytes),
|
|
||||||
AnyBody::Body(ref stream) => write!(f, "AnyBody::Message({:?})", stream),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<&'static str> for AnyBody<B> {
|
|
||||||
fn from(string: &'static str) -> Self {
|
|
||||||
Self::Bytes(Bytes::from_static(string.as_ref()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<&'static [u8]> for AnyBody<B> {
|
|
||||||
fn from(bytes: &'static [u8]) -> Self {
|
|
||||||
Self::Bytes(Bytes::from_static(bytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<Vec<u8>> for AnyBody<B> {
|
|
||||||
fn from(vec: Vec<u8>) -> Self {
|
|
||||||
Self::Bytes(Bytes::from(vec))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<String> for AnyBody<B> {
|
|
||||||
fn from(string: String) -> Self {
|
|
||||||
Self::Bytes(Bytes::from(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<&'_ String> for AnyBody<B> {
|
|
||||||
fn from(string: &String) -> Self {
|
|
||||||
Self::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<Cow<'_, str>> for AnyBody<B> {
|
|
||||||
fn from(string: Cow<'_, str>) -> Self {
|
|
||||||
match string {
|
|
||||||
Cow::Owned(s) => Self::from(s),
|
|
||||||
Cow::Borrowed(s) => {
|
|
||||||
Self::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<Bytes> for AnyBody<B> {
|
|
||||||
fn from(bytes: Bytes) -> Self {
|
|
||||||
Self::Bytes(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<BytesMut> for AnyBody<B> {
|
|
||||||
fn from(bytes: BytesMut) -> Self {
|
|
||||||
Self::Bytes(bytes.freeze())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
where
|
|
||||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
|
||||||
{
|
|
||||||
fn from(stream: SizedStream<S>) -> Self {
|
|
||||||
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
|
|
||||||
where
|
|
||||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
|
||||||
{
|
|
||||||
fn from(stream: BodyStream<S>) -> Self {
|
|
||||||
AnyBody::new_boxed(stream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A boxed message body with boxed errors.
|
|
||||||
pub struct BoxBody(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>);
|
|
||||||
|
|
||||||
impl BoxBody {
|
|
||||||
/// Boxes a `MessageBody` and any errors it generates.
|
|
||||||
pub fn from_body<B>(body: B) -> Self
|
|
||||||
where
|
|
||||||
B: MessageBody + 'static,
|
|
||||||
B::Error: Into<Box<dyn StdError + 'static>>,
|
|
||||||
{
|
|
||||||
let body = MessageBodyMapErr::new(body, Into::into);
|
|
||||||
Self(Box::pin(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutable pinned reference to the inner message body type.
|
|
||||||
pub fn as_pin_mut(
|
|
||||||
&mut self,
|
|
||||||
) -> Pin<&mut (dyn MessageBody<Error = Box<dyn StdError>>)> {
|
|
||||||
self.0.as_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for BoxBody {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_str("BoxAnyBody(dyn MessageBody)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MessageBody for BoxBody {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn size(&self) -> BodySize {
|
|
||||||
self.0.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_next(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
|
||||||
self.0
|
|
||||||
.as_mut()
|
|
||||||
.poll_next(cx)
|
|
||||||
.map_err(|err| Error::new_body().with_cause(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::marker::PhantomPinned;
|
|
||||||
|
|
||||||
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::body::to_bytes;
|
|
||||||
|
|
||||||
struct PinType(PhantomPinned);
|
|
||||||
|
|
||||||
impl MessageBody for PinType {
|
|
||||||
type Error = crate::Error;
|
|
||||||
|
|
||||||
fn size(&self) -> BodySize {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_next(
|
|
||||||
self: Pin<&mut Self>,
|
|
||||||
_cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_impl_all!(AnyBody<()>: MessageBody, fmt::Debug, Send, Sync, Unpin);
|
|
||||||
assert_impl_all!(AnyBody<AnyBody<()>>: MessageBody, fmt::Debug, Send, Sync, Unpin);
|
|
||||||
assert_impl_all!(AnyBody<Bytes>: MessageBody, fmt::Debug, Send, Sync, Unpin);
|
|
||||||
assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Unpin);
|
|
||||||
assert_impl_all!(BoxBody: MessageBody, fmt::Debug, Unpin);
|
|
||||||
assert_impl_all!(AnyBody<PinType>: MessageBody);
|
|
||||||
|
|
||||||
assert_not_impl_all!(AnyBody: Send, Sync, Unpin);
|
|
||||||
assert_not_impl_all!(BoxBody: Send, Sync, Unpin);
|
|
||||||
assert_not_impl_all!(AnyBody<PinType>: Send, Sync, Unpin);
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn nested_boxed_body() {
|
|
||||||
let body = AnyBody::copy_from_slice(&[1, 2, 3]);
|
|
||||||
let boxed_body = BoxBody::from_body(BoxBody::from_body(body));
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
to_bytes(boxed_body).await.unwrap(),
|
|
||||||
Bytes::from(vec![1, 2, 3]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>>,
|
||||||
|
@ -75,6 +77,7 @@ mod tests {
|
||||||
use derive_more::{Display, Error};
|
use derive_more::{Display, Error};
|
||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use futures_util::{stream, FutureExt as _};
|
use futures_util::{stream, FutureExt as _};
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -166,12 +169,14 @@ mod tests {
|
||||||
BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)]));
|
BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)]));
|
||||||
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
|
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
|
||||||
|
|
||||||
#[pin_project::pin_project(project = TimeDelayStreamProj)]
|
pin_project! {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum TimeDelayStream {
|
#[project = TimeDelayStreamProj]
|
||||||
Start,
|
enum TimeDelayStream {
|
||||||
Sleep(Pin<Box<Sleep>>),
|
Start,
|
||||||
Done,
|
Sleep { delay: Pin<Box<Sleep>> },
|
||||||
|
Done,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for TimeDelayStream {
|
impl Stream for TimeDelayStream {
|
||||||
|
@ -184,12 +189,14 @@ mod tests {
|
||||||
match self.as_mut().get_mut() {
|
match self.as_mut().get_mut() {
|
||||||
TimeDelayStream::Start => {
|
TimeDelayStream::Start => {
|
||||||
let sleep = sleep(Duration::from_millis(1));
|
let sleep = sleep(Duration::from_millis(1));
|
||||||
self.as_mut().set(TimeDelayStream::Sleep(Box::pin(sleep)));
|
self.as_mut().set(TimeDelayStream::Sleep {
|
||||||
|
delay: Box::pin(sleep),
|
||||||
|
});
|
||||||
cx.waker().wake_by_ref();
|
cx.waker().wake_by_ref();
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeDelayStream::Sleep(ref mut delay) => {
|
TimeDelayStream::Sleep { ref mut delay } => {
|
||||||
ready!(delay.poll_unpin(cx));
|
ready!(delay.poll_unpin(cx));
|
||||||
self.set(TimeDelayStream::Done);
|
self.set(TimeDelayStream::Done);
|
||||||
cx.waker().wake_by_ref();
|
cx.waker().wake_by_ref();
|
||||||
|
|
80
actix-http/src/body/boxed.rs
Normal file
80
actix-http/src/body/boxed.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
use std::{
|
||||||
|
error::Error as StdError,
|
||||||
|
fmt,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
use super::{BodySize, MessageBody, MessageBodyMapErr};
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
/// A boxed message body with boxed errors.
|
||||||
|
pub struct BoxBody(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>);
|
||||||
|
|
||||||
|
impl BoxBody {
|
||||||
|
/// Boxes a `MessageBody` and any errors it generates.
|
||||||
|
pub fn new<B>(body: B) -> Self
|
||||||
|
where
|
||||||
|
B: MessageBody + 'static,
|
||||||
|
{
|
||||||
|
let body = MessageBodyMapErr::new(body, Into::into);
|
||||||
|
Self(Box::pin(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable pinned reference to the inner message body type.
|
||||||
|
pub fn as_pin_mut(
|
||||||
|
&mut self,
|
||||||
|
) -> Pin<&mut (dyn MessageBody<Error = Box<dyn StdError>>)> {
|
||||||
|
self.0.as_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for BoxBody {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("BoxBody(dyn MessageBody)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBody for BoxBody {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn size(&self) -> BodySize {
|
||||||
|
self.0.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
self.0
|
||||||
|
.as_mut()
|
||||||
|
.poll_next(cx)
|
||||||
|
.map_err(|err| Error::new_body().with_cause(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::body::to_bytes;
|
||||||
|
|
||||||
|
assert_impl_all!(BoxBody: MessageBody, fmt::Debug, Unpin);
|
||||||
|
|
||||||
|
assert_not_impl_all!(BoxBody: Send, Sync, Unpin);
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn nested_boxed_body() {
|
||||||
|
let body = Bytes::from_static(&[1, 2, 3]);
|
||||||
|
let boxed_body = BoxBody::new(BoxBody::new(body));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
to_bytes(boxed_body).await.unwrap(),
|
||||||
|
Bytes::from(vec![1, 2, 3]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
83
actix-http/src/body/either.rs
Normal file
83
actix-http/src/body/either.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use std::{
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
|
use super::{BodySize, BoxBody, MessageBody};
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
#[project = EitherBodyProj]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum EitherBody<L, R = BoxBody> {
|
||||||
|
/// A body of type `L`.
|
||||||
|
Left { #[pin] body: L },
|
||||||
|
|
||||||
|
/// A body of type `R`.
|
||||||
|
Right { #[pin] body: R },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L> EitherBody<L, BoxBody> {
|
||||||
|
/// Creates new `EitherBody` using left variant and boxed right variant.
|
||||||
|
pub fn new(body: L) -> Self {
|
||||||
|
Self::Left { body }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> EitherBody<L, R> {
|
||||||
|
/// Creates new `EitherBody` using left variant.
|
||||||
|
pub fn left(body: L) -> Self {
|
||||||
|
Self::Left { body }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates new `EitherBody` using right variant.
|
||||||
|
pub fn right(body: R) -> Self {
|
||||||
|
Self::Right { body }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> MessageBody for EitherBody<L, R>
|
||||||
|
where
|
||||||
|
L: MessageBody + 'static,
|
||||||
|
R: MessageBody + 'static,
|
||||||
|
{
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn size(&self) -> BodySize {
|
||||||
|
match self {
|
||||||
|
EitherBody::Left { body } => body.size(),
|
||||||
|
EitherBody::Right { body } => body.size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
match self.project() {
|
||||||
|
EitherBodyProj::Left { body } => body
|
||||||
|
.poll_next(cx)
|
||||||
|
.map_err(|err| Error::new_body().with_cause(err)),
|
||||||
|
EitherBodyProj::Right { body } => body
|
||||||
|
.poll_next(cx)
|
||||||
|
.map_err(|err| Error::new_body().with_cause(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_parameter_inference() {
|
||||||
|
let _body: EitherBody<(), _> = EitherBody::new(());
|
||||||
|
|
||||||
|
let _body: EitherBody<_, ()> = EitherBody::left(());
|
||||||
|
let _body: EitherBody<(), _> = EitherBody::right(());
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
|
error::Error as StdError,
|
||||||
mem,
|
mem,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
|
@ -13,9 +14,12 @@ use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use super::BodySize;
|
use super::BodySize;
|
||||||
|
|
||||||
/// An interface for response bodies.
|
/// An interface types that can converted to bytes and used as response bodies.
|
||||||
|
// TODO: examples
|
||||||
pub trait MessageBody {
|
pub trait MessageBody {
|
||||||
type Error;
|
// 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>>;
|
||||||
|
|
||||||
/// Body size hint.
|
/// Body size hint.
|
||||||
fn size(&self) -> BodySize;
|
fn size(&self) -> BodySize;
|
||||||
|
@ -27,152 +31,218 @@ pub trait MessageBody {
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>>;
|
) -> Poll<Option<Result<Bytes, Self::Error>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageBody for () {
|
mod foreign_impls {
|
||||||
type Error = Infallible;
|
use super::*;
|
||||||
|
|
||||||
fn size(&self) -> BodySize {
|
impl MessageBody for Infallible {
|
||||||
BodySize::Sized(0)
|
type Error = Infallible;
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_next(
|
#[inline]
|
||||||
self: Pin<&mut Self>,
|
fn size(&self) -> BodySize {
|
||||||
_: &mut Context<'_>,
|
match *self {}
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
}
|
||||||
Poll::Ready(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> MessageBody for Box<B>
|
#[inline]
|
||||||
where
|
fn poll_next(
|
||||||
B: MessageBody + Unpin,
|
self: Pin<&mut Self>,
|
||||||
{
|
_cx: &mut Context<'_>,
|
||||||
type Error = B::Error;
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
match *self {}
|
||||||
fn size(&self) -> BodySize {
|
|
||||||
self.as_ref().size()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_next(
|
|
||||||
self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
|
||||||
Pin::new(self.get_mut().as_mut()).poll_next(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> MessageBody for Pin<Box<B>>
|
|
||||||
where
|
|
||||||
B: MessageBody,
|
|
||||||
{
|
|
||||||
type Error = B::Error;
|
|
||||||
|
|
||||||
fn size(&self) -> BodySize {
|
|
||||||
self.as_ref().size()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_next(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
|
||||||
self.as_mut().poll_next(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MessageBody for Bytes {
|
|
||||||
type Error = Infallible;
|
|
||||||
|
|
||||||
fn size(&self) -> BodySize {
|
|
||||||
BodySize::Sized(self.len() as u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_next(
|
|
||||||
self: Pin<&mut Self>,
|
|
||||||
_: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
|
||||||
if self.is_empty() {
|
|
||||||
Poll::Ready(None)
|
|
||||||
} else {
|
|
||||||
Poll::Ready(Some(Ok(mem::take(self.get_mut()))))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl MessageBody for BytesMut {
|
impl MessageBody for () {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
fn size(&self) -> BodySize {
|
#[inline]
|
||||||
BodySize::Sized(self.len() as u64)
|
fn size(&self) -> BodySize {
|
||||||
}
|
BodySize::Sized(0)
|
||||||
|
}
|
||||||
|
|
||||||
fn poll_next(
|
#[inline]
|
||||||
self: Pin<&mut Self>,
|
fn poll_next(
|
||||||
_: &mut Context<'_>,
|
self: Pin<&mut Self>,
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
_cx: &mut Context<'_>,
|
||||||
if self.is_empty() {
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
} else {
|
|
||||||
Poll::Ready(Some(Ok(mem::take(self.get_mut()).freeze())))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl MessageBody for &'static str {
|
impl<B> MessageBody for Box<B>
|
||||||
type Error = Infallible;
|
where
|
||||||
|
B: MessageBody + Unpin,
|
||||||
|
{
|
||||||
|
type Error = B::Error;
|
||||||
|
|
||||||
fn size(&self) -> BodySize {
|
#[inline]
|
||||||
BodySize::Sized(self.len() as u64)
|
fn size(&self) -> BodySize {
|
||||||
}
|
self.as_ref().size()
|
||||||
|
}
|
||||||
|
|
||||||
fn poll_next(
|
#[inline]
|
||||||
self: Pin<&mut Self>,
|
fn poll_next(
|
||||||
_: &mut Context<'_>,
|
self: Pin<&mut Self>,
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
cx: &mut Context<'_>,
|
||||||
if self.is_empty() {
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
Poll::Ready(None)
|
Pin::new(self.get_mut().as_mut()).poll_next(cx)
|
||||||
} else {
|
|
||||||
Poll::Ready(Some(Ok(Bytes::from_static(
|
|
||||||
mem::take(self.get_mut()).as_ref(),
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl MessageBody for Vec<u8> {
|
impl<B> MessageBody for Pin<Box<B>>
|
||||||
type Error = Infallible;
|
where
|
||||||
|
B: MessageBody,
|
||||||
|
{
|
||||||
|
type Error = B::Error;
|
||||||
|
|
||||||
fn size(&self) -> BodySize {
|
#[inline]
|
||||||
BodySize::Sized(self.len() as u64)
|
fn size(&self) -> BodySize {
|
||||||
}
|
self.as_ref().size()
|
||||||
|
}
|
||||||
|
|
||||||
fn poll_next(
|
#[inline]
|
||||||
self: Pin<&mut Self>,
|
fn poll_next(
|
||||||
_: &mut Context<'_>,
|
mut self: Pin<&mut Self>,
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
cx: &mut Context<'_>,
|
||||||
if self.is_empty() {
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
Poll::Ready(None)
|
self.as_mut().poll_next(cx)
|
||||||
} else {
|
|
||||||
Poll::Ready(Some(Ok(Bytes::from(mem::take(self.get_mut())))))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl MessageBody for String {
|
impl MessageBody for &'static [u8] {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
BodySize::Sized(self.len() as u64)
|
BodySize::Sized(self.len() as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
_cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
if self.is_empty() {
|
||||||
|
Poll::Ready(None)
|
||||||
|
} else {
|
||||||
|
let bytes = mem::take(self.get_mut());
|
||||||
|
let bytes = Bytes::from_static(bytes);
|
||||||
|
Poll::Ready(Some(Ok(bytes)))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_next(
|
impl MessageBody for Bytes {
|
||||||
self: Pin<&mut Self>,
|
type Error = Infallible;
|
||||||
_: &mut Context<'_>,
|
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
fn size(&self) -> BodySize {
|
||||||
if self.is_empty() {
|
BodySize::Sized(self.len() as u64)
|
||||||
Poll::Ready(None)
|
}
|
||||||
} else {
|
|
||||||
Poll::Ready(Some(Ok(Bytes::from(
|
fn poll_next(
|
||||||
mem::take(self.get_mut()).into_bytes(),
|
self: Pin<&mut Self>,
|
||||||
))))
|
_cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
if self.is_empty() {
|
||||||
|
Poll::Ready(None)
|
||||||
|
} else {
|
||||||
|
let bytes = mem::take(self.get_mut());
|
||||||
|
Poll::Ready(Some(Ok(bytes)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBody for BytesMut {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn size(&self) -> BodySize {
|
||||||
|
BodySize::Sized(self.len() as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
_cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
if self.is_empty() {
|
||||||
|
Poll::Ready(None)
|
||||||
|
} else {
|
||||||
|
let bytes = mem::take(self.get_mut()).freeze();
|
||||||
|
Poll::Ready(Some(Ok(bytes)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBody for Vec<u8> {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn size(&self) -> BodySize {
|
||||||
|
BodySize::Sized(self.len() as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
_cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
if self.is_empty() {
|
||||||
|
Poll::Ready(None)
|
||||||
|
} else {
|
||||||
|
let bytes = mem::take(self.get_mut());
|
||||||
|
Poll::Ready(Some(Ok(Bytes::from(bytes))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBody for &'static str {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn size(&self) -> BodySize {
|
||||||
|
BodySize::Sized(self.len() as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
_cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
if self.is_empty() {
|
||||||
|
Poll::Ready(None)
|
||||||
|
} else {
|
||||||
|
let string = mem::take(self.get_mut());
|
||||||
|
let bytes = Bytes::from_static(string.as_bytes());
|
||||||
|
Poll::Ready(Some(Ok(bytes)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBody for String {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn size(&self) -> BodySize {
|
||||||
|
BodySize::Sized(self.len() as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
_cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
if self.is_empty() {
|
||||||
|
Poll::Ready(None)
|
||||||
|
} else {
|
||||||
|
let string = mem::take(self.get_mut());
|
||||||
|
Poll::Ready(Some(Ok(Bytes::from(string))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBody for bytestring::ByteString {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn size(&self) -> BodySize {
|
||||||
|
BodySize::Sized(self.len() as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
_cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
let string = mem::take(self.get_mut());
|
||||||
|
Poll::Ready(Some(Ok(string.into_bytes())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,6 +272,7 @@ impl<B, F, E> MessageBody for MessageBodyMapErr<B, F>
|
||||||
where
|
where
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
F: FnOnce(B::Error) -> E,
|
F: FnOnce(B::Error) -> E,
|
||||||
|
E: Into<Box<dyn StdError>>,
|
||||||
{
|
{
|
||||||
type Error = E;
|
type Error = E;
|
||||||
|
|
||||||
|
@ -226,3 +297,129 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use actix_rt::pin;
|
||||||
|
use actix_utils::future::poll_fn;
|
||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
macro_rules! assert_poll_next {
|
||||||
|
($pin:expr, $exp:expr) => {
|
||||||
|
assert_eq!(
|
||||||
|
poll_fn(|cx| $pin.as_mut().poll_next(cx))
|
||||||
|
.await
|
||||||
|
.unwrap() // unwrap option
|
||||||
|
.unwrap(), // unwrap result
|
||||||
|
$exp
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_poll_next_none {
|
||||||
|
($pin:expr) => {
|
||||||
|
assert!(poll_fn(|cx| $pin.as_mut().poll_next(cx)).await.is_none());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn boxing_equivalence() {
|
||||||
|
assert_eq!(().size(), BodySize::Sized(0));
|
||||||
|
assert_eq!(().size(), Box::new(()).size());
|
||||||
|
assert_eq!(().size(), Box::pin(()).size());
|
||||||
|
|
||||||
|
let pl = Box::new(());
|
||||||
|
pin!(pl);
|
||||||
|
assert_poll_next_none!(pl);
|
||||||
|
|
||||||
|
let mut pl = Box::pin(());
|
||||||
|
assert_poll_next_none!(pl);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_unit() {
|
||||||
|
let pl = ();
|
||||||
|
assert_eq!(pl.size(), BodySize::Sized(0));
|
||||||
|
pin!(pl);
|
||||||
|
assert_poll_next_none!(pl);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_static_str() {
|
||||||
|
assert_eq!("".size(), BodySize::Sized(0));
|
||||||
|
assert_eq!("test".size(), BodySize::Sized(4));
|
||||||
|
|
||||||
|
let pl = "test";
|
||||||
|
pin!(pl);
|
||||||
|
assert_poll_next!(pl, Bytes::from("test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_static_bytes() {
|
||||||
|
assert_eq!(b"".as_ref().size(), BodySize::Sized(0));
|
||||||
|
assert_eq!(b"test".as_ref().size(), BodySize::Sized(4));
|
||||||
|
|
||||||
|
let pl = b"test".as_ref();
|
||||||
|
pin!(pl);
|
||||||
|
assert_poll_next!(pl, Bytes::from("test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_vec() {
|
||||||
|
assert_eq!(vec![0; 0].size(), BodySize::Sized(0));
|
||||||
|
assert_eq!(Vec::from("test").size(), BodySize::Sized(4));
|
||||||
|
|
||||||
|
let pl = Vec::from("test");
|
||||||
|
pin!(pl);
|
||||||
|
assert_poll_next!(pl, Bytes::from("test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_bytes() {
|
||||||
|
assert_eq!(Bytes::new().size(), BodySize::Sized(0));
|
||||||
|
assert_eq!(Bytes::from_static(b"test").size(), BodySize::Sized(4));
|
||||||
|
|
||||||
|
let pl = Bytes::from_static(b"test");
|
||||||
|
pin!(pl);
|
||||||
|
assert_poll_next!(pl, Bytes::from("test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_bytes_mut() {
|
||||||
|
assert_eq!(BytesMut::new().size(), BodySize::Sized(0));
|
||||||
|
assert_eq!(BytesMut::from(b"test".as_ref()).size(), BodySize::Sized(4));
|
||||||
|
|
||||||
|
let pl = BytesMut::from("test");
|
||||||
|
pin!(pl);
|
||||||
|
assert_poll_next!(pl, Bytes::from("test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_string() {
|
||||||
|
assert_eq!(String::new().size(), BodySize::Sized(0));
|
||||||
|
assert_eq!("test".to_owned().size(), BodySize::Sized(4));
|
||||||
|
|
||||||
|
let pl = "test".to_owned();
|
||||||
|
pin!(pl);
|
||||||
|
assert_poll_next!(pl, Bytes::from("test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// down-casting used to be done with a method on MessageBody trait
|
||||||
|
// test is kept to demonstrate equivalence of Any trait
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_body_casting() {
|
||||||
|
let mut body = String::from("hello cast");
|
||||||
|
// let mut resp_body: &mut dyn MessageBody<Error = Error> = &mut body;
|
||||||
|
let resp_body: &mut dyn std::any::Any = &mut body;
|
||||||
|
let body = resp_body.downcast_ref::<String>().unwrap();
|
||||||
|
assert_eq!(body, "hello cast");
|
||||||
|
let body = &mut resp_body.downcast_mut::<String>().unwrap();
|
||||||
|
body.push('!');
|
||||||
|
let body = resp_body.downcast_ref::<String>().unwrap();
|
||||||
|
assert_eq!(body, "hello cast!");
|
||||||
|
let not_body = resp_body.downcast_ref::<()>();
|
||||||
|
assert!(not_body.is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,272 +1,20 @@
|
||||||
//! Traits and structures to aid consuming and writing HTTP payloads.
|
//! Traits and structures to aid consuming and writing HTTP payloads.
|
||||||
|
|
||||||
use std::task::Poll;
|
|
||||||
|
|
||||||
use actix_rt::pin;
|
|
||||||
use actix_utils::future::poll_fn;
|
|
||||||
use bytes::{Bytes, BytesMut};
|
|
||||||
use futures_core::ready;
|
|
||||||
|
|
||||||
#[allow(clippy::module_inception)]
|
|
||||||
mod body;
|
|
||||||
mod body_stream;
|
mod body_stream;
|
||||||
|
mod boxed;
|
||||||
|
mod either;
|
||||||
mod message_body;
|
mod message_body;
|
||||||
|
mod none;
|
||||||
mod size;
|
mod size;
|
||||||
mod sized_stream;
|
mod sized_stream;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
#[allow(deprecated)]
|
|
||||||
pub use self::body::{AnyBody, Body, BoxBody};
|
|
||||||
pub use self::body_stream::BodyStream;
|
pub use self::body_stream::BodyStream;
|
||||||
|
pub use self::boxed::BoxBody;
|
||||||
|
pub use self::either::EitherBody;
|
||||||
pub use self::message_body::MessageBody;
|
pub use self::message_body::MessageBody;
|
||||||
pub(crate) use self::message_body::MessageBodyMapErr;
|
pub(crate) use self::message_body::MessageBodyMapErr;
|
||||||
|
pub use self::none::None;
|
||||||
pub use self::size::BodySize;
|
pub use self::size::BodySize;
|
||||||
pub use self::sized_stream::SizedStream;
|
pub use self::sized_stream::SizedStream;
|
||||||
|
pub use self::utils::to_bytes;
|
||||||
/// Collects the body produced by a `MessageBody` implementation into `Bytes`.
|
|
||||||
///
|
|
||||||
/// Any errors produced by the body stream are returned immediately.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use actix_http::body::{AnyBody, to_bytes};
|
|
||||||
/// use bytes::Bytes;
|
|
||||||
///
|
|
||||||
/// # async fn test_to_bytes() {
|
|
||||||
/// let body = AnyBody::none();
|
|
||||||
/// let bytes = to_bytes(body).await.unwrap();
|
|
||||||
/// assert!(bytes.is_empty());
|
|
||||||
///
|
|
||||||
/// let body = AnyBody::copy_from_slice(b"123");
|
|
||||||
/// let bytes = to_bytes(body).await.unwrap();
|
|
||||||
/// assert_eq!(bytes, b"123"[..]);
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub async fn to_bytes<B: MessageBody>(body: B) -> Result<Bytes, B::Error> {
|
|
||||||
let cap = match body.size() {
|
|
||||||
BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()),
|
|
||||||
BodySize::Sized(size) => size as usize,
|
|
||||||
// good enough first guess for chunk size
|
|
||||||
BodySize::Stream => 32_768,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut buf = BytesMut::with_capacity(cap);
|
|
||||||
|
|
||||||
pin!(body);
|
|
||||||
|
|
||||||
poll_fn(|cx| loop {
|
|
||||||
let body = body.as_mut();
|
|
||||||
|
|
||||||
match ready!(body.poll_next(cx)) {
|
|
||||||
Some(Ok(bytes)) => buf.extend_from_slice(&*bytes),
|
|
||||||
None => return Poll::Ready(Ok(())),
|
|
||||||
Some(Err(err)) => return Poll::Ready(Err(err)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(buf.freeze())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use actix_rt::pin;
|
|
||||||
use actix_utils::future::poll_fn;
|
|
||||||
use bytes::{Bytes, BytesMut};
|
|
||||||
|
|
||||||
use super::{to_bytes, AnyBody as TestAnyBody, BodySize, MessageBody as _};
|
|
||||||
|
|
||||||
impl AnyBody {
|
|
||||||
pub(crate) fn get_ref(&self) -> &[u8] {
|
|
||||||
match *self {
|
|
||||||
AnyBody::Bytes(ref bin) => bin,
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// AnyBody alias because rustc does not (can not?) infer the default type parameter.
|
|
||||||
type AnyBody = TestAnyBody;
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_static_str() {
|
|
||||||
assert_eq!(AnyBody::from("").size(), BodySize::Sized(0));
|
|
||||||
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!(
|
|
||||||
poll_fn(|cx| Pin::new(&mut "test").poll_next(cx))
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.ok(),
|
|
||||||
Some(Bytes::from("test"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
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()).get_ref(), b"test");
|
|
||||||
assert_eq!(
|
|
||||||
AnyBody::copy_from_slice(b"test".as_ref()).size(),
|
|
||||||
BodySize::Sized(4)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
AnyBody::copy_from_slice(b"test".as_ref()).get_ref(),
|
|
||||||
b"test"
|
|
||||||
);
|
|
||||||
let sb = Bytes::from(&b"test"[..]);
|
|
||||||
pin!(sb);
|
|
||||||
|
|
||||||
assert_eq!(sb.size(), BodySize::Sized(4));
|
|
||||||
assert_eq!(
|
|
||||||
poll_fn(|cx| sb.as_mut().poll_next(cx)).await.unwrap().ok(),
|
|
||||||
Some(Bytes::from("test"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_vec() {
|
|
||||||
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");
|
|
||||||
pin!(test_vec);
|
|
||||||
|
|
||||||
assert_eq!(test_vec.size(), BodySize::Sized(4));
|
|
||||||
assert_eq!(
|
|
||||||
poll_fn(|cx| test_vec.as_mut().poll_next(cx))
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.ok(),
|
|
||||||
Some(Bytes::from("test"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_bytes() {
|
|
||||||
let b = Bytes::from("test");
|
|
||||||
assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4));
|
|
||||||
assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test");
|
|
||||||
pin!(b);
|
|
||||||
|
|
||||||
assert_eq!(b.size(), BodySize::Sized(4));
|
|
||||||
assert_eq!(
|
|
||||||
poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(),
|
|
||||||
Some(Bytes::from("test"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_bytes_mut() {
|
|
||||||
let b = BytesMut::from("test");
|
|
||||||
assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4));
|
|
||||||
assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test");
|
|
||||||
pin!(b);
|
|
||||||
|
|
||||||
assert_eq!(b.size(), BodySize::Sized(4));
|
|
||||||
assert_eq!(
|
|
||||||
poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(),
|
|
||||||
Some(Bytes::from("test"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_string() {
|
|
||||||
let b = "test".to_owned();
|
|
||||||
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).get_ref(), b"test");
|
|
||||||
pin!(b);
|
|
||||||
|
|
||||||
assert_eq!(b.size(), BodySize::Sized(4));
|
|
||||||
assert_eq!(
|
|
||||||
poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(),
|
|
||||||
Some(Bytes::from("test"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_unit() {
|
|
||||||
assert_eq!(().size(), BodySize::Sized(0));
|
|
||||||
assert!(poll_fn(|cx| Pin::new(&mut ()).poll_next(cx))
|
|
||||||
.await
|
|
||||||
.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_box_and_pin() {
|
|
||||||
let val = Box::new(());
|
|
||||||
pin!(val);
|
|
||||||
assert_eq!(val.size(), BodySize::Sized(0));
|
|
||||||
assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none());
|
|
||||||
|
|
||||||
let mut val = Box::pin(());
|
|
||||||
assert_eq!(val.size(), BodySize::Sized(0));
|
|
||||||
assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_body_eq() {
|
|
||||||
assert!(
|
|
||||||
AnyBody::Bytes(Bytes::from_static(b"1"))
|
|
||||||
== AnyBody::Bytes(Bytes::from_static(b"1"))
|
|
||||||
);
|
|
||||||
assert!(AnyBody::Bytes(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]
|
|
||||||
async fn test_serde_json() {
|
|
||||||
use serde_json::{json, Value};
|
|
||||||
assert_eq!(
|
|
||||||
AnyBody::from(
|
|
||||||
serde_json::to_vec(&Value::String("test".to_owned())).unwrap()
|
|
||||||
)
|
|
||||||
.size(),
|
|
||||||
BodySize::Sized(6)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
AnyBody::from(
|
|
||||||
serde_json::to_vec(&json!({"test-key":"test-value"})).unwrap()
|
|
||||||
)
|
|
||||||
.size(),
|
|
||||||
BodySize::Sized(25)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// down-casting used to be done with a method on MessageBody trait
|
|
||||||
// test is kept to demonstrate equivalence of Any trait
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_body_casting() {
|
|
||||||
let mut body = String::from("hello cast");
|
|
||||||
// let mut resp_body: &mut dyn MessageBody<Error = Error> = &mut body;
|
|
||||||
let resp_body: &mut dyn std::any::Any = &mut body;
|
|
||||||
let body = resp_body.downcast_ref::<String>().unwrap();
|
|
||||||
assert_eq!(body, "hello cast");
|
|
||||||
let body = &mut resp_body.downcast_mut::<String>().unwrap();
|
|
||||||
body.push('!');
|
|
||||||
let body = resp_body.downcast_ref::<String>().unwrap();
|
|
||||||
assert_eq!(body, "hello cast!");
|
|
||||||
let not_body = resp_body.downcast_ref::<()>();
|
|
||||||
assert!(not_body.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_to_bytes() {
|
|
||||||
let body = AnyBody::empty();
|
|
||||||
let bytes = to_bytes(body).await.unwrap();
|
|
||||||
assert!(bytes.is_empty());
|
|
||||||
|
|
||||||
let body = AnyBody::copy_from_slice(b"123");
|
|
||||||
let bytes = to_bytes(body).await.unwrap();
|
|
||||||
assert_eq!(bytes, b"123"[..]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
43
actix-http/src/body/none.rs
Normal file
43
actix-http/src/body/none.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
use super::{BodySize, MessageBody};
|
||||||
|
|
||||||
|
/// Body type for responses that forbid payloads.
|
||||||
|
///
|
||||||
|
/// Distinct from an empty response which would contain a Content-Length header.
|
||||||
|
///
|
||||||
|
/// For an "empty" body, use `()` or `Bytes::new()`.
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct None;
|
||||||
|
|
||||||
|
impl None {
|
||||||
|
/// Constructs new "none" body.
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBody for None {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn size(&self) -> BodySize {
|
||||||
|
BodySize::None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll_next(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
_cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
Poll::Ready(Option::None)
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ pub enum BodySize {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BodySize {
|
impl BodySize {
|
||||||
/// Returns true if size hint indicates no or empty body.
|
/// Returns true if size hint indicates omitted or empty body.
|
||||||
///
|
///
|
||||||
/// Streams will return false because it cannot be known without reading the stream.
|
/// Streams will return false because it cannot be known without reading the stream.
|
||||||
///
|
///
|
||||||
|
|
|
@ -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>>,
|
||||||
|
|
78
actix-http/src/body/utils.rs
Normal file
78
actix-http/src/body/utils.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use std::task::Poll;
|
||||||
|
|
||||||
|
use actix_rt::pin;
|
||||||
|
use actix_utils::future::poll_fn;
|
||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
|
use futures_core::ready;
|
||||||
|
|
||||||
|
use super::{BodySize, MessageBody};
|
||||||
|
|
||||||
|
/// Collects the body produced by a `MessageBody` implementation into `Bytes`.
|
||||||
|
///
|
||||||
|
/// Any errors produced by the body stream are returned immediately.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// use actix_http::body::{self, to_bytes};
|
||||||
|
/// use bytes::Bytes;
|
||||||
|
///
|
||||||
|
/// # async fn test_to_bytes() {
|
||||||
|
/// let body = body::None::new();
|
||||||
|
/// let bytes = to_bytes(body).await.unwrap();
|
||||||
|
/// assert!(bytes.is_empty());
|
||||||
|
///
|
||||||
|
/// let body = Bytes::from_static(b"123");
|
||||||
|
/// let bytes = to_bytes(body).await.unwrap();
|
||||||
|
/// assert_eq!(bytes, b"123"[..]);
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub async fn to_bytes<B: MessageBody>(body: B) -> Result<Bytes, B::Error> {
|
||||||
|
let cap = match body.size() {
|
||||||
|
BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()),
|
||||||
|
BodySize::Sized(size) => size as usize,
|
||||||
|
// good enough first guess for chunk size
|
||||||
|
BodySize::Stream => 32_768,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buf = BytesMut::with_capacity(cap);
|
||||||
|
|
||||||
|
pin!(body);
|
||||||
|
|
||||||
|
poll_fn(|cx| loop {
|
||||||
|
let body = body.as_mut();
|
||||||
|
|
||||||
|
match ready!(body.poll_next(cx)) {
|
||||||
|
Some(Ok(bytes)) => buf.extend_from_slice(&*bytes),
|
||||||
|
None => return Poll::Ready(Ok(())),
|
||||||
|
Some(Err(err)) => return Poll::Ready(Err(err)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(buf.freeze())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use futures_util::{stream, StreamExt as _};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{body::BodyStream, Error};
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_to_bytes() {
|
||||||
|
let bytes = to_bytes(()).await.unwrap();
|
||||||
|
assert!(bytes.is_empty());
|
||||||
|
|
||||||
|
let body = Bytes::from_static(b"123");
|
||||||
|
let bytes = to_bytes(body).await.unwrap();
|
||||||
|
assert_eq!(bytes, b"123"[..]);
|
||||||
|
|
||||||
|
let stream =
|
||||||
|
stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")])
|
||||||
|
.map(Ok::<_, Error>);
|
||||||
|
let body = BodyStream::new(stream);
|
||||||
|
let bytes = to_bytes(body).await.unwrap();
|
||||||
|
assert_eq!(bytes, b"123abc"[..]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
use std::{error::Error as StdError, fmt, marker::PhantomData, net, rc::Rc};
|
use std::{fmt, marker::PhantomData, net, rc::Rc};
|
||||||
|
|
||||||
use actix_codec::Framed;
|
use actix_codec::Framed;
|
||||||
use actix_service::{IntoServiceFactory, Service, ServiceFactory};
|
use actix_service::{IntoServiceFactory, Service, ServiceFactory};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{AnyBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
config::{KeepAlive, ServiceConfig},
|
config::{KeepAlive, ServiceConfig},
|
||||||
h1::{self, ExpectHandler, H1Service, UpgradeHandler},
|
h1::{self, ExpectHandler, H1Service, UpgradeHandler},
|
||||||
h2::H2Service,
|
h2::H2Service,
|
||||||
|
@ -31,7 +31,7 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
|
||||||
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
|
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
{
|
{
|
||||||
|
@ -54,11 +54,11 @@ where
|
||||||
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
|
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
|
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
|
@ -120,7 +120,7 @@ where
|
||||||
where
|
where
|
||||||
F: IntoServiceFactory<X1, Request>,
|
F: IntoServiceFactory<X1, Request>,
|
||||||
X1: ServiceFactory<Request, Config = (), Response = Request>,
|
X1: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X1::Error: Into<Response<AnyBody>>,
|
X1::Error: Into<Response<BoxBody>>,
|
||||||
X1::InitError: fmt::Debug,
|
X1::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
HttpServiceBuilder {
|
HttpServiceBuilder {
|
||||||
|
@ -178,7 +178,7 @@ where
|
||||||
where
|
where
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
F: IntoServiceFactory<S, Request>,
|
F: IntoServiceFactory<S, Request>,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
{
|
{
|
||||||
|
@ -200,12 +200,11 @@ where
|
||||||
pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
|
pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
|
||||||
where
|
where
|
||||||
F: IntoServiceFactory<S, Request>,
|
F: IntoServiceFactory<S, Request>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
let cfg = ServiceConfig::new(
|
let cfg = ServiceConfig::new(
|
||||||
self.keep_alive,
|
self.keep_alive,
|
||||||
|
@ -223,12 +222,11 @@ where
|
||||||
pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U>
|
pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
F: IntoServiceFactory<S, Request>,
|
F: IntoServiceFactory<S, Request>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
let cfg = ServiceConfig::new(
|
let cfg = ServiceConfig::new(
|
||||||
self.keep_alive,
|
self.keep_alive,
|
||||||
|
|
|
@ -12,7 +12,7 @@ use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use derive_more::Display;
|
use derive_more::Display;
|
||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use pin_project::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
use brotli2::write::BrotliEncoder;
|
use brotli2::write::BrotliEncoder;
|
||||||
|
@ -23,8 +23,10 @@ use flate2::write::{GzEncoder, ZlibEncoder};
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
use zstd::stream::write::Encoder as ZstdEncoder;
|
use zstd::stream::write::Encoder as ZstdEncoder;
|
||||||
|
|
||||||
|
use super::Writer;
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{AnyBody, BodySize, MessageBody},
|
body::{BodySize, MessageBody},
|
||||||
|
error::BlockingError,
|
||||||
http::{
|
http::{
|
||||||
header::{ContentEncoding, CONTENT_ENCODING},
|
header::{ContentEncoding, CONTENT_ENCODING},
|
||||||
HeaderValue, StatusCode,
|
HeaderValue, StatusCode,
|
||||||
|
@ -32,84 +34,92 @@ use crate::{
|
||||||
ResponseHead,
|
ResponseHead,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Writer;
|
|
||||||
use crate::error::BlockingError;
|
|
||||||
|
|
||||||
const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024;
|
const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024;
|
||||||
|
|
||||||
#[pin_project]
|
pin_project! {
|
||||||
pub struct Encoder<B> {
|
pub struct Encoder<B> {
|
||||||
eof: bool,
|
#[pin]
|
||||||
#[pin]
|
body: EncoderBody<B>,
|
||||||
body: EncoderBody<B>,
|
encoder: Option<ContentEncoder>,
|
||||||
encoder: Option<ContentEncoder>,
|
fut: Option<JoinHandle<Result<ContentEncoder, io::Error>>>,
|
||||||
fut: Option<JoinHandle<Result<ContentEncoder, io::Error>>>,
|
eof: bool,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: MessageBody> Encoder<B> {
|
impl<B: MessageBody> Encoder<B> {
|
||||||
|
fn none() -> Self {
|
||||||
|
Encoder {
|
||||||
|
body: EncoderBody::None,
|
||||||
|
encoder: None,
|
||||||
|
fut: None,
|
||||||
|
eof: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn response(
|
pub fn response(
|
||||||
encoding: ContentEncoding,
|
encoding: ContentEncoding,
|
||||||
head: &mut ResponseHead,
|
head: &mut ResponseHead,
|
||||||
body: AnyBody<B>,
|
body: B,
|
||||||
) -> AnyBody<Encoder<B>> {
|
) -> Self {
|
||||||
let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|
let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|
||||||
|| head.status == StatusCode::SWITCHING_PROTOCOLS
|
|| head.status == StatusCode::SWITCHING_PROTOCOLS
|
||||||
|| head.status == StatusCode::NO_CONTENT
|
|| head.status == StatusCode::NO_CONTENT
|
||||||
|| encoding == ContentEncoding::Identity
|
|| encoding == ContentEncoding::Identity
|
||||||
|| encoding == ContentEncoding::Auto);
|
|| encoding == ContentEncoding::Auto);
|
||||||
|
|
||||||
let body = match body {
|
match body.size() {
|
||||||
AnyBody::None => return AnyBody::None,
|
// no need to compress an empty body
|
||||||
AnyBody::Bytes(buf) => {
|
BodySize::None => return Self::none(),
|
||||||
if can_encode {
|
|
||||||
EncoderBody::Bytes(buf)
|
// we cannot assume that Sized is not a stream
|
||||||
} else {
|
BodySize::Sized(_) | BodySize::Stream => {}
|
||||||
return AnyBody::Bytes(buf);
|
}
|
||||||
}
|
|
||||||
}
|
// TODO potentially some optimisation for single-chunk responses here by trying to read the
|
||||||
AnyBody::Body(body) => EncoderBody::Stream(body),
|
// 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 not None
|
// Modify response body only if encoder is set
|
||||||
if let Some(enc) = ContentEncoder::encoder(encoding) {
|
if let Some(enc) = ContentEncoder::encoder(encoding) {
|
||||||
update_head(encoding, head);
|
update_head(encoding, head);
|
||||||
head.no_chunking(false);
|
head.no_chunking(false);
|
||||||
|
|
||||||
return AnyBody::Body(Encoder {
|
return Encoder {
|
||||||
body,
|
body: EncoderBody::Stream { body },
|
||||||
eof: false,
|
|
||||||
fut: None,
|
|
||||||
encoder: Some(enc),
|
encoder: Some(enc),
|
||||||
});
|
fut: None,
|
||||||
|
eof: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnyBody::Body(Encoder {
|
Encoder {
|
||||||
body,
|
body: EncoderBody::Stream { body },
|
||||||
eof: false,
|
|
||||||
fut: None,
|
|
||||||
encoder: None,
|
encoder: None,
|
||||||
})
|
fut: None,
|
||||||
|
eof: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project(project = EncoderBodyProj)]
|
pin_project! {
|
||||||
enum EncoderBody<B> {
|
#[project = EncoderBodyProj]
|
||||||
Bytes(Bytes),
|
enum EncoderBody<B> {
|
||||||
Stream(#[pin] B),
|
None,
|
||||||
|
Stream { #[pin] body: B },
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B> MessageBody for EncoderBody<B>
|
impl<B> MessageBody for EncoderBody<B>
|
||||||
where
|
where
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
type Error = EncoderError<B::Error>;
|
type Error = EncoderError;
|
||||||
|
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
match self {
|
match self {
|
||||||
EncoderBody::Bytes(ref b) => b.size(),
|
EncoderBody::None => BodySize::None,
|
||||||
EncoderBody::Stream(ref b) => b.size(),
|
EncoderBody::Stream { body } => body.size(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,14 +128,11 @@ where
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
match self.project() {
|
match self.project() {
|
||||||
EncoderBodyProj::Bytes(b) => {
|
EncoderBodyProj::None => Poll::Ready(None),
|
||||||
if b.is_empty() {
|
|
||||||
Poll::Ready(None)
|
EncoderBodyProj::Stream { body } => body
|
||||||
} else {
|
.poll_next(cx)
|
||||||
Poll::Ready(Some(Ok(std::mem::take(b))))
|
.map_err(|err| EncoderError::Body(err.into())),
|
||||||
}
|
|
||||||
}
|
|
||||||
EncoderBodyProj::Stream(b) => b.poll_next(cx).map_err(EncoderError::Body),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,7 +141,7 @@ impl<B> MessageBody for Encoder<B>
|
||||||
where
|
where
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
type Error = EncoderError<B::Error>;
|
type Error = EncoderError;
|
||||||
|
|
||||||
fn size(&self) -> BodySize {
|
fn size(&self) -> BodySize {
|
||||||
if self.encoder.is_none() {
|
if self.encoder.is_none() {
|
||||||
|
@ -197,6 +204,7 @@ where
|
||||||
None => {
|
None => {
|
||||||
if let Some(encoder) = this.encoder.take() {
|
if let Some(encoder) = this.encoder.take() {
|
||||||
let chunk = encoder.finish().map_err(EncoderError::Io)?;
|
let chunk = encoder.finish().map_err(EncoderError::Io)?;
|
||||||
|
|
||||||
if chunk.is_empty() {
|
if chunk.is_empty() {
|
||||||
return Poll::Ready(None);
|
return Poll::Ready(None);
|
||||||
} else {
|
} else {
|
||||||
|
@ -222,12 +230,15 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
|
||||||
enum ContentEncoder {
|
enum ContentEncoder {
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
Deflate(ZlibEncoder<Writer>),
|
Deflate(ZlibEncoder<Writer>),
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
Gzip(GzEncoder<Writer>),
|
Gzip(GzEncoder<Writer>),
|
||||||
|
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
Br(BrotliEncoder<Writer>),
|
Br(BrotliEncoder<Writer>),
|
||||||
// We need explicit 'static lifetime here because ZstdEncoder need lifetime
|
|
||||||
// argument, and we use `spawn_blocking` in `Encoder::poll_next` that require `FnOnce() -> R + Send + 'static`
|
// Wwe need explicit 'static lifetime here because ZstdEncoder needs a lifetime argument and we
|
||||||
|
// use `spawn_blocking` in `Encoder::poll_next` that requires `FnOnce() -> R + Send + 'static`.
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
Zstd(ZstdEncoder<'static, Writer>),
|
Zstd(ZstdEncoder<'static, Writer>),
|
||||||
}
|
}
|
||||||
|
@ -240,20 +251,24 @@ impl ContentEncoder {
|
||||||
Writer::new(),
|
Writer::new(),
|
||||||
flate2::Compression::fast(),
|
flate2::Compression::fast(),
|
||||||
))),
|
))),
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new(
|
ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new(
|
||||||
Writer::new(),
|
Writer::new(),
|
||||||
flate2::Compression::fast(),
|
flate2::Compression::fast(),
|
||||||
))),
|
))),
|
||||||
|
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
ContentEncoding::Br => {
|
ContentEncoding::Br => {
|
||||||
Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3)))
|
Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
ContentEncoding::Zstd => {
|
ContentEncoding::Zstd => {
|
||||||
let encoder = ZstdEncoder::new(Writer::new(), 3).ok()?;
|
let encoder = ZstdEncoder::new(Writer::new(), 3).ok()?;
|
||||||
Some(ContentEncoder::Zstd(encoder))
|
Some(ContentEncoder::Zstd(encoder))
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,10 +278,13 @@ impl ContentEncoder {
|
||||||
match *self {
|
match *self {
|
||||||
#[cfg(feature = "compress-brotli")]
|
#[cfg(feature = "compress-brotli")]
|
||||||
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
|
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
|
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
|
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
|
||||||
|
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
ContentEncoder::Zstd(ref mut encoder) => encoder.get_mut().take(),
|
ContentEncoder::Zstd(ref mut encoder) => encoder.get_mut().take(),
|
||||||
}
|
}
|
||||||
|
@ -279,16 +297,19 @@ impl ContentEncoder {
|
||||||
Ok(writer) => Ok(writer.buf.freeze()),
|
Ok(writer) => Ok(writer.buf.freeze()),
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoder::Gzip(encoder) => match encoder.finish() {
|
ContentEncoder::Gzip(encoder) => match encoder.finish() {
|
||||||
Ok(writer) => Ok(writer.buf.freeze()),
|
Ok(writer) => Ok(writer.buf.freeze()),
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoder::Deflate(encoder) => match encoder.finish() {
|
ContentEncoder::Deflate(encoder) => match encoder.finish() {
|
||||||
Ok(writer) => Ok(writer.buf.freeze()),
|
Ok(writer) => Ok(writer.buf.freeze()),
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
ContentEncoder::Zstd(encoder) => match encoder.finish() {
|
ContentEncoder::Zstd(encoder) => match encoder.finish() {
|
||||||
Ok(writer) => Ok(writer.buf.freeze()),
|
Ok(writer) => Ok(writer.buf.freeze()),
|
||||||
|
@ -307,6 +328,7 @@ impl ContentEncoder {
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
|
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
|
@ -315,6 +337,7 @@ impl ContentEncoder {
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
|
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
|
@ -323,6 +346,7 @@ impl ContentEncoder {
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) {
|
ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
|
@ -337,9 +361,9 @@ impl ContentEncoder {
|
||||||
|
|
||||||
#[derive(Debug, Display)]
|
#[derive(Debug, Display)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum EncoderError<E> {
|
pub enum EncoderError {
|
||||||
#[display(fmt = "body")]
|
#[display(fmt = "body")]
|
||||||
Body(E),
|
Body(Box<dyn StdError>),
|
||||||
|
|
||||||
#[display(fmt = "blocking")]
|
#[display(fmt = "blocking")]
|
||||||
Blocking(BlockingError),
|
Blocking(BlockingError),
|
||||||
|
@ -348,18 +372,18 @@ pub enum EncoderError<E> {
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: StdError + 'static> StdError for EncoderError<E> {
|
impl StdError for EncoderError {
|
||||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
EncoderError::Body(err) => Some(err),
|
EncoderError::Body(err) => Some(&**err),
|
||||||
EncoderError::Blocking(err) => Some(err),
|
EncoderError::Blocking(err) => Some(err),
|
||||||
EncoderError::Io(err) => Some(err),
|
EncoderError::Io(err) => Some(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: StdError + 'static> From<EncoderError<E>> for crate::Error {
|
impl From<EncoderError> for crate::Error {
|
||||||
fn from(err: EncoderError<E>) -> Self {
|
fn from(err: EncoderError) -> Self {
|
||||||
crate::Error::new_encoder().with_cause(err)
|
crate::Error::new_encoder().with_cause(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Err
|
||||||
use derive_more::{Display, Error, From};
|
use derive_more::{Display, Error, From};
|
||||||
use http::{uri::InvalidUri, StatusCode};
|
use http::{uri::InvalidUri, StatusCode};
|
||||||
|
|
||||||
use crate::{body::AnyBody, ws, Response};
|
use crate::{body::BoxBody, ws, Response};
|
||||||
|
|
||||||
pub use http::Error as HttpError;
|
pub use http::Error as HttpError;
|
||||||
|
|
||||||
|
@ -66,14 +66,15 @@ impl Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B> From<Error> for Response<AnyBody<B>> {
|
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,
|
||||||
};
|
};
|
||||||
|
|
||||||
Response::new(status_code).set_body(AnyBody::from(err.to_string()))
|
Response::new(status_code).set_body(BoxBody::new(err.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,12 +133,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 +145,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]
|
||||||
|
@ -240,7 +241,7 @@ impl From<ParseError> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ParseError> for Response<AnyBody> {
|
impl From<ParseError> for Response<BoxBody> {
|
||||||
fn from(err: ParseError) -> Self {
|
fn from(err: ParseError) -> Self {
|
||||||
Error::from(err).into()
|
Error::from(err).into()
|
||||||
}
|
}
|
||||||
|
@ -337,7 +338,7 @@ pub enum DispatchError {
|
||||||
/// Service error
|
/// Service error
|
||||||
// FIXME: display and error type
|
// FIXME: display and error type
|
||||||
#[display(fmt = "Service Error")]
|
#[display(fmt = "Service Error")]
|
||||||
Service(#[error(not(source))] Response<AnyBody>),
|
Service(#[error(not(source))] Response<BoxBody>),
|
||||||
|
|
||||||
/// Body error
|
/// Body error
|
||||||
// FIXME: display and error type
|
// FIXME: display and error type
|
||||||
|
@ -421,11 +422,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_into_response() {
|
fn test_into_response() {
|
||||||
let resp: Response<AnyBody> = ParseError::Incomplete.into();
|
let resp: Response<BoxBody> = ParseError::Incomplete.into();
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
|
|
||||||
let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into();
|
let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into();
|
||||||
let resp: Response<AnyBody> = Error::new_http().with_cause(err).into();
|
let resp: Response<BoxBody> = Error::new_http().with_cause(err).into();
|
||||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -450,7 +451,7 @@ mod tests {
|
||||||
fn test_error_http_response() {
|
fn test_error_http_response() {
|
||||||
let orig = io::Error::new(io::ErrorKind::Other, "other");
|
let orig = io::Error::new(io::ErrorKind::Other, "other");
|
||||||
let err = Error::new_io().with_cause(orig);
|
let err = Error::new_io().with_cause(orig);
|
||||||
let resp: Response<AnyBody> = err.into();
|
let resp: Response<BoxBody> = err.into();
|
||||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
error::Error as StdError,
|
|
||||||
fmt,
|
fmt,
|
||||||
future::Future,
|
future::Future,
|
||||||
io, mem, net,
|
io, mem, net,
|
||||||
|
@ -19,7 +18,7 @@ use log::{error, trace};
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{AnyBody, BodySize, MessageBody},
|
body::{BodySize, BoxBody, MessageBody},
|
||||||
config::ServiceConfig,
|
config::ServiceConfig,
|
||||||
error::{DispatchError, ParseError, PayloadError},
|
error::{DispatchError, ParseError, PayloadError},
|
||||||
service::HttpFlow,
|
service::HttpFlow,
|
||||||
|
@ -51,13 +50,12 @@ bitflags! {
|
||||||
pub struct Dispatcher<T, S, B, X, U>
|
pub struct Dispatcher<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
|
@ -73,13 +71,12 @@ where
|
||||||
enum DispatcherState<T, S, B, X, U>
|
enum DispatcherState<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
|
@ -92,13 +89,12 @@ where
|
||||||
struct InnerDispatcher<T, S, B, X, U>
|
struct InnerDispatcher<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
|
@ -137,13 +133,12 @@ where
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
ExpectCall(#[pin] X::Future),
|
ExpectCall(#[pin] X::Future),
|
||||||
ServiceCall(#[pin] S::Future),
|
ServiceCall(#[pin] S::Future),
|
||||||
SendPayload(#[pin] B),
|
SendPayload(#[pin] B),
|
||||||
SendErrorPayload(#[pin] AnyBody),
|
SendErrorPayload(#[pin] BoxBody),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, B, X> State<S, B, X>
|
impl<S, B, X> State<S, B, X>
|
||||||
|
@ -153,7 +148,6 @@ where
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
matches!(self, State::None)
|
matches!(self, State::None)
|
||||||
|
@ -171,14 +165,13 @@ where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
|
@ -232,14 +225,13 @@ where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
|
@ -335,7 +327,7 @@ where
|
||||||
fn send_error_response(
|
fn send_error_response(
|
||||||
mut self: Pin<&mut Self>,
|
mut self: Pin<&mut Self>,
|
||||||
message: Response<()>,
|
message: Response<()>,
|
||||||
body: AnyBody,
|
body: BoxBody,
|
||||||
) -> Result<(), DispatchError> {
|
) -> Result<(), DispatchError> {
|
||||||
let size = self.as_mut().send_response_inner(message, &body)?;
|
let size = self.as_mut().send_response_inner(message, &body)?;
|
||||||
let state = match size {
|
let state = match size {
|
||||||
|
@ -380,7 +372,7 @@ where
|
||||||
// send_response would update InnerDispatcher state to SendPayload or
|
// send_response would update InnerDispatcher state to SendPayload or
|
||||||
// None(If response body is empty).
|
// None(If response body is empty).
|
||||||
// continue loop to poll it.
|
// continue loop to poll it.
|
||||||
self.as_mut().send_error_response(res, AnyBody::empty())?;
|
self.as_mut().send_error_response(res, BoxBody::new(()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// return with upgrade request and poll it exclusively.
|
// return with upgrade request and poll it exclusively.
|
||||||
|
@ -400,7 +392,7 @@ where
|
||||||
|
|
||||||
// send service call error as response
|
// send service call error as response
|
||||||
Poll::Ready(Err(err)) => {
|
Poll::Ready(Err(err)) => {
|
||||||
let res: Response<AnyBody> = err.into();
|
let res: Response<BoxBody> = err.into();
|
||||||
let (res, body) = res.replace_body(());
|
let (res, body) = res.replace_body(());
|
||||||
self.as_mut().send_error_response(res, body)?;
|
self.as_mut().send_error_response(res, body)?;
|
||||||
}
|
}
|
||||||
|
@ -497,7 +489,7 @@ where
|
||||||
|
|
||||||
// send expect error as response
|
// send expect error as response
|
||||||
Poll::Ready(Err(err)) => {
|
Poll::Ready(Err(err)) => {
|
||||||
let res: Response<AnyBody> = err.into();
|
let res: Response<BoxBody> = err.into();
|
||||||
let (res, body) = res.replace_body(());
|
let (res, body) = res.replace_body(());
|
||||||
self.as_mut().send_error_response(res, body)?;
|
self.as_mut().send_error_response(res, body)?;
|
||||||
}
|
}
|
||||||
|
@ -546,7 +538,7 @@ where
|
||||||
// to notify the dispatcher a new state is set and the outer loop
|
// to notify the dispatcher a new state is set and the outer loop
|
||||||
// should be continue.
|
// should be continue.
|
||||||
Poll::Ready(Err(err)) => {
|
Poll::Ready(Err(err)) => {
|
||||||
let res: Response<AnyBody> = err.into();
|
let res: Response<BoxBody> = err.into();
|
||||||
let (res, body) = res.replace_body(());
|
let (res, body) = res.replace_body(());
|
||||||
return self.send_error_response(res, body);
|
return self.send_error_response(res, body);
|
||||||
}
|
}
|
||||||
|
@ -566,7 +558,7 @@ where
|
||||||
Poll::Pending => Ok(()),
|
Poll::Pending => Ok(()),
|
||||||
// see the comment on ExpectCall state branch's Ready(Err(err)).
|
// see the comment on ExpectCall state branch's Ready(Err(err)).
|
||||||
Poll::Ready(Err(err)) => {
|
Poll::Ready(Err(err)) => {
|
||||||
let res: Response<AnyBody> = err.into();
|
let res: Response<BoxBody> = err.into();
|
||||||
let (res, body) = res.replace_body(());
|
let (res, body) = res.replace_body(());
|
||||||
self.send_error_response(res, body)
|
self.send_error_response(res, body)
|
||||||
}
|
}
|
||||||
|
@ -772,7 +764,7 @@ where
|
||||||
trace!("Slow request timeout");
|
trace!("Slow request timeout");
|
||||||
let _ = self.as_mut().send_error_response(
|
let _ = self.as_mut().send_error_response(
|
||||||
Response::with_body(StatusCode::REQUEST_TIMEOUT, ()),
|
Response::with_body(StatusCode::REQUEST_TIMEOUT, ()),
|
||||||
AnyBody::empty(),
|
BoxBody::new(()),
|
||||||
);
|
);
|
||||||
this = self.project();
|
this = self.project();
|
||||||
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
|
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
|
||||||
|
@ -909,14 +901,13 @@ where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
|
@ -1067,17 +1058,19 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ok_service() -> impl Service<Request, Response = Response<AnyBody>, 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<AnyBody>, 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>(
|
||||||
Response::ok().set_body(AnyBody::copy_from_slice(path)),
|
Response::ok().set_body(Bytes::copy_from_slice(path)),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::{
|
use std::{
|
||||||
error::Error as StdError,
|
|
||||||
fmt,
|
fmt,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
net,
|
net,
|
||||||
|
@ -16,7 +15,7 @@ use actix_utils::future::ready;
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{AnyBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
config::ServiceConfig,
|
config::ServiceConfig,
|
||||||
error::DispatchError,
|
error::DispatchError,
|
||||||
service::HttpServiceHandler,
|
service::HttpServiceHandler,
|
||||||
|
@ -38,7 +37,7 @@ pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> {
|
||||||
impl<T, S, B> H1Service<T, S, B>
|
impl<T, S, B> H1Service<T, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
|
@ -63,21 +62,20 @@ impl<S, B, X, U> H1Service<TcpStream, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Future: 'static,
|
X::Future: 'static,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
|
|
||||||
U: ServiceFactory<(Request, Framed<TcpStream, Codec>), Config = (), Response = ()>,
|
U: ServiceFactory<(Request, Framed<TcpStream, Codec>), Config = (), Response = ()>,
|
||||||
U::Future: 'static,
|
U::Future: 'static,
|
||||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
/// Create simple tcp stream service
|
/// Create simple tcp stream service
|
||||||
|
@ -114,16 +112,15 @@ mod openssl {
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Future: 'static,
|
X::Future: 'static,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
|
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<
|
||||||
|
@ -132,7 +129,7 @@ mod openssl {
|
||||||
Response = (),
|
Response = (),
|
||||||
>,
|
>,
|
||||||
U::Future: 'static,
|
U::Future: 'static,
|
||||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
/// Create OpenSSL based service.
|
/// Create OpenSSL based service.
|
||||||
|
@ -177,16 +174,15 @@ mod rustls {
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Future: 'static,
|
X::Future: 'static,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
|
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<
|
||||||
|
@ -195,7 +191,7 @@ mod rustls {
|
||||||
Response = (),
|
Response = (),
|
||||||
>,
|
>,
|
||||||
U::Future: 'static,
|
U::Future: 'static,
|
||||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
/// Create Rustls based service.
|
/// Create Rustls based service.
|
||||||
|
@ -226,7 +222,7 @@ mod rustls {
|
||||||
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
|
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
|
@ -234,7 +230,7 @@ where
|
||||||
pub fn expect<X1>(self, expect: X1) -> H1Service<T, S, B, X1, U>
|
pub fn expect<X1>(self, expect: X1) -> H1Service<T, S, B, X1, U>
|
||||||
where
|
where
|
||||||
X1: ServiceFactory<Request, Response = Request>,
|
X1: ServiceFactory<Request, Response = Request>,
|
||||||
X1::Error: Into<Response<AnyBody>>,
|
X1::Error: Into<Response<BoxBody>>,
|
||||||
X1::InitError: fmt::Debug,
|
X1::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
H1Service {
|
H1Service {
|
||||||
|
@ -277,21 +273,20 @@ where
|
||||||
|
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Future: 'static,
|
X::Future: 'static,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
|
|
||||||
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
|
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
|
||||||
U::Future: 'static,
|
U::Future: 'static,
|
||||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
type Response = ();
|
type Response = ();
|
||||||
|
@ -347,17 +342,16 @@ where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||||
{
|
{
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = DispatchError;
|
type Error = DispatchError;
|
||||||
|
|
|
@ -1,22 +1,30 @@
|
||||||
use std::future::Future;
|
use std::{
|
||||||
use std::pin::Pin;
|
future::Future,
|
||||||
use std::task::{Context, Poll};
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use crate::body::{BodySize, MessageBody};
|
use crate::{
|
||||||
use crate::error::Error;
|
body::{BodySize, MessageBody},
|
||||||
use crate::h1::{Codec, Message};
|
error::Error,
|
||||||
use crate::response::Response;
|
h1::{Codec, Message},
|
||||||
|
response::Response,
|
||||||
|
};
|
||||||
|
|
||||||
/// Send HTTP/1 response
|
pin_project! {
|
||||||
#[pin_project::pin_project]
|
/// Send HTTP/1 response
|
||||||
pub struct SendResponse<T, B> {
|
pub struct SendResponse<T, B> {
|
||||||
res: Option<Message<(Response<()>, BodySize)>>,
|
res: Option<Message<(Response<()>, BodySize)>>,
|
||||||
#[pin]
|
|
||||||
body: Option<B>,
|
#[pin]
|
||||||
#[pin]
|
body: Option<B>,
|
||||||
framed: Option<Framed<T, Codec>>,
|
|
||||||
|
#[pin]
|
||||||
|
framed: Option<Framed<T, Codec>>,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, B> SendResponse<T, B>
|
impl<T, B> SendResponse<T, B>
|
||||||
|
|
|
@ -24,7 +24,7 @@ use log::{error, trace};
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{AnyBody, BodySize, MessageBody},
|
body::{BodySize, BoxBody, MessageBody},
|
||||||
config::ServiceConfig,
|
config::ServiceConfig,
|
||||||
service::HttpFlow,
|
service::HttpFlow,
|
||||||
OnConnectData, Payload, Request, Response, ResponseHead,
|
OnConnectData, Payload, Request, Response, ResponseHead,
|
||||||
|
@ -51,7 +51,7 @@ where
|
||||||
{
|
{
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
flow: Rc<HttpFlow<S, X, U>>,
|
flow: Rc<HttpFlow<S, X, U>>,
|
||||||
mut connection: Connection<T, Bytes>,
|
mut conn: Connection<T, Bytes>,
|
||||||
on_connect_data: OnConnectData,
|
on_connect_data: OnConnectData,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
|
@ -66,14 +66,14 @@ where
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| Box::pin(sleep(dur))),
|
.unwrap_or_else(|| Box::pin(sleep(dur))),
|
||||||
on_flight: false,
|
on_flight: false,
|
||||||
ping_pong: connection.ping_pong().unwrap(),
|
ping_pong: conn.ping_pong().unwrap(),
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
flow,
|
flow,
|
||||||
config,
|
config,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
connection,
|
connection: conn,
|
||||||
on_connect_data,
|
on_connect_data,
|
||||||
ping_pong,
|
ping_pong,
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
|
@ -92,12 +92,11 @@ where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>>,
|
S::Response: Into<Response<B>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
type Output = Result<(), crate::error::DispatchError>;
|
type Output = Result<(), crate::error::DispatchError>;
|
||||||
|
|
||||||
|
@ -132,7 +131,7 @@ where
|
||||||
let res = match fut.await {
|
let res = match fut.await {
|
||||||
Ok(res) => handle_response(res.into(), tx, config).await,
|
Ok(res) => handle_response(res.into(), tx, config).await,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let res: Response<AnyBody> = err.into();
|
let res: Response<BoxBody> = err.into();
|
||||||
handle_response(res, tx, config).await
|
handle_response(res, tx, config).await
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -207,7 +206,6 @@ async fn handle_response<B>(
|
||||||
) -> Result<(), DispatchError>
|
) -> Result<(), DispatchError>
|
||||||
where
|
where
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
let (res, body) = res.replace_body(());
|
let (res, body) = res.replace_body(());
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::{
|
use std::{
|
||||||
error::Error as StdError,
|
|
||||||
future::Future,
|
future::Future,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
net,
|
net,
|
||||||
|
@ -19,7 +18,7 @@ use futures_core::{future::LocalBoxFuture, ready};
|
||||||
use log::error;
|
use log::error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{AnyBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
config::ServiceConfig,
|
config::ServiceConfig,
|
||||||
error::DispatchError,
|
error::DispatchError,
|
||||||
service::HttpFlow,
|
service::HttpFlow,
|
||||||
|
@ -39,12 +38,11 @@ pub struct H2Service<T, S, B> {
|
||||||
impl<T, S, B> H2Service<T, S, B>
|
impl<T, S, B> H2Service<T, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
/// Create new `H2Service` instance with config.
|
/// Create new `H2Service` instance with config.
|
||||||
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
|
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
|
||||||
|
@ -70,12 +68,11 @@ impl<S, B> H2Service<TcpStream, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
/// Create plain TCP based service
|
/// Create plain TCP based service
|
||||||
pub fn tcp(
|
pub fn tcp(
|
||||||
|
@ -114,12 +111,11 @@ mod openssl {
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
/// Create OpenSSL based service.
|
/// Create OpenSSL based service.
|
||||||
pub fn openssl(
|
pub fn openssl(
|
||||||
|
@ -162,12 +158,11 @@ mod rustls {
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
/// Create Rustls based service.
|
/// Create Rustls based service.
|
||||||
pub fn rustls(
|
pub fn rustls(
|
||||||
|
@ -204,12 +199,11 @@ where
|
||||||
|
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = DispatchError;
|
type Error = DispatchError;
|
||||||
|
@ -244,7 +238,7 @@ where
|
||||||
impl<T, S, B> H2ServiceHandler<T, S, B>
|
impl<T, S, B> H2ServiceHandler<T, S, B>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
|
@ -267,11 +261,10 @@ impl<T, S, B> Service<(T, Option<net::SocketAddr>)> for H2ServiceHandler<T, S, B
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = DispatchError;
|
type Error = DispatchError;
|
||||||
|
@ -320,7 +313,7 @@ pub struct H2ServiceHandlerResponse<T, S, B>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
|
@ -332,11 +325,10 @@ impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
type Output = Result<(), DispatchError>;
|
type Output = Result<(), DispatchError>;
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,8 @@ pub trait Head: Default + 'static {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RequestHead {
|
pub struct RequestHead {
|
||||||
pub uri: Uri,
|
|
||||||
pub method: Method,
|
pub method: Method,
|
||||||
|
pub uri: Uri,
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
pub headers: HeaderMap,
|
pub headers: HeaderMap,
|
||||||
pub extensions: RefCell<Extensions>,
|
pub extensions: RefCell<Extensions>,
|
||||||
|
@ -58,13 +58,13 @@ pub struct RequestHead {
|
||||||
impl Default for RequestHead {
|
impl Default for RequestHead {
|
||||||
fn default() -> RequestHead {
|
fn default() -> RequestHead {
|
||||||
RequestHead {
|
RequestHead {
|
||||||
uri: Uri::default(),
|
|
||||||
method: Method::default(),
|
method: Method::default(),
|
||||||
|
uri: Uri::default(),
|
||||||
version: Version::HTTP_11,
|
version: Version::HTTP_11,
|
||||||
headers: HeaderMap::with_capacity(16),
|
headers: HeaderMap::with_capacity(16),
|
||||||
flags: Flags::empty(),
|
|
||||||
peer_addr: None,
|
|
||||||
extensions: RefCell::new(Extensions::new()),
|
extensions: RefCell::new(Extensions::new()),
|
||||||
|
peer_addr: None,
|
||||||
|
flags: Flags::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,6 +192,7 @@ impl RequestHead {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum RequestHeadType {
|
pub enum RequestHeadType {
|
||||||
Owned(RequestHead),
|
Owned(RequestHead),
|
||||||
Rc(Rc<RequestHead>, Option<HeaderMap>),
|
Rc(Rc<RequestHead>, Option<HeaderMap>),
|
||||||
|
|
|
@ -6,14 +6,15 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
|
use bytestring::ByteString;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{AnyBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
error::Error,
|
|
||||||
extensions::Extensions,
|
extensions::Extensions,
|
||||||
|
header::{self, IntoHeaderValue},
|
||||||
http::{HeaderMap, StatusCode},
|
http::{HeaderMap, StatusCode},
|
||||||
message::{BoxedResponseHead, ResponseHead},
|
message::{BoxedResponseHead, ResponseHead},
|
||||||
ResponseBuilder,
|
Error, ResponseBuilder,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An HTTP response.
|
/// An HTTP response.
|
||||||
|
@ -22,13 +23,13 @@ pub struct Response<B> {
|
||||||
pub(crate) body: B,
|
pub(crate) body: B,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response<AnyBody> {
|
impl Response<BoxBody> {
|
||||||
/// Constructs a new response with default body.
|
/// Constructs a new response with default body.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(status: StatusCode) -> Self {
|
pub fn new(status: StatusCode) -> Self {
|
||||||
Response {
|
Response {
|
||||||
head: BoxedResponseHead::new(status),
|
head: BoxedResponseHead::new(status),
|
||||||
body: AnyBody::empty(),
|
body: BoxBody::new(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,6 +190,14 @@ impl<B> Response<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn map_into_boxed_body(self) -> Response<BoxBody>
|
||||||
|
where
|
||||||
|
B: MessageBody + 'static,
|
||||||
|
{
|
||||||
|
self.map_body(|_, body| BoxBody::new(body))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns body, consuming this response.
|
/// Returns body, consuming this response.
|
||||||
pub fn into_body(self) -> B {
|
pub fn into_body(self) -> B {
|
||||||
self.body
|
self.body
|
||||||
|
@ -223,81 +232,99 @@ impl<B: Default> Default for Response<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: Into<Response<AnyBody>>, E: Into<Error>> From<Result<I, E>>
|
impl<I: Into<Response<BoxBody>>, E: Into<Error>> From<Result<I, E>>
|
||||||
for Response<AnyBody>
|
for Response<BoxBody>
|
||||||
{
|
{
|
||||||
fn from(res: Result<I, E>) -> Self {
|
fn from(res: Result<I, E>) -> Self {
|
||||||
match res {
|
match res {
|
||||||
Ok(val) => val.into(),
|
Ok(val) => val.into(),
|
||||||
Err(err) => err.into().into(),
|
Err(err) => Response::from(err.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ResponseBuilder> for Response<AnyBody> {
|
impl From<ResponseBuilder> for Response<BoxBody> {
|
||||||
fn from(mut builder: ResponseBuilder) -> Self {
|
fn from(mut builder: ResponseBuilder) -> Self {
|
||||||
builder.finish()
|
builder.finish().map_into_boxed_body()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::convert::Infallible> for Response<AnyBody> {
|
impl From<std::convert::Infallible> for Response<BoxBody> {
|
||||||
fn from(val: std::convert::Infallible) -> Self {
|
fn from(val: std::convert::Infallible) -> Self {
|
||||||
match val {}
|
match val {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&'static str> for Response<AnyBody> {
|
impl From<&'static str> for Response<&'static str> {
|
||||||
fn from(val: &'static str) -> Self {
|
fn from(val: &'static str) -> Self {
|
||||||
Response::build(StatusCode::OK)
|
let mut res = Response::with_body(StatusCode::OK, val);
|
||||||
.content_type(mime::TEXT_PLAIN_UTF_8)
|
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||||
.body(val)
|
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&'static [u8]> for Response<AnyBody> {
|
impl From<&'static [u8]> for Response<&'static [u8]> {
|
||||||
fn from(val: &'static [u8]) -> Self {
|
fn from(val: &'static [u8]) -> Self {
|
||||||
Response::build(StatusCode::OK)
|
let mut res = Response::with_body(StatusCode::OK, val);
|
||||||
.content_type(mime::APPLICATION_OCTET_STREAM)
|
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
|
||||||
.body(val)
|
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for Response<AnyBody> {
|
impl From<String> for Response<String> {
|
||||||
fn from(val: String) -> Self {
|
fn from(val: String) -> Self {
|
||||||
Response::build(StatusCode::OK)
|
let mut res = Response::with_body(StatusCode::OK, val);
|
||||||
.content_type(mime::TEXT_PLAIN_UTF_8)
|
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||||
.body(val)
|
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a String> for Response<AnyBody> {
|
impl From<&String> for Response<String> {
|
||||||
fn from(val: &'a String) -> Self {
|
fn from(val: &String) -> Self {
|
||||||
Response::build(StatusCode::OK)
|
let mut res = Response::with_body(StatusCode::OK, val.clone());
|
||||||
.content_type(mime::TEXT_PLAIN_UTF_8)
|
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||||
.body(val)
|
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Bytes> for Response<AnyBody> {
|
impl From<Bytes> for Response<Bytes> {
|
||||||
fn from(val: Bytes) -> Self {
|
fn from(val: Bytes) -> Self {
|
||||||
Response::build(StatusCode::OK)
|
let mut res = Response::with_body(StatusCode::OK, val);
|
||||||
.content_type(mime::APPLICATION_OCTET_STREAM)
|
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
|
||||||
.body(val)
|
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BytesMut> for Response<AnyBody> {
|
impl From<BytesMut> for Response<BytesMut> {
|
||||||
fn from(val: BytesMut) -> Self {
|
fn from(val: BytesMut) -> Self {
|
||||||
Response::build(StatusCode::OK)
|
let mut res = Response::with_body(StatusCode::OK, val);
|
||||||
.content_type(mime::APPLICATION_OCTET_STREAM)
|
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
|
||||||
.body(val)
|
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ByteString> for Response<ByteString> {
|
||||||
|
fn from(val: ByteString) -> Self {
|
||||||
|
let mut res = Response::with_body(StatusCode::OK, val);
|
||||||
|
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||||
|
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE};
|
use crate::{
|
||||||
|
body::to_bytes,
|
||||||
|
http::header::{HeaderValue, CONTENT_TYPE, COOKIE},
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_debug() {
|
fn test_debug() {
|
||||||
|
@ -309,73 +336,73 @@ mod tests {
|
||||||
assert!(dbg.contains("Response"));
|
assert!(dbg.contains("Response"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[actix_rt::test]
|
||||||
fn test_into_response() {
|
async fn test_into_response() {
|
||||||
let resp: Response<AnyBody> = "test".into();
|
let res = Response::from("test");
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().get_ref(), b"test");
|
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||||
|
|
||||||
let resp: Response<AnyBody> = b"test".as_ref().into();
|
let res = Response::from(b"test".as_ref());
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("application/octet-stream")
|
HeaderValue::from_static("application/octet-stream")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().get_ref(), b"test");
|
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||||
|
|
||||||
let resp: Response<AnyBody> = "test".to_owned().into();
|
let res = Response::from("test".to_owned());
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().get_ref(), b"test");
|
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||||
|
|
||||||
let resp: Response<AnyBody> = (&"test".to_owned()).into();
|
let res = Response::from("test".to_owned());
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().get_ref(), b"test");
|
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||||
|
|
||||||
let b = Bytes::from_static(b"test");
|
let b = Bytes::from_static(b"test");
|
||||||
let resp: Response<AnyBody> = b.into();
|
let res = Response::from(b);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("application/octet-stream")
|
HeaderValue::from_static("application/octet-stream")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().get_ref(), b"test");
|
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||||
|
|
||||||
let b = Bytes::from_static(b"test");
|
let b = Bytes::from_static(b"test");
|
||||||
let resp: Response<AnyBody> = b.into();
|
let res = Response::from(b);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("application/octet-stream")
|
HeaderValue::from_static("application/octet-stream")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().get_ref(), b"test");
|
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||||
|
|
||||||
let b = BytesMut::from("test");
|
let b = BytesMut::from("test");
|
||||||
let resp: Response<AnyBody> = b.into();
|
let res = Response::from(b);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("application/octet-stream")
|
HeaderValue::from_static("application/octet-stream")
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().get_ref(), b"test");
|
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,11 @@
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefMut},
|
cell::{Ref, RefMut},
|
||||||
error::Error as StdError,
|
fmt, str,
|
||||||
fmt,
|
|
||||||
future::Future,
|
|
||||||
pin::Pin,
|
|
||||||
str,
|
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use bytes::Bytes;
|
|
||||||
use futures_core::Stream;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{AnyBody, BodyStream},
|
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},
|
||||||
|
@ -235,10 +227,14 @@ impl ResponseBuilder {
|
||||||
/// Generate response with a wrapped body.
|
/// Generate response with a wrapped body.
|
||||||
///
|
///
|
||||||
/// This `ResponseBuilder` will be left in a useless state.
|
/// This `ResponseBuilder` will be left in a useless state.
|
||||||
#[inline]
|
pub fn body<B>(&mut self, body: B) -> Response<EitherBody<B>>
|
||||||
pub fn body<B: Into<AnyBody>>(&mut self, body: B) -> Response<AnyBody> {
|
where
|
||||||
self.message_body(body.into())
|
B: MessageBody + 'static,
|
||||||
.unwrap_or_else(Response::from)
|
{
|
||||||
|
match self.message_body(body) {
|
||||||
|
Ok(res) => res.map_body(|_, body| EitherBody::left(body)),
|
||||||
|
Err(err) => Response::from(err).map_body(|_, body| EitherBody::right(body)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate response with a body.
|
/// Generate response with a body.
|
||||||
|
@ -253,24 +249,12 @@ impl ResponseBuilder {
|
||||||
Ok(Response { head, body })
|
Ok(Response { head, body })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate response with a streaming body.
|
|
||||||
///
|
|
||||||
/// This `ResponseBuilder` will be left in a useless state.
|
|
||||||
#[inline]
|
|
||||||
pub fn streaming<S, E>(&mut self, stream: S) -> Response<AnyBody>
|
|
||||||
where
|
|
||||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
|
||||||
E: Into<Box<dyn StdError>> + 'static,
|
|
||||||
{
|
|
||||||
self.body(AnyBody::new_boxed(BodyStream::new(stream)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate response with an empty body.
|
/// Generate response with an empty body.
|
||||||
///
|
///
|
||||||
/// This `ResponseBuilder` will be left in a useless state.
|
/// This `ResponseBuilder` will be left in a useless state.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn finish(&mut self) -> Response<AnyBody> {
|
pub fn finish(&mut self) -> Response<EitherBody<()>> {
|
||||||
self.body(AnyBody::empty())
|
self.body(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an owned `ResponseBuilder`, leaving the original in a useless state.
|
/// Create an owned `ResponseBuilder`, leaving the original in a useless state.
|
||||||
|
@ -327,14 +311,6 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Future for ResponseBuilder {
|
|
||||||
type Output = Result<Response<AnyBody>, Error>;
|
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
Poll::Ready(Ok(self.finish()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
||||||
|
@ -356,8 +332,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]
|
||||||
|
@ -383,20 +360,28 @@ 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]
|
||||||
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");
|
||||||
|
|
||||||
|
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]
|
||||||
fn test_into_builder() {
|
fn test_into_builder() {
|
||||||
let mut resp: Response<AnyBody> = "test".into();
|
let mut resp: Response<_> = "test".into();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
resp.headers_mut().insert(
|
resp.headers_mut().insert(
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::{
|
use std::{
|
||||||
error::Error as StdError,
|
|
||||||
fmt,
|
fmt,
|
||||||
future::Future,
|
future::Future,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
@ -15,10 +14,10 @@ use actix_service::{
|
||||||
fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _,
|
fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _,
|
||||||
};
|
};
|
||||||
use futures_core::{future::LocalBoxFuture, ready};
|
use futures_core::{future::LocalBoxFuture, ready};
|
||||||
use pin_project::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{AnyBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
builder::HttpServiceBuilder,
|
builder::HttpServiceBuilder,
|
||||||
config::{KeepAlive, ServiceConfig},
|
config::{KeepAlive, ServiceConfig},
|
||||||
error::DispatchError,
|
error::DispatchError,
|
||||||
|
@ -38,7 +37,7 @@ pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler> {
|
||||||
impl<T, S, B> HttpService<T, S, B>
|
impl<T, S, B> HttpService<T, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
|
@ -53,12 +52,11 @@ where
|
||||||
impl<T, S, B> HttpService<T, S, B>
|
impl<T, S, B> HttpService<T, S, B>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
/// Create new `HttpService` instance.
|
/// Create new `HttpService` instance.
|
||||||
pub fn new<F: IntoServiceFactory<S, Request>>(service: F) -> Self {
|
pub fn new<F: IntoServiceFactory<S, Request>>(service: F) -> Self {
|
||||||
|
@ -93,7 +91,7 @@ where
|
||||||
impl<T, S, B, X, U> HttpService<T, S, B, X, U>
|
impl<T, S, B, X, U> HttpService<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
|
@ -107,7 +105,7 @@ where
|
||||||
pub fn expect<X1>(self, expect: X1) -> HttpService<T, S, B, X1, U>
|
pub fn expect<X1>(self, expect: X1) -> HttpService<T, S, B, X1, U>
|
||||||
where
|
where
|
||||||
X1: ServiceFactory<Request, Config = (), Response = Request>,
|
X1: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X1::Error: Into<Response<AnyBody>>,
|
X1::Error: Into<Response<BoxBody>>,
|
||||||
X1::InitError: fmt::Debug,
|
X1::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
HttpService {
|
HttpService {
|
||||||
|
@ -151,17 +149,16 @@ impl<S, B, X, U> HttpService<TcpStream, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Future: 'static,
|
X::Future: 'static,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
|
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<
|
||||||
|
@ -170,7 +167,7 @@ where
|
||||||
Response = (),
|
Response = (),
|
||||||
>,
|
>,
|
||||||
U::Future: 'static,
|
U::Future: 'static,
|
||||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
/// Create simple tcp stream service
|
/// Create simple tcp stream service
|
||||||
|
@ -208,17 +205,16 @@ mod openssl {
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Future: 'static,
|
X::Future: 'static,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
|
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<
|
||||||
|
@ -227,7 +223,7 @@ mod openssl {
|
||||||
Response = (),
|
Response = (),
|
||||||
>,
|
>,
|
||||||
U::Future: 'static,
|
U::Future: 'static,
|
||||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
/// Create OpenSSL based service.
|
/// Create OpenSSL based service.
|
||||||
|
@ -281,17 +277,16 @@ mod rustls {
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Future: 'static,
|
X::Future: 'static,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
|
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<
|
||||||
|
@ -300,7 +295,7 @@ mod rustls {
|
||||||
Response = (),
|
Response = (),
|
||||||
>,
|
>,
|
||||||
U::Future: 'static,
|
U::Future: 'static,
|
||||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
/// Create Rustls based service.
|
/// Create Rustls based service.
|
||||||
|
@ -348,22 +343,21 @@ where
|
||||||
|
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: ServiceFactory<Request, Config = (), Response = Request>,
|
X: ServiceFactory<Request, Config = (), Response = Request>,
|
||||||
X::Future: 'static,
|
X::Future: 'static,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
X::InitError: fmt::Debug,
|
X::InitError: fmt::Debug,
|
||||||
|
|
||||||
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
|
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
|
||||||
U::Future: 'static,
|
U::Future: 'static,
|
||||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
type Response = ();
|
type Response = ();
|
||||||
|
@ -426,11 +420,11 @@ where
|
||||||
impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
|
impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
X: Service<Request>,
|
X: Service<Request>,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
U: Service<(Request, Framed<T, h1::Codec>)>,
|
U: Service<(Request, Framed<T, h1::Codec>)>,
|
||||||
U::Error: Into<Response<AnyBody>>,
|
U::Error: Into<Response<BoxBody>>,
|
||||||
{
|
{
|
||||||
pub(super) fn new(
|
pub(super) fn new(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
|
@ -450,7 +444,7 @@ where
|
||||||
pub(super) fn _poll_ready(
|
pub(super) fn _poll_ready(
|
||||||
&self,
|
&self,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Result<(), Response<AnyBody>>> {
|
) -> Poll<Result<(), Response<BoxBody>>> {
|
||||||
ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?;
|
ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?;
|
||||||
|
|
||||||
ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?;
|
ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?;
|
||||||
|
@ -486,18 +480,17 @@ where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display + Into<Response<AnyBody>>,
|
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||||
{
|
{
|
||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = DispatchError;
|
type Error = DispatchError;
|
||||||
|
@ -519,23 +512,27 @@ where
|
||||||
|
|
||||||
match proto {
|
match proto {
|
||||||
Protocol::Http2 => HttpServiceHandlerResponse {
|
Protocol::Http2 => HttpServiceHandlerResponse {
|
||||||
state: State::H2Handshake(Some((
|
state: State::H2Handshake {
|
||||||
h2::handshake_with_timeout(io, &self.cfg),
|
handshake: Some((
|
||||||
self.cfg.clone(),
|
h2::handshake_with_timeout(io, &self.cfg),
|
||||||
self.flow.clone(),
|
self.cfg.clone(),
|
||||||
on_connect_data,
|
self.flow.clone(),
|
||||||
peer_addr,
|
on_connect_data,
|
||||||
))),
|
peer_addr,
|
||||||
|
)),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Protocol::Http1 => HttpServiceHandlerResponse {
|
Protocol::Http1 => HttpServiceHandlerResponse {
|
||||||
state: State::H1(h1::Dispatcher::new(
|
state: State::H1 {
|
||||||
io,
|
dispatcher: h1::Dispatcher::new(
|
||||||
self.cfg.clone(),
|
io,
|
||||||
self.flow.clone(),
|
self.cfg.clone(),
|
||||||
on_connect_data,
|
self.flow.clone(),
|
||||||
peer_addr,
|
on_connect_data,
|
||||||
)),
|
peer_addr,
|
||||||
|
),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
proto => unimplemented!("Unsupported HTTP version: {:?}.", proto),
|
proto => unimplemented!("Unsupported HTTP version: {:?}.", proto),
|
||||||
|
@ -543,58 +540,65 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project(project = StateProj)]
|
pin_project! {
|
||||||
enum State<T, S, B, X, U>
|
#[project = StateProj]
|
||||||
where
|
enum State<T, S, B, X, U>
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
where
|
||||||
|
T: AsyncRead,
|
||||||
|
T: AsyncWrite,
|
||||||
|
T: Unpin,
|
||||||
|
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Error: Into<Response<AnyBody>>,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
H1(#[pin] h1::Dispatcher<T, S, B, X, U>),
|
H1 { #[pin] dispatcher: h1::Dispatcher<T, S, B, X, U> },
|
||||||
H2(#[pin] h2::Dispatcher<T, S, B, X, U>),
|
H2 { #[pin] dispatcher: h2::Dispatcher<T, S, B, X, U> },
|
||||||
H2Handshake(
|
H2Handshake {
|
||||||
Option<(
|
handshake: Option<(
|
||||||
h2::HandshakeWithTimeout<T>,
|
h2::HandshakeWithTimeout<T>,
|
||||||
ServiceConfig,
|
ServiceConfig,
|
||||||
Rc<HttpFlow<S, X, U>>,
|
Rc<HttpFlow<S, X, U>>,
|
||||||
OnConnectData,
|
OnConnectData,
|
||||||
Option<net::SocketAddr>,
|
Option<net::SocketAddr>,
|
||||||
)>,
|
)>,
|
||||||
),
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project]
|
pin_project! {
|
||||||
pub struct HttpServiceHandlerResponse<T, S, B, X, U>
|
pub struct HttpServiceHandlerResponse<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead,
|
||||||
|
T: AsyncWrite,
|
||||||
|
T: Unpin,
|
||||||
|
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>>,
|
||||||
S::Future: 'static,
|
S::Error: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Future: 'static,
|
||||||
|
S::Response: Into<Response<B>>,
|
||||||
|
S::Response: 'static,
|
||||||
|
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
#[pin]
|
#[pin]
|
||||||
state: State<T, S, B, X, U>,
|
state: State<T, S, B, X, U>,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U>
|
impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U>
|
||||||
|
@ -602,15 +606,14 @@ where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
S::Error: Into<Response<AnyBody>> + 'static,
|
S::Error: Into<Response<BoxBody>> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
|
|
||||||
X: Service<Request, Response = Request>,
|
X: Service<Request, Response = Request>,
|
||||||
X::Error: Into<Response<AnyBody>>,
|
X::Error: Into<Response<BoxBody>>,
|
||||||
|
|
||||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
|
@ -619,23 +622,24 @@ where
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
match self.as_mut().project().state.project() {
|
match self.as_mut().project().state.project() {
|
||||||
StateProj::H1(disp) => disp.poll(cx),
|
StateProj::H1 { dispatcher } => dispatcher.poll(cx),
|
||||||
StateProj::H2(disp) => disp.poll(cx),
|
StateProj::H2 { dispatcher } => dispatcher.poll(cx),
|
||||||
StateProj::H2Handshake(data) => {
|
StateProj::H2Handshake { handshake: data } => {
|
||||||
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
|
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
|
||||||
Ok((conn, timer)) => {
|
Ok((conn, timer)) => {
|
||||||
let (_, cfg, srv, on_connect_data, peer_addr) =
|
let (_, config, flow, on_connect_data, peer_addr) =
|
||||||
data.take().unwrap();
|
data.take().unwrap();
|
||||||
self.as_mut().project().state.set(State::H2(
|
|
||||||
h2::Dispatcher::new(
|
self.as_mut().project().state.set(State::H2 {
|
||||||
srv,
|
dispatcher: h2::Dispatcher::new(
|
||||||
|
flow,
|
||||||
conn,
|
conn,
|
||||||
on_connect_data,
|
on_connect_data,
|
||||||
cfg,
|
config,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
timer,
|
timer,
|
||||||
),
|
),
|
||||||
));
|
});
|
||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
|
@ -4,17 +4,21 @@ use std::task::{Context, Poll};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
use actix_service::{IntoService, Service};
|
use actix_service::{IntoService, Service};
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use super::{Codec, Frame, Message};
|
use super::{Codec, Frame, Message};
|
||||||
|
|
||||||
#[pin_project::pin_project]
|
pin_project! {
|
||||||
pub struct Dispatcher<S, T>
|
pub struct Dispatcher<S, T>
|
||||||
where
|
where
|
||||||
S: Service<Frame, Response = Message> + 'static,
|
S: Service<Frame, Response = Message>,
|
||||||
T: AsyncRead + AsyncWrite,
|
S: 'static,
|
||||||
{
|
T: AsyncRead,
|
||||||
#[pin]
|
T: AsyncWrite,
|
||||||
inner: inner::Dispatcher<S, T, Codec, Message>,
|
{
|
||||||
|
#[pin]
|
||||||
|
inner: inner::Dispatcher<S, T, Codec, Message>,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, T> Dispatcher<S, T>
|
impl<S, T> Dispatcher<S, T>
|
||||||
|
@ -72,7 +76,7 @@ mod inner {
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
|
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
|
||||||
|
|
||||||
use crate::{body::AnyBody, Response};
|
use crate::{body::BoxBody, Response};
|
||||||
|
|
||||||
/// Framed transport errors
|
/// Framed transport errors
|
||||||
pub enum DispatcherError<E, U, I>
|
pub enum DispatcherError<E, U, I>
|
||||||
|
@ -136,7 +140,7 @@ mod inner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E, U, I> From<DispatcherError<E, U, I>> for Response<AnyBody>
|
impl<E, U, I> From<DispatcherError<E, U, I>> for Response<BoxBody>
|
||||||
where
|
where
|
||||||
E: fmt::Debug + fmt::Display,
|
E: fmt::Debug + fmt::Display,
|
||||||
U: Encoder<I> + Decoder,
|
U: Encoder<I> + Decoder,
|
||||||
|
@ -144,7 +148,7 @@ mod inner {
|
||||||
<U as Decoder>::Error: fmt::Debug,
|
<U as Decoder>::Error: fmt::Debug,
|
||||||
{
|
{
|
||||||
fn from(err: DispatcherError<E, U, I>) -> Self {
|
fn from(err: DispatcherError<E, U, I>) -> Self {
|
||||||
Response::internal_server_error().set_body(AnyBody::from(err.to_string()))
|
Response::internal_server_error().set_body(BoxBody::new(err.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,9 @@ use std::io;
|
||||||
use derive_more::{Display, Error, From};
|
use derive_more::{Display, Error, From};
|
||||||
use http::{header, Method, StatusCode};
|
use http::{header, Method, StatusCode};
|
||||||
|
|
||||||
|
use crate::body::BoxBody;
|
||||||
use crate::{
|
use crate::{
|
||||||
body::AnyBody, header::HeaderValue, message::RequestHead, response::Response,
|
header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder,
|
||||||
ResponseBuilder,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod codec;
|
mod codec;
|
||||||
|
@ -69,7 +69,7 @@ pub enum ProtocolError {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// WebSocket handshake errors
|
/// WebSocket handshake errors
|
||||||
#[derive(Debug, PartialEq, Display, Error)]
|
#[derive(Debug, Clone, Copy, PartialEq, Display, Error)]
|
||||||
pub enum HandshakeError {
|
pub enum HandshakeError {
|
||||||
/// Only get method is allowed.
|
/// Only get method is allowed.
|
||||||
#[display(fmt = "Method not allowed.")]
|
#[display(fmt = "Method not allowed.")]
|
||||||
|
@ -96,8 +96,8 @@ pub enum HandshakeError {
|
||||||
BadWebsocketKey,
|
BadWebsocketKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&HandshakeError> for Response<AnyBody> {
|
impl From<HandshakeError> for Response<BoxBody> {
|
||||||
fn from(err: &HandshakeError) -> Self {
|
fn from(err: HandshakeError) -> Self {
|
||||||
match err {
|
match err {
|
||||||
HandshakeError::GetMethodRequired => {
|
HandshakeError::GetMethodRequired => {
|
||||||
let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED);
|
let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED);
|
||||||
|
@ -139,9 +139,9 @@ impl From<&HandshakeError> for Response<AnyBody> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<HandshakeError> for Response<AnyBody> {
|
impl From<&HandshakeError> for Response<BoxBody> {
|
||||||
fn from(err: HandshakeError) -> Self {
|
fn from(err: &HandshakeError) -> Self {
|
||||||
(&err).into()
|
(*err).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,9 +220,10 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::{header, Method};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{body::AnyBody, test::TestRequest};
|
use crate::test::TestRequest;
|
||||||
use http::{header, Method};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_handshake() {
|
fn test_handshake() {
|
||||||
|
@ -336,17 +337,17 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ws_error_http_response() {
|
fn test_ws_error_http_response() {
|
||||||
let resp: Response<AnyBody> = HandshakeError::GetMethodRequired.into();
|
let resp: Response<BoxBody> = HandshakeError::GetMethodRequired.into();
|
||||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||||
let resp: Response<AnyBody> = HandshakeError::NoWebsocketUpgrade.into();
|
let resp: Response<BoxBody> = HandshakeError::NoWebsocketUpgrade.into();
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
let resp: Response<AnyBody> = HandshakeError::NoConnectionUpgrade.into();
|
let resp: Response<BoxBody> = HandshakeError::NoConnectionUpgrade.into();
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
let resp: Response<AnyBody> = HandshakeError::NoVersionHeader.into();
|
let resp: Response<BoxBody> = HandshakeError::NoVersionHeader.into();
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
let resp: Response<AnyBody> = HandshakeError::UnsupportedVersion.into();
|
let resp: Response<BoxBody> = HandshakeError::UnsupportedVersion.into();
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
let resp: Response<AnyBody> = HandshakeError::BadWebsocketKey.into();
|
let resp: Response<BoxBody> = HandshakeError::BadWebsocketKey.into();
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::AnyBody, http, http::StatusCode, HttpMessage, HttpService, Request, Response,
|
body::BoxBody, http, http::StatusCode, HttpMessage, HttpService, Request, Response,
|
||||||
};
|
};
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
use actix_service::ServiceFactoryExt;
|
use actix_service::ServiceFactoryExt;
|
||||||
|
@ -99,7 +99,7 @@ async fn test_with_query_parameter() {
|
||||||
#[display(fmt = "expect failed")]
|
#[display(fmt = "expect failed")]
|
||||||
struct ExpectFailed;
|
struct ExpectFailed;
|
||||||
|
|
||||||
impl From<ExpectFailed> for Response<AnyBody> {
|
impl From<ExpectFailed> for Response<BoxBody> {
|
||||||
fn from(_: ExpectFailed) -> Self {
|
fn from(_: ExpectFailed) -> Self {
|
||||||
Response::new(StatusCode::EXPECTATION_FAILED)
|
Response::new(StatusCode::EXPECTATION_FAILED)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ extern crate tls_openssl as openssl;
|
||||||
use std::{convert::Infallible, io};
|
use std::{convert::Infallible, io};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::{AnyBody, SizedStream},
|
body::{BodyStream, BoxBody, SizedStream},
|
||||||
error::PayloadError,
|
error::PayloadError,
|
||||||
http::{
|
http::{
|
||||||
header::{self, HeaderValue},
|
header::{self, HeaderValue},
|
||||||
|
@ -348,7 +348,7 @@ async fn test_h2_body_chunked_explicit() {
|
||||||
ok::<_, Infallible>(
|
ok::<_, Infallible>(
|
||||||
Response::build(StatusCode::OK)
|
Response::build(StatusCode::OK)
|
||||||
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
.streaming(body),
|
.body(BodyStream::new(body)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.openssl(tls_config())
|
.openssl(tls_config())
|
||||||
|
@ -399,9 +399,11 @@ async fn test_h2_response_http_error_handling() {
|
||||||
#[display(fmt = "error")]
|
#[display(fmt = "error")]
|
||||||
struct BadRequest;
|
struct BadRequest;
|
||||||
|
|
||||||
impl From<BadRequest> for Response<AnyBody> {
|
impl From<BadRequest> for Response<BoxBody> {
|
||||||
fn from(err: BadRequest) -> Self {
|
fn from(err: BadRequest) -> Self {
|
||||||
Response::build(StatusCode::BAD_REQUEST).body(err.to_string())
|
Response::build(StatusCode::BAD_REQUEST)
|
||||||
|
.body(err.to_string())
|
||||||
|
.map_into_boxed_body()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,7 +411,7 @@ impl From<BadRequest> for Response<AnyBody> {
|
||||||
async fn test_h2_service_error() {
|
async fn test_h2_service_error() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| err::<Response<AnyBody>, _>(BadRequest))
|
.h2(|_| err::<Response<BoxBody>, _>(BadRequest))
|
||||||
.openssl(tls_config())
|
.openssl(tls_config())
|
||||||
.map_err(|_| ())
|
.map_err(|_| ())
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,7 +10,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::{AnyBody, SizedStream},
|
body::{BodyStream, BoxBody, SizedStream},
|
||||||
error::PayloadError,
|
error::PayloadError,
|
||||||
http::{
|
http::{
|
||||||
header::{self, HeaderName, HeaderValue},
|
header::{self, HeaderName, HeaderValue},
|
||||||
|
@ -416,7 +416,7 @@ async fn test_h2_body_chunked_explicit() {
|
||||||
ok::<_, Infallible>(
|
ok::<_, Infallible>(
|
||||||
Response::build(StatusCode::OK)
|
Response::build(StatusCode::OK)
|
||||||
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
.streaming(body),
|
.body(BodyStream::new(body)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.rustls(tls_config())
|
.rustls(tls_config())
|
||||||
|
@ -467,9 +467,9 @@ async fn test_h2_response_http_error_handling() {
|
||||||
#[display(fmt = "error")]
|
#[display(fmt = "error")]
|
||||||
struct BadRequest;
|
struct BadRequest;
|
||||||
|
|
||||||
impl From<BadRequest> for Response<AnyBody> {
|
impl From<BadRequest> for Response<BoxBody> {
|
||||||
fn from(_: BadRequest) -> Self {
|
fn from(_: BadRequest) -> Self {
|
||||||
Response::bad_request().set_body(AnyBody::from("error"))
|
Response::bad_request().set_body(BoxBody::new("error"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,7 +477,7 @@ impl From<BadRequest> for Response<AnyBody> {
|
||||||
async fn test_h2_service_error() {
|
async fn test_h2_service_error() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| err::<Response<AnyBody>, _>(BadRequest))
|
.h2(|_| err::<Response<BoxBody>, _>(BadRequest))
|
||||||
.rustls(tls_config())
|
.rustls(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
@ -494,7 +494,7 @@ async fn test_h2_service_error() {
|
||||||
async fn test_h1_service_error() {
|
async fn test_h1_service_error() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| err::<Response<AnyBody>, _>(BadRequest))
|
.h1(|_| err::<Response<BoxBody>, _>(BadRequest))
|
||||||
.rustls(tls_config())
|
.rustls(tls_config())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::{AnyBody, SizedStream},
|
body::{self, BodyStream, BoxBody, SizedStream},
|
||||||
header, http, Error, HttpMessage, HttpService, KeepAlive, Request, Response,
|
header, http, Error, HttpMessage, HttpService, KeepAlive, Request, Response,
|
||||||
StatusCode,
|
StatusCode,
|
||||||
};
|
};
|
||||||
|
@ -69,7 +69,7 @@ async fn test_h1_2() {
|
||||||
#[display(fmt = "expect failed")]
|
#[display(fmt = "expect failed")]
|
||||||
struct ExpectFailed;
|
struct ExpectFailed;
|
||||||
|
|
||||||
impl From<ExpectFailed> for Response<AnyBody> {
|
impl From<ExpectFailed> for Response<BoxBody> {
|
||||||
fn from(_: ExpectFailed) -> Self {
|
fn from(_: ExpectFailed) -> Self {
|
||||||
Response::new(StatusCode::EXPECTATION_FAILED)
|
Response::new(StatusCode::EXPECTATION_FAILED)
|
||||||
}
|
}
|
||||||
|
@ -622,7 +622,7 @@ async fn test_h1_body_chunked_explicit() {
|
||||||
ok::<_, Infallible>(
|
ok::<_, Infallible>(
|
||||||
Response::build(StatusCode::OK)
|
Response::build(StatusCode::OK)
|
||||||
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
.streaming(body),
|
.body(BodyStream::new(body)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.tcp()
|
.tcp()
|
||||||
|
@ -656,7 +656,9 @@ async fn test_h1_body_chunked_implicit() {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| {
|
.h1(|_| {
|
||||||
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
||||||
ok::<_, Infallible>(Response::build(StatusCode::OK).streaming(body))
|
ok::<_, Infallible>(
|
||||||
|
Response::build(StatusCode::OK).body(BodyStream::new(body)),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.tcp()
|
.tcp()
|
||||||
})
|
})
|
||||||
|
@ -714,9 +716,9 @@ async fn test_h1_response_http_error_handling() {
|
||||||
#[display(fmt = "error")]
|
#[display(fmt = "error")]
|
||||||
struct BadRequest;
|
struct BadRequest;
|
||||||
|
|
||||||
impl From<BadRequest> for Response<AnyBody> {
|
impl From<BadRequest> for Response<BoxBody> {
|
||||||
fn from(_: BadRequest) -> Self {
|
fn from(_: BadRequest) -> Self {
|
||||||
Response::bad_request().set_body(AnyBody::from("error"))
|
Response::bad_request().set_body(BoxBody::new("error"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -724,7 +726,7 @@ impl From<BadRequest> for Response<AnyBody> {
|
||||||
async fn test_h1_service_error() {
|
async fn test_h1_service_error() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|_| err::<Response<AnyBody>, _>(BadRequest))
|
.h1(|_| err::<Response<()>, _>(BadRequest))
|
||||||
.tcp()
|
.tcp()
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
@ -773,36 +775,35 @@ async fn test_not_modified_spec_h1() {
|
||||||
let mut srv = test_server(|| {
|
let mut srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h1(|req: Request| {
|
.h1(|req: Request| {
|
||||||
let res: Response<AnyBody> = match req.path() {
|
let res: Response<BoxBody> = match req.path() {
|
||||||
// with no content-length
|
// with no content-length
|
||||||
"/none" => {
|
"/none" => {
|
||||||
Response::with_body(StatusCode::NOT_MODIFIED, AnyBody::None)
|
Response::with_body(StatusCode::NOT_MODIFIED, body::None::new())
|
||||||
|
.map_into_boxed_body()
|
||||||
}
|
}
|
||||||
|
|
||||||
// with no content-length
|
// with no content-length
|
||||||
"/body" => Response::with_body(
|
"/body" => Response::with_body(StatusCode::NOT_MODIFIED, "1234")
|
||||||
StatusCode::NOT_MODIFIED,
|
.map_into_boxed_body(),
|
||||||
AnyBody::from("1234"),
|
|
||||||
),
|
|
||||||
|
|
||||||
// with manual content-length header and specific None body
|
// with manual content-length header and specific None body
|
||||||
"/cl-none" => {
|
"/cl-none" => {
|
||||||
let mut res =
|
let mut res = Response::with_body(
|
||||||
Response::with_body(StatusCode::NOT_MODIFIED, AnyBody::None);
|
StatusCode::NOT_MODIFIED,
|
||||||
|
body::None::new(),
|
||||||
|
);
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert(CL.clone(), header::HeaderValue::from_static("24"));
|
.insert(CL.clone(), header::HeaderValue::from_static("24"));
|
||||||
res
|
res.map_into_boxed_body()
|
||||||
}
|
}
|
||||||
|
|
||||||
// with manual content-length header and ignore-able body
|
// with manual content-length header and ignore-able body
|
||||||
"/cl-body" => {
|
"/cl-body" => {
|
||||||
let mut res = Response::with_body(
|
let mut res =
|
||||||
StatusCode::NOT_MODIFIED,
|
Response::with_body(StatusCode::NOT_MODIFIED, "1234");
|
||||||
AnyBody::from("1234"),
|
|
||||||
);
|
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert(CL.clone(), header::HeaderValue::from_static("4"));
|
.insert(CL.clone(), header::HeaderValue::from_static("4"));
|
||||||
res
|
res.map_into_boxed_body()
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => panic!("unknown route"),
|
_ => panic!("unknown route"),
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::{AnyBody, BodySize},
|
body::{BodySize, BoxBody},
|
||||||
h1,
|
h1,
|
||||||
ws::{self, CloseCode, Frame, Item, Message},
|
ws::{self, CloseCode, Frame, Item, Message},
|
||||||
Error, HttpService, Request, Response,
|
Error, HttpService, Request, Response,
|
||||||
|
@ -50,14 +50,14 @@ enum WsServiceError {
|
||||||
Dispatcher,
|
Dispatcher,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<WsServiceError> for Response<AnyBody> {
|
impl From<WsServiceError> for Response<BoxBody> {
|
||||||
fn from(err: WsServiceError) -> Self {
|
fn from(err: WsServiceError) -> Self {
|
||||||
match err {
|
match err {
|
||||||
WsServiceError::Http(err) => err.into(),
|
WsServiceError::Http(err) => err.into(),
|
||||||
WsServiceError::Ws(err) => err.into(),
|
WsServiceError::Ws(err) => err.into(),
|
||||||
WsServiceError::Io(_err) => unreachable!(),
|
WsServiceError::Io(_err) => unreachable!(),
|
||||||
WsServiceError::Dispatcher => Response::internal_server_error()
|
WsServiceError::Dispatcher => Response::internal_server_error()
|
||||||
.set_body(AnyBody::from(format!("{}", err))),
|
.set_body(BoxBody::new(format!("{}", err))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ extern crate tls_openssl as openssl;
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
extern crate tls_rustls as rustls;
|
extern crate tls_rustls as rustls;
|
||||||
|
|
||||||
use std::{error::Error as StdError, fmt, net, thread, time::Duration};
|
use std::{fmt, net, thread, time::Duration};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
pub use actix_http::test::TestBuffer;
|
pub use actix_http::test::TestBuffer;
|
||||||
|
@ -41,7 +41,8 @@ use actix_http::{
|
||||||
};
|
};
|
||||||
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
|
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{AppConfig, MessageBody, Server, ServerHandle, Service},
|
body::MessageBody,
|
||||||
|
dev::{AppConfig, Server, ServerHandle, Service},
|
||||||
rt::{self, System},
|
rt::{self, System},
|
||||||
web, Error,
|
web, Error,
|
||||||
};
|
};
|
||||||
|
@ -88,7 +89,6 @@ where
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
start_with(TestServerConfig::default(), factory)
|
start_with(TestServerConfig::default(), factory)
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,6 @@ where
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
// for sending handles and server info back from the spawned thread
|
// for sending handles and server info back from the spawned thread
|
||||||
let (started_tx, started_rx) = std::sync::mpsc::channel();
|
let (started_tx, started_rx) = std::sync::mpsc::channel();
|
||||||
|
|
|
@ -22,7 +22,7 @@ actix-web = { version = "4.0.0-beta.11", default-features = false }
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytestring = "1"
|
bytestring = "1"
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
pin-project = "1.0.0"
|
pin-project-lite = "0.2"
|
||||||
tokio = { version = "1", features = ["sync"] }
|
tokio = { version = "1", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -30,6 +30,7 @@ use actix_web::{
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use bytestring::ByteString;
|
use bytestring::ByteString;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
use tokio::sync::oneshot::Sender;
|
use tokio::sync::oneshot::Sender;
|
||||||
|
|
||||||
/// Perform WebSocket handshake and start actor.
|
/// Perform WebSocket handshake and start actor.
|
||||||
|
@ -462,13 +463,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pin_project::pin_project]
|
pin_project! {
|
||||||
struct WsStream<S> {
|
struct WsStream<S> {
|
||||||
#[pin]
|
#[pin]
|
||||||
stream: S,
|
stream: S,
|
||||||
decoder: Codec,
|
decoder: Codec,
|
||||||
buf: BytesMut,
|
buf: BytesMut,
|
||||||
closed: bool,
|
closed: bool,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> WsStream<S>
|
impl<S> WsStream<S>
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
266
awc/src/any_body.rs
Normal file
266
awc/src/any_body.rs
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
fmt, mem,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
|
use futures_core::Stream;
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
|
use actix_http::body::{BodySize, BodyStream, BoxBody, MessageBody, SizedStream};
|
||||||
|
|
||||||
|
use crate::BoxError;
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
/// Represents various types of HTTP message body.
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[project = AnyBodyProj]
|
||||||
|
pub enum AnyBody<B = BoxBody> {
|
||||||
|
/// Empty response. `Content-Length` header is not set.
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// Complete, in-memory response body.
|
||||||
|
Bytes { body: Bytes },
|
||||||
|
|
||||||
|
/// Generic / Other message body.
|
||||||
|
Body { #[pin] body: B },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnyBody {
|
||||||
|
/// Constructs a "body" representing an empty response.
|
||||||
|
pub fn none() -> Self {
|
||||||
|
Self::None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a new, 0-length body.
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self::Bytes { body: Bytes::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create boxed body from generic message body.
|
||||||
|
pub fn new_boxed<B>(body: B) -> Self
|
||||||
|
where
|
||||||
|
B: MessageBody + 'static,
|
||||||
|
{
|
||||||
|
Self::Body {
|
||||||
|
body: BoxBody::new(body),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs new `AnyBody` instance from a slice of bytes by copying it.
|
||||||
|
///
|
||||||
|
/// If your bytes container is owned, it may be cheaper to use a `From` impl.
|
||||||
|
pub fn copy_from_slice(s: &[u8]) -> Self {
|
||||||
|
Self::Bytes {
|
||||||
|
body: Bytes::copy_from_slice(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[deprecated(since = "4.0.0", note = "Renamed to `copy_from_slice`.")]
|
||||||
|
pub fn from_slice(s: &[u8]) -> Self {
|
||||||
|
Self::Bytes {
|
||||||
|
body: Bytes::copy_from_slice(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> AnyBody<B> {
|
||||||
|
/// Create body from generic message body.
|
||||||
|
pub fn new(body: B) -> Self {
|
||||||
|
Self::Body { body }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> AnyBody<B>
|
||||||
|
where
|
||||||
|
B: MessageBody + 'static,
|
||||||
|
{
|
||||||
|
pub fn into_boxed(self) -> AnyBody {
|
||||||
|
match self {
|
||||||
|
Self::None => AnyBody::None,
|
||||||
|
Self::Bytes { body: bytes } => AnyBody::Bytes { body: bytes },
|
||||||
|
Self::Body { body } => AnyBody::new_boxed(body),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> MessageBody for AnyBody<B>
|
||||||
|
where
|
||||||
|
B: MessageBody,
|
||||||
|
{
|
||||||
|
type Error = crate::BoxError;
|
||||||
|
|
||||||
|
fn size(&self) -> BodySize {
|
||||||
|
match self {
|
||||||
|
AnyBody::None => BodySize::None,
|
||||||
|
AnyBody::Bytes { ref body } => BodySize::Sized(body.len() as u64),
|
||||||
|
AnyBody::Body { ref body } => body.size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
match self.project() {
|
||||||
|
AnyBodyProj::None => Poll::Ready(None),
|
||||||
|
AnyBodyProj::Bytes { body } => {
|
||||||
|
let len = body.len();
|
||||||
|
if len == 0 {
|
||||||
|
Poll::Ready(None)
|
||||||
|
} else {
|
||||||
|
Poll::Ready(Some(Ok(mem::take(body))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnyBodyProj::Body { body } => body.poll_next(cx).map_err(|err| err.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for AnyBody {
|
||||||
|
fn eq(&self, other: &AnyBody) -> bool {
|
||||||
|
match self {
|
||||||
|
AnyBody::None => matches!(*other, AnyBody::None),
|
||||||
|
AnyBody::Bytes { body } => match other {
|
||||||
|
AnyBody::Bytes { body: b2 } => body == b2,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
AnyBody::Body { .. } => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: fmt::Debug> fmt::Debug for AnyBody<S> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
AnyBody::None => write!(f, "AnyBody::None"),
|
||||||
|
AnyBody::Bytes { ref body } => write!(f, "AnyBody::Bytes({:?})", body),
|
||||||
|
AnyBody::Body { ref body } => write!(f, "AnyBody::Message({:?})", body),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<&'static str> for AnyBody<B> {
|
||||||
|
fn from(string: &'static str) -> Self {
|
||||||
|
Self::Bytes {
|
||||||
|
body: Bytes::from_static(string.as_ref()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<&'static [u8]> for AnyBody<B> {
|
||||||
|
fn from(bytes: &'static [u8]) -> Self {
|
||||||
|
Self::Bytes {
|
||||||
|
body: Bytes::from_static(bytes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<Vec<u8>> for AnyBody<B> {
|
||||||
|
fn from(vec: Vec<u8>) -> Self {
|
||||||
|
Self::Bytes {
|
||||||
|
body: Bytes::from(vec),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<String> for AnyBody<B> {
|
||||||
|
fn from(string: String) -> Self {
|
||||||
|
Self::Bytes {
|
||||||
|
body: Bytes::from(string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<&'_ String> for AnyBody<B> {
|
||||||
|
fn from(string: &String) -> Self {
|
||||||
|
Self::Bytes {
|
||||||
|
body: Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<Cow<'_, str>> for AnyBody<B> {
|
||||||
|
fn from(string: Cow<'_, str>) -> Self {
|
||||||
|
match string {
|
||||||
|
Cow::Owned(s) => Self::from(s),
|
||||||
|
Cow::Borrowed(s) => Self::Bytes {
|
||||||
|
body: Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<Bytes> for AnyBody<B> {
|
||||||
|
fn from(bytes: Bytes) -> Self {
|
||||||
|
Self::Bytes { body: bytes }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> From<BytesMut> for AnyBody<B> {
|
||||||
|
fn from(bytes: BytesMut) -> Self {
|
||||||
|
Self::Bytes {
|
||||||
|
body: bytes.freeze(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, E> From<SizedStream<S>> for AnyBody
|
||||||
|
where
|
||||||
|
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||||
|
E: Into<BoxError> + 'static,
|
||||||
|
{
|
||||||
|
fn from(stream: SizedStream<S>) -> Self {
|
||||||
|
AnyBody::new_boxed(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, E> From<BodyStream<S>> for AnyBody
|
||||||
|
where
|
||||||
|
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||||
|
E: Into<BoxError> + 'static,
|
||||||
|
{
|
||||||
|
fn from(stream: BodyStream<S>) -> Self {
|
||||||
|
AnyBody::new_boxed(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::marker::PhantomPinned;
|
||||||
|
|
||||||
|
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct PinType(PhantomPinned);
|
||||||
|
|
||||||
|
impl MessageBody for PinType {
|
||||||
|
type Error = crate::BoxError;
|
||||||
|
|
||||||
|
fn size(&self) -> BodySize {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
_cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_impl_all!(AnyBody<()>: MessageBody, fmt::Debug, Send, Sync, Unpin);
|
||||||
|
assert_impl_all!(AnyBody<AnyBody<()>>: MessageBody, fmt::Debug, Send, Sync, Unpin);
|
||||||
|
assert_impl_all!(AnyBody<Bytes>: MessageBody, fmt::Debug, Send, Sync, Unpin);
|
||||||
|
assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Unpin);
|
||||||
|
assert_impl_all!(AnyBody<PinType>: MessageBody);
|
||||||
|
|
||||||
|
assert_not_impl_all!(AnyBody: Send, Sync, Unpin);
|
||||||
|
assert_not_impl_all!(AnyBody<PinType>: Send, Sync, Unpin);
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use std::{error::Error as StdError, fmt, io};
|
use std::{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,11 +118,11 @@ 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)]
|
||||||
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 {}
|
||||||
|
|
|
@ -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))?;
|
||||||
|
|
|
@ -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(());
|
||||||
|
|
|
@ -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<
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
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;
|
||||||
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,
|
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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -95,7 +96,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
let body_opt = match body {
|
let body_opt = match body {
|
||||||
AnyBody::Bytes(ref b) => Some(b.clone()),
|
AnyBody::Bytes { ref body } => Some(body.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -192,7 +193,9 @@ where
|
||||||
let body_new = if is_redirect {
|
let body_new = if is_redirect {
|
||||||
// try to reuse body
|
// try to reuse body
|
||||||
match body {
|
match body {
|
||||||
Some(ref bytes) => AnyBody::Bytes(bytes.clone()),
|
Some(ref bytes) => AnyBody::Bytes {
|
||||||
|
body: bytes.clone(),
|
||||||
|
},
|
||||||
// TODO: should this be AnyBody::Empty or AnyBody::None.
|
// TODO: should this be AnyBody::Empty or AnyBody::None.
|
||||||
_ => AnyBody::empty(),
|
_ => AnyBody::empty(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
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;
|
||||||
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,
|
BoxError, 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
|
||||||
|
@ -404,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,
|
||||||
|
@ -9,12 +8,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,8 +25,9 @@ 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,
|
BoxError, ClientConfig, ClientResponse, ConnectRequest, ConnectResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, From)]
|
#[derive(Debug, From)]
|
||||||
|
@ -162,12 +162,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()))
|
||||||
|
@ -236,7 +230,9 @@ impl RequestSender {
|
||||||
response_decompress,
|
response_decompress,
|
||||||
timeout,
|
timeout,
|
||||||
config,
|
config,
|
||||||
AnyBody::Bytes(Bytes::from(body)),
|
AnyBody::Bytes {
|
||||||
|
body: Bytes::from(body),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,7 +261,9 @@ impl RequestSender {
|
||||||
response_decompress,
|
response_decompress,
|
||||||
timeout,
|
timeout,
|
||||||
config,
|
config,
|
||||||
AnyBody::Bytes(Bytes::from(body)),
|
AnyBody::Bytes {
|
||||||
|
body: Bytes::from(body),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,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,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use std::{future::Future, time::Instant};
|
use std::{future::Future, time::Instant};
|
||||||
|
|
||||||
|
use actix_http::body::BoxBody;
|
||||||
use actix_utils::future::{ready, Ready};
|
use actix_utils::future::{ready, Ready};
|
||||||
use actix_web::http::StatusCode;
|
use actix_web::{
|
||||||
use actix_web::test::TestRequest;
|
error, http::StatusCode, test::TestRequest, Error, HttpRequest, HttpResponse, Responder,
|
||||||
use actix_web::{error, Error, HttpRequest, HttpResponse, Responder};
|
};
|
||||||
use criterion::{criterion_group, criterion_main, Criterion};
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
use futures_util::future::{join_all, Either};
|
use futures_util::future::{join_all, Either};
|
||||||
|
|
||||||
|
@ -50,7 +51,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for StringResponder {
|
impl Responder for StringResponder {
|
||||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
type Body = BoxBody;
|
||||||
|
|
||||||
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
HttpResponse::build(StatusCode::OK)
|
HttpResponse::build(StatusCode::OK)
|
||||||
.content_type("text/plain; charset=utf-8")
|
.content_type("text/plain; charset=utf-8")
|
||||||
.body(self.0)
|
.body(self.0)
|
||||||
|
@ -58,9 +61,11 @@ impl Responder for StringResponder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Responder> Responder for OptionResponder<T> {
|
impl<T: Responder> Responder for OptionResponder<T> {
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
type Body = BoxBody;
|
||||||
|
|
||||||
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
Some(t) => t.respond_to(req),
|
Some(t) => t.respond_to(req).map_into_boxed_body(),
|
||||||
None => HttpResponse::from_error(error::ErrorInternalServerError("err")),
|
None => HttpResponse::from_error(error::ErrorInternalServerError("err")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
56
src/app.rs
56
src/app.rs
|
@ -1,37 +1,35 @@
|
||||||
use std::cell::RefCell;
|
use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, rc::Rc};
|
||||||
use std::fmt;
|
|
||||||
use std::future::Future;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use actix_http::body::{AnyBody, MessageBody};
|
use actix_http::{
|
||||||
use actix_http::{Extensions, Request};
|
body::{BoxBody, MessageBody},
|
||||||
use actix_service::boxed::{self, BoxServiceFactory};
|
Extensions, Request,
|
||||||
|
};
|
||||||
use actix_service::{
|
use actix_service::{
|
||||||
apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform,
|
apply, apply_fn_factory, boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt,
|
||||||
|
Transform,
|
||||||
};
|
};
|
||||||
use futures_util::future::FutureExt as _;
|
use futures_util::future::FutureExt as _;
|
||||||
|
|
||||||
use crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
|
use crate::{
|
||||||
use crate::config::ServiceConfig;
|
app_service::{AppEntry, AppInit, AppRoutingFactory},
|
||||||
use crate::data::{Data, DataFactory, FnDataFactory};
|
config::ServiceConfig,
|
||||||
use crate::dev::ResourceDef;
|
data::{Data, DataFactory, FnDataFactory},
|
||||||
use crate::error::Error;
|
dev::ResourceDef,
|
||||||
use crate::resource::Resource;
|
error::Error,
|
||||||
use crate::route::Route;
|
resource::Resource,
|
||||||
use crate::service::{
|
route::Route,
|
||||||
AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest,
|
service::{
|
||||||
ServiceResponse,
|
AppServiceFactory, BoxedHttpServiceFactory, HttpServiceFactory, ServiceFactoryWrapper,
|
||||||
|
ServiceRequest, ServiceResponse,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
|
||||||
|
|
||||||
/// Application builder - structure that follows the builder pattern
|
/// Application builder - structure that follows the builder pattern
|
||||||
/// for building application instances.
|
/// for building application instances.
|
||||||
pub struct App<T, B> {
|
pub struct App<T, B> {
|
||||||
endpoint: T,
|
endpoint: T,
|
||||||
services: Vec<Box<dyn AppServiceFactory>>,
|
services: Vec<Box<dyn AppServiceFactory>>,
|
||||||
default: Option<Rc<HttpNewService>>,
|
default: Option<Rc<BoxedHttpServiceFactory>>,
|
||||||
factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||||
data_factories: Vec<FnDataFactory>,
|
data_factories: Vec<FnDataFactory>,
|
||||||
external: Vec<ResourceDef>,
|
external: Vec<ResourceDef>,
|
||||||
|
@ -39,7 +37,7 @@ pub struct App<T, B> {
|
||||||
_phantom: PhantomData<B>,
|
_phantom: PhantomData<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App<AppEntry, AnyBody> {
|
impl App<AppEntry, BoxBody> {
|
||||||
/// Create application builder. Application can be configured with a builder-like pattern.
|
/// Create application builder. Application can be configured with a builder-like pattern.
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
@ -287,7 +285,7 @@ where
|
||||||
/// );
|
/// );
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn default_service<F, U>(mut self, f: F) -> Self
|
pub fn default_service<F, U>(mut self, svc: F) -> Self
|
||||||
where
|
where
|
||||||
F: IntoServiceFactory<U, ServiceRequest>,
|
F: IntoServiceFactory<U, ServiceRequest>,
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<
|
||||||
|
@ -298,10 +296,12 @@ where
|
||||||
> + 'static,
|
> + 'static,
|
||||||
U::InitError: fmt::Debug,
|
U::InitError: fmt::Debug,
|
||||||
{
|
{
|
||||||
// create and configure default resource
|
let svc = svc
|
||||||
self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err(
|
.into_factory()
|
||||||
|e| log::error!("Can not construct default service: {:?}", e),
|
.map(|res| res.map_into_boxed_body())
|
||||||
))));
|
.map_init_err(|e| log::error!("Can not construct default service: {:?}", e));
|
||||||
|
|
||||||
|
self.default = Some(Rc::new(boxed::factory(svc)));
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,7 @@ use std::{cell::RefCell, mem, rc::Rc};
|
||||||
|
|
||||||
use actix_http::{Extensions, Request};
|
use actix_http::{Extensions, Request};
|
||||||
use actix_router::{Path, ResourceDef, Router, Url};
|
use actix_router::{Path, ResourceDef, Router, Url};
|
||||||
use actix_service::{
|
use actix_service::{boxed, fn_service, Service, ServiceFactory};
|
||||||
boxed::{self, BoxService, BoxServiceFactory},
|
|
||||||
fn_service, Service, ServiceFactory,
|
|
||||||
};
|
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use futures_util::future::join_all;
|
use futures_util::future::join_all;
|
||||||
|
|
||||||
|
@ -15,13 +12,14 @@ use crate::{
|
||||||
guard::Guard,
|
guard::Guard,
|
||||||
request::{HttpRequest, HttpRequestPool},
|
request::{HttpRequest, HttpRequestPool},
|
||||||
rmap::ResourceMap,
|
rmap::ResourceMap,
|
||||||
service::{AppServiceFactory, ServiceRequest, ServiceResponse},
|
service::{
|
||||||
|
AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, ServiceRequest,
|
||||||
|
ServiceResponse,
|
||||||
|
},
|
||||||
Error, HttpResponse,
|
Error, HttpResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Guards = Vec<Box<dyn Guard>>;
|
type Guards = Vec<Box<dyn Guard>>;
|
||||||
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
|
||||||
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
|
||||||
|
|
||||||
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
|
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
|
||||||
/// It also executes data factories.
|
/// It also executes data factories.
|
||||||
|
@ -39,7 +37,7 @@ where
|
||||||
pub(crate) extensions: RefCell<Option<Extensions>>,
|
pub(crate) extensions: RefCell<Option<Extensions>>,
|
||||||
pub(crate) async_data_factories: Rc<[FnDataFactory]>,
|
pub(crate) async_data_factories: Rc<[FnDataFactory]>,
|
||||||
pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
|
pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
|
||||||
pub(crate) default: Option<Rc<HttpNewService>>,
|
pub(crate) default: Option<Rc<BoxedHttpServiceFactory>>,
|
||||||
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||||
pub(crate) external: RefCell<Vec<ResourceDef>>,
|
pub(crate) external: RefCell<Vec<ResourceDef>>,
|
||||||
}
|
}
|
||||||
|
@ -230,8 +228,14 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppRoutingFactory {
|
pub struct AppRoutingFactory {
|
||||||
services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>,
|
services: Rc<
|
||||||
default: Rc<HttpNewService>,
|
[(
|
||||||
|
ResourceDef,
|
||||||
|
BoxedHttpServiceFactory,
|
||||||
|
RefCell<Option<Guards>>,
|
||||||
|
)],
|
||||||
|
>,
|
||||||
|
default: Rc<BoxedHttpServiceFactory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
||||||
|
@ -279,8 +283,8 @@ impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
||||||
|
|
||||||
/// The Actix Web router default entry point.
|
/// The Actix Web router default entry point.
|
||||||
pub struct AppRouting {
|
pub struct AppRouting {
|
||||||
router: Router<HttpService, Guards>,
|
router: Router<BoxedHttpService, Guards>,
|
||||||
default: HttpService,
|
default: BoxedHttpService,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<ServiceRequest> for AppRouting {
|
impl Service<ServiceRequest> for AppRouting {
|
||||||
|
|
|
@ -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]
|
||||||
|
|
53
src/dev.rs
53
src/dev.rs
|
@ -1,7 +1,7 @@
|
||||||
//! Lower-level types and re-exports.
|
//! Lower-level types and re-exports.
|
||||||
//!
|
//!
|
||||||
//! Most users will not have to interact with the types in this module, but it is useful for those
|
//! Most users will not have to interact with the types in this module, but it is useful for those
|
||||||
//! writing extractors, middleware and libraries, or interacting with the service API directly.
|
//! writing extractors, middleware, libraries, or interacting with the service API directly.
|
||||||
|
|
||||||
pub use crate::config::{AppConfig, AppService};
|
pub use crate::config::{AppConfig, AppService};
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -14,11 +14,6 @@ pub use crate::types::form::UrlEncoded;
|
||||||
pub use crate::types::json::JsonBody;
|
pub use crate::types::json::JsonBody;
|
||||||
pub use crate::types::readlines::Readlines;
|
pub use crate::types::readlines::Readlines;
|
||||||
|
|
||||||
#[allow(deprecated)]
|
|
||||||
pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, SizedStream};
|
|
||||||
|
|
||||||
#[cfg(feature = "__compress")]
|
|
||||||
pub use actix_http::encoding::Decoder as Decompress;
|
|
||||||
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};
|
||||||
pub use actix_server::{Server, ServerHandle};
|
pub use actix_server::{Server, ServerHandle};
|
||||||
|
@ -26,8 +21,10 @@ pub use actix_service::{
|
||||||
always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform,
|
always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "__compress")]
|
||||||
|
pub use actix_http::encoding::Decoder as Decompress;
|
||||||
|
|
||||||
use crate::http::header::ContentEncoding;
|
use crate::http::header::ContentEncoding;
|
||||||
use actix_http::ResponseBuilder;
|
|
||||||
|
|
||||||
use actix_router::Patterns;
|
use actix_router::Patterns;
|
||||||
|
|
||||||
|
@ -62,7 +59,7 @@ pub trait BodyEncoding {
|
||||||
fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self;
|
fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BodyEncoding for ResponseBuilder {
|
impl BodyEncoding for actix_http::ResponseBuilder {
|
||||||
fn get_encoding(&self) -> Option<ContentEncoding> {
|
fn get_encoding(&self) -> Option<ContentEncoding> {
|
||||||
self.extensions().get::<Enc>().map(|enc| enc.0)
|
self.extensions().get::<Enc>().map(|enc| enc.0)
|
||||||
}
|
}
|
||||||
|
@ -73,7 +70,7 @@ impl BodyEncoding for ResponseBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B> BodyEncoding for Response<B> {
|
impl<B> BodyEncoding for actix_http::Response<B> {
|
||||||
fn get_encoding(&self) -> Option<ContentEncoding> {
|
fn get_encoding(&self) -> Option<ContentEncoding> {
|
||||||
self.extensions().get::<Enc>().map(|enc| enc.0)
|
self.extensions().get::<Enc>().map(|enc| enc.0)
|
||||||
}
|
}
|
||||||
|
@ -105,3 +102,41 @@ impl<B> BodyEncoding for crate::HttpResponse<B> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove this if it doesn't appear to be needed
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum AnyBody {
|
||||||
|
None,
|
||||||
|
Full { body: crate::web::Bytes },
|
||||||
|
Boxed { body: actix_http::body::BoxBody },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::body::MessageBody for AnyBody {
|
||||||
|
type Error = crate::BoxError;
|
||||||
|
|
||||||
|
/// Body size hint.
|
||||||
|
fn size(&self) -> crate::body::BodySize {
|
||||||
|
match self {
|
||||||
|
AnyBody::None => crate::body::BodySize::None,
|
||||||
|
AnyBody::Full { body } => body.size(),
|
||||||
|
AnyBody::Boxed { body } => body.size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to pull out the next chunk of body bytes.
|
||||||
|
fn poll_next(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Option<Result<crate::web::Bytes, Self::Error>>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
AnyBody::None => std::task::Poll::Ready(None),
|
||||||
|
AnyBody::Full { body } => {
|
||||||
|
let bytes = std::mem::take(body);
|
||||||
|
std::task::Poll::Ready(Some(Ok(bytes)))
|
||||||
|
}
|
||||||
|
AnyBody::Boxed { body } => body.as_pin_mut().poll_next(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{error::Error as StdError, fmt};
|
use std::{error::Error as StdError, fmt};
|
||||||
|
|
||||||
use actix_http::{body::AnyBody, Response};
|
use actix_http::{body::BoxBody, Response};
|
||||||
|
|
||||||
use crate::{HttpResponse, ResponseError};
|
use crate::{HttpResponse, ResponseError};
|
||||||
|
|
||||||
|
@ -69,8 +69,8 @@ impl<T: ResponseError + 'static> From<T> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for Response<AnyBody> {
|
impl From<Error> for Response<BoxBody> {
|
||||||
fn from(err: Error) -> Response<AnyBody> {
|
fn from(err: Error) -> Response<BoxBody> {
|
||||||
err.error_response().into()
|
err.error_response().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
use std::{cell::RefCell, fmt, io::Write as _};
|
use std::{cell::RefCell, fmt, io::Write as _};
|
||||||
|
|
||||||
use actix_http::{body::AnyBody, header, StatusCode};
|
use actix_http::{
|
||||||
|
body::BoxBody,
|
||||||
|
header::{self, IntoHeaderValue as _},
|
||||||
|
StatusCode,
|
||||||
|
};
|
||||||
use bytes::{BufMut as _, BytesMut};
|
use bytes::{BufMut as _, BytesMut};
|
||||||
|
|
||||||
use crate::{Error, HttpRequest, HttpResponse, Responder, ResponseError};
|
use crate::{Error, HttpRequest, HttpResponse, Responder, ResponseError};
|
||||||
|
@ -84,11 +88,10 @@ where
|
||||||
let mut buf = BytesMut::new().writer();
|
let mut buf = BytesMut::new().writer();
|
||||||
let _ = write!(buf, "{}", self);
|
let _ = write!(buf, "{}", self);
|
||||||
|
|
||||||
res.headers_mut().insert(
|
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||||
header::CONTENT_TYPE,
|
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||||
header::HeaderValue::from_static("text/plain; charset=utf-8"),
|
|
||||||
);
|
res.set_body(BoxBody::new(buf.into_inner()))
|
||||||
res.set_body(AnyBody::from(buf.into_inner()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InternalErrorType::Response(ref resp) => {
|
InternalErrorType::Response(ref resp) => {
|
||||||
|
@ -106,7 +109,9 @@ impl<T> Responder for InternalError<T>
|
||||||
where
|
where
|
||||||
T: fmt::Debug + fmt::Display + 'static,
|
T: fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
type Body = BoxBody;
|
||||||
|
|
||||||
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
HttpResponse::from_error(self)
|
HttpResponse::from_error(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ mod tests {
|
||||||
let resp_body: &mut dyn MB = &mut body;
|
let resp_body: &mut dyn MB = &mut body;
|
||||||
let body = resp_body.downcast_ref::<String>().unwrap();
|
let body = resp_body.downcast_ref::<String>().unwrap();
|
||||||
assert_eq!(body, "hello cast");
|
assert_eq!(body, "hello cast");
|
||||||
let body = &mut resp_body.downcast_mut::<String>().unwrap();
|
let body = resp_body.downcast_mut::<String>().unwrap();
|
||||||
body.push('!');
|
body.push('!');
|
||||||
let body = resp_body.downcast_ref::<String>().unwrap();
|
let body = resp_body.downcast_ref::<String>().unwrap();
|
||||||
assert_eq!(body, "hello cast!");
|
assert_eq!(body, "hello cast!");
|
||||||
|
|
|
@ -6,11 +6,17 @@ use std::{
|
||||||
io::{self, Write as _},
|
io::{self, Write as _},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{body::AnyBody, header, Response, StatusCode};
|
use actix_http::{
|
||||||
|
body::BoxBody,
|
||||||
|
header::{self, IntoHeaderValue},
|
||||||
|
Response, StatusCode,
|
||||||
|
};
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
|
|
||||||
use crate::error::{downcast_dyn, downcast_get_type_id};
|
use crate::{
|
||||||
use crate::{helpers, HttpResponse};
|
error::{downcast_dyn, downcast_get_type_id},
|
||||||
|
helpers, HttpResponse,
|
||||||
|
};
|
||||||
|
|
||||||
/// Errors that can generate responses.
|
/// Errors that can generate responses.
|
||||||
// TODO: add std::error::Error bound when replacement for Box<dyn Error> is found
|
// TODO: add std::error::Error bound when replacement for Box<dyn Error> is found
|
||||||
|
@ -27,18 +33,16 @@ pub trait ResponseError: fmt::Debug + fmt::Display {
|
||||||
///
|
///
|
||||||
/// By default, the generated response uses a 500 Internal Server Error status code, a
|
/// By default, the generated response uses a 500 Internal Server Error status code, a
|
||||||
/// `Content-Type` of `text/plain`, and the body is set to `Self`'s `Display` impl.
|
/// `Content-Type` of `text/plain`, and the body is set to `Self`'s `Display` impl.
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||||
let mut res = HttpResponse::new(self.status_code());
|
let mut res = HttpResponse::new(self.status_code());
|
||||||
|
|
||||||
let mut buf = BytesMut::new();
|
let mut buf = BytesMut::new();
|
||||||
let _ = write!(helpers::MutWriter(&mut buf), "{}", self);
|
let _ = write!(helpers::MutWriter(&mut buf), "{}", self);
|
||||||
|
|
||||||
res.headers_mut().insert(
|
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||||
header::CONTENT_TYPE,
|
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||||
header::HeaderValue::from_static("text/plain; charset=utf-8"),
|
|
||||||
);
|
|
||||||
|
|
||||||
res.set_body(AnyBody::from(buf))
|
res.set_body(BoxBody::new(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
downcast_get_type_id!();
|
downcast_get_type_id!();
|
||||||
|
@ -86,8 +90,8 @@ impl ResponseError for actix_http::Error {
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||||
HttpResponse::new(self.status_code()).set_body(self.to_string().into())
|
HttpResponse::with_body(self.status_code(), self.to_string()).map_into_boxed_body()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,8 +127,8 @@ impl ResponseError for actix_http::error::ContentTypeError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseError for actix_http::ws::HandshakeError {
|
impl ResponseError for actix_http::ws::HandshakeError {
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||||
Response::from(self).into()
|
Response::from(self).map_into_boxed_body().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use actix_service::{
|
use actix_service::{boxed, fn_service};
|
||||||
boxed::{self, BoxServiceFactory},
|
|
||||||
fn_service,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
service::{ServiceRequest, ServiceResponse},
|
body::MessageBody,
|
||||||
Error, FromRequest, HttpResponse, Responder,
|
service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse},
|
||||||
|
BoxError, FromRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A request handler is an async function that accepts zero or more parameters that can be
|
/// A request handler is an async function that accepts zero or more parameters that can be
|
||||||
/// extracted from a request (i.e., [`impl FromRequest`](crate::FromRequest)) and returns a type
|
/// extracted from a request (i.e., [`impl FromRequest`]) and returns a type that can be converted
|
||||||
/// that can be converted into an [`HttpResponse`] (that is, it impls the [`Responder`] trait).
|
/// into an [`HttpResponse`] (that is, it impls the [`Responder`] trait).
|
||||||
///
|
///
|
||||||
/// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not
|
/// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not
|
||||||
/// a valid handler. See [Request Handlers](https://actix.rs/docs/handlers/) for more information.
|
/// a valid handler. See <https://actix.rs/docs/handlers> for more information.
|
||||||
|
///
|
||||||
|
/// [`impl FromRequest`]: crate::FromRequest
|
||||||
pub trait Handler<T, R>: Clone + 'static
|
pub trait Handler<T, R>: Clone + 'static
|
||||||
where
|
where
|
||||||
R: Future,
|
R: Future,
|
||||||
|
@ -24,29 +24,44 @@ where
|
||||||
fn call(&self, param: T) -> R;
|
fn call(&self, param: T) -> R;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handler_service<F, T, R>(
|
pub(crate) fn handler_service<F, T, R>(handler: F) -> BoxedHttpServiceFactory
|
||||||
handler: F,
|
|
||||||
) -> BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>
|
|
||||||
where
|
where
|
||||||
F: Handler<T, R>,
|
F: Handler<T, R>,
|
||||||
T: FromRequest,
|
T: FromRequest,
|
||||||
R: Future,
|
R: Future,
|
||||||
R::Output: Responder,
|
R::Output: Responder,
|
||||||
|
<R::Output as Responder>::Body: MessageBody,
|
||||||
|
<<R::Output as Responder>::Body as MessageBody>::Error: Into<BoxError>,
|
||||||
{
|
{
|
||||||
boxed::factory(fn_service(move |req: ServiceRequest| {
|
boxed::factory(fn_service(move |req: ServiceRequest| {
|
||||||
let handler = handler.clone();
|
let handler = handler.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let (req, mut payload) = req.into_parts();
|
let (req, mut payload) = req.into_parts();
|
||||||
|
|
||||||
let res = match T::from_request(&req, &mut payload).await {
|
let res = match T::from_request(&req, &mut payload).await {
|
||||||
Err(err) => HttpResponse::from_error(err),
|
Err(err) => HttpResponse::from_error(err),
|
||||||
Ok(data) => handler.call(data).await.respond_to(&req),
|
|
||||||
|
Ok(data) => handler
|
||||||
|
.call(data)
|
||||||
|
.await
|
||||||
|
.respond_to(&req)
|
||||||
|
.map_into_boxed_body(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ServiceResponse::new(req, res))
|
Ok(ServiceResponse::new(req, res))
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FromRequest trait impl for tuples
|
/// Generates a [`Handler`] trait impl for N-ary functions where N is specified with a sequence of
|
||||||
|
/// space separated type parameters.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```ignore
|
||||||
|
/// factory_tuple! {} // implements Handler for types: fn() -> Res
|
||||||
|
/// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> Res
|
||||||
|
/// ```
|
||||||
macro_rules! factory_tuple ({ $($param:ident)* } => {
|
macro_rules! factory_tuple ({ $($param:ident)* } => {
|
||||||
impl<Func, $($param,)* Res> Handler<($($param,)*), Res> for Func
|
impl<Func, $($param,)* Res> Handler<($($param,)*), Res> for Func
|
||||||
where Func: Fn($($param),*) -> Res + Clone + 'static,
|
where Func: Fn($($param),*) -> Res + Clone + 'static,
|
||||||
|
|
|
@ -208,7 +208,7 @@ impl Accept {
|
||||||
/// If no q-factors are provided, the first mime type is chosen. Note that items without
|
/// If no q-factors are provided, the first mime type is chosen. Note that items without
|
||||||
/// q-factors are given the maximum preference value.
|
/// q-factors are given the maximum preference value.
|
||||||
///
|
///
|
||||||
/// As per the spec, will return [`Mime::STAR_STAR`] (indicating no preference) if the contained
|
/// As per the spec, will return [`mime::STAR_STAR`] (indicating no preference) if the contained
|
||||||
/// list is empty.
|
/// list is empty.
|
||||||
///
|
///
|
||||||
/// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2
|
/// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2
|
||||||
|
|
|
@ -115,3 +115,5 @@ pub use crate::scope::Scope;
|
||||||
pub use crate::server::HttpServer;
|
pub use crate::server::HttpServer;
|
||||||
// TODO: is exposing the error directly really needed
|
// TODO: is exposing the error directly really needed
|
||||||
pub use crate::types::{Either, EitherExtractError};
|
pub use crate::types::{Either, EitherExtractError};
|
||||||
|
|
||||||
|
pub(crate) type BoxError = Box<dyn std::error::Error>;
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
//! For middleware documentation, see [`Compat`].
|
//! For middleware documentation, see [`Compat`].
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
error::Error as StdError,
|
|
||||||
future::Future,
|
future::Future,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::body::{AnyBody, MessageBody};
|
use actix_http::body::MessageBody;
|
||||||
use actix_service::{Service, Transform};
|
use actix_service::{Service, Transform};
|
||||||
use futures_core::{future::LocalBoxFuture, ready};
|
use futures_core::{future::LocalBoxFuture, ready};
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
@ -123,10 +122,9 @@ pub trait MapServiceResponseBody {
|
||||||
impl<B> MapServiceResponseBody for ServiceResponse<B>
|
impl<B> MapServiceResponseBody for ServiceResponse<B>
|
||||||
where
|
where
|
||||||
B: MessageBody + Unpin + 'static,
|
B: MessageBody + Unpin + 'static,
|
||||||
B::Error: Into<Box<dyn StdError + 'static>>,
|
|
||||||
{
|
{
|
||||||
fn map_body(self) -> ServiceResponse {
|
fn map_body(self) -> ServiceResponse {
|
||||||
self.map_body(|_, body| AnyBody::new_boxed(body))
|
self.map_into_boxed_body()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,13 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::{AnyBody, MessageBody},
|
body::{EitherBody, MessageBody},
|
||||||
encoding::Encoder,
|
encoding::Encoder,
|
||||||
http::header::{ContentEncoding, ACCEPT_ENCODING},
|
http::header::{ContentEncoding, ACCEPT_ENCODING},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
};
|
};
|
||||||
use actix_service::{Service, Transform};
|
use actix_service::{Service, Transform};
|
||||||
use actix_utils::future::{ok, Either, Ready};
|
use actix_utils::future::{ok, Either, Ready};
|
||||||
use bytes::Bytes;
|
|
||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
@ -62,7 +61,7 @@ where
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
{
|
{
|
||||||
type Response = ServiceResponse<AnyBody<Encoder<B>>>;
|
type Response = ServiceResponse<EitherBody<Encoder<B>>>;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Transform = CompressMiddleware<S>;
|
type Transform = CompressMiddleware<S>;
|
||||||
type InitError = ();
|
type InitError = ();
|
||||||
|
@ -112,7 +111,7 @@ where
|
||||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
{
|
{
|
||||||
type Response = ServiceResponse<AnyBody<Encoder<B>>>;
|
type Response = ServiceResponse<EitherBody<Encoder<B>>>;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Either<CompressResponse<S, B>, Ready<Result<Self::Response, Self::Error>>>;
|
type Future = Either<CompressResponse<S, B>, Ready<Result<Self::Response, Self::Error>>>;
|
||||||
|
|
||||||
|
@ -144,19 +143,15 @@ where
|
||||||
|
|
||||||
// There is an HTTP header but we cannot match what client as asked for
|
// There is an HTTP header but we cannot match what client as asked for
|
||||||
Some(Err(_)) => {
|
Some(Err(_)) => {
|
||||||
let res = HttpResponse::new(StatusCode::NOT_ACCEPTABLE);
|
let res = HttpResponse::with_body(
|
||||||
|
StatusCode::NOT_ACCEPTABLE,
|
||||||
|
SUPPORTED_ALGORITHM_NAMES.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
let res: HttpResponse<AnyBody<Encoder<B>>> = res.map_body(move |head, _| {
|
Either::right(ok(req
|
||||||
let body_bytes = Bytes::from(SUPPORTED_ALGORITHM_NAMES.as_bytes());
|
.into_response(res)
|
||||||
|
.map_into_boxed_body()
|
||||||
Encoder::response(
|
.map_into_right_body()))
|
||||||
ContentEncoding::Identity,
|
|
||||||
head,
|
|
||||||
AnyBody::Bytes(body_bytes),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
Either::right(ok(req.into_response(res)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,7 +174,7 @@ where
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
{
|
{
|
||||||
type Output = Result<ServiceResponse<AnyBody<Encoder<B>>>, Error>;
|
type Output = Result<ServiceResponse<EitherBody<Encoder<B>>>, Error>;
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
let this = self.project();
|
let this = self.project();
|
||||||
|
@ -193,10 +188,11 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
Poll::Ready(Ok(resp.map_body(move |head, body| {
|
Poll::Ready(Ok(resp.map_body(move |head, body| {
|
||||||
Encoder::response(enc, head, AnyBody::Body(body))
|
EitherBody::left(Encoder::response(enc, head, body))
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
Err(e) => Poll::Ready(Err(e)),
|
|
||||||
|
Err(err) => Poll::Ready(Err(err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ use regex::{Regex, RegexSet};
|
||||||
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dev::{BodySize, MessageBody},
|
body::{BodySize, MessageBody},
|
||||||
http::HeaderName,
|
http::HeaderName,
|
||||||
service::{ServiceRequest, ServiceResponse},
|
service::{ServiceRequest, ServiceResponse},
|
||||||
Error, HttpResponse, Result,
|
Error, HttpResponse, Result,
|
||||||
|
|
|
@ -1,32 +1,29 @@
|
||||||
use std::cell::RefCell;
|
use std::{cell::RefCell, fmt, future::Future, rc::Rc};
|
||||||
use std::fmt;
|
|
||||||
use std::future::Future;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use actix_http::Extensions;
|
use actix_http::Extensions;
|
||||||
use actix_router::{IntoPatterns, Patterns};
|
use actix_router::{IntoPatterns, Patterns};
|
||||||
use actix_service::boxed::{self, BoxService, BoxServiceFactory};
|
|
||||||
use actix_service::{
|
use actix_service::{
|
||||||
apply, apply_fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory,
|
apply, apply_fn_factory, boxed, fn_service, IntoServiceFactory, Service, ServiceFactory,
|
||||||
ServiceFactoryExt, Transform,
|
ServiceFactoryExt, Transform,
|
||||||
};
|
};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use futures_util::future::join_all;
|
use futures_util::future::join_all;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
body::MessageBody,
|
||||||
data::Data,
|
data::Data,
|
||||||
dev::{ensure_leading_slash, AppService, HttpServiceFactory, ResourceDef},
|
dev::{ensure_leading_slash, AppService, ResourceDef},
|
||||||
guard::Guard,
|
guard::Guard,
|
||||||
handler::Handler,
|
handler::Handler,
|
||||||
responder::Responder,
|
responder::Responder,
|
||||||
route::{Route, RouteService},
|
route::{Route, RouteService},
|
||||||
service::{ServiceRequest, ServiceResponse},
|
service::{
|
||||||
Error, FromRequest, HttpResponse,
|
BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest,
|
||||||
|
ServiceResponse,
|
||||||
|
},
|
||||||
|
BoxError, Error, FromRequest, HttpResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
|
||||||
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
|
||||||
|
|
||||||
/// *Resource* is an entry in resources table which corresponds to requested URL.
|
/// *Resource* is an entry in resources table which corresponds to requested URL.
|
||||||
///
|
///
|
||||||
/// Resource in turn has at least one route.
|
/// Resource in turn has at least one route.
|
||||||
|
@ -56,7 +53,7 @@ pub struct Resource<T = ResourceEndpoint> {
|
||||||
routes: Vec<Route>,
|
routes: Vec<Route>,
|
||||||
app_data: Option<Extensions>,
|
app_data: Option<Extensions>,
|
||||||
guards: Vec<Box<dyn Guard>>,
|
guards: Vec<Box<dyn Guard>>,
|
||||||
default: HttpNewService,
|
default: BoxedHttpServiceFactory,
|
||||||
factory_ref: Rc<RefCell<Option<ResourceFactory>>>,
|
factory_ref: Rc<RefCell<Option<ResourceFactory>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,6 +239,8 @@ where
|
||||||
I: FromRequest + 'static,
|
I: FromRequest + 'static,
|
||||||
R: Future + 'static,
|
R: Future + 'static,
|
||||||
R::Output: Responder + 'static,
|
R::Output: Responder + 'static,
|
||||||
|
<R::Output as Responder>::Body: MessageBody,
|
||||||
|
<<R::Output as Responder>::Body as MessageBody>::Error: Into<BoxError>,
|
||||||
{
|
{
|
||||||
self.routes.push(Route::new().to(handler));
|
self.routes.push(Route::new().to(handler));
|
||||||
self
|
self
|
||||||
|
@ -422,7 +421,7 @@ where
|
||||||
|
|
||||||
pub struct ResourceFactory {
|
pub struct ResourceFactory {
|
||||||
routes: Vec<Route>,
|
routes: Vec<Route>,
|
||||||
default: HttpNewService,
|
default: BoxedHttpServiceFactory,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceFactory<ServiceRequest> for ResourceFactory {
|
impl ServiceFactory<ServiceRequest> for ResourceFactory {
|
||||||
|
@ -454,7 +453,7 @@ impl ServiceFactory<ServiceRequest> for ResourceFactory {
|
||||||
|
|
||||||
pub struct ResourceService {
|
pub struct ResourceService {
|
||||||
routes: Vec<RouteService>,
|
routes: Vec<RouteService>,
|
||||||
default: HttpService,
|
default: BoxedHttpService,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<ServiceRequest> for ResourceService {
|
impl Service<ServiceRequest> for ResourceService {
|
||||||
|
|
299
src/responder.rs
299
src/responder.rs
|
@ -1,19 +1,21 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::AnyBody,
|
body::{BoxBody, EitherBody, MessageBody},
|
||||||
http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode},
|
http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode},
|
||||||
};
|
};
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
||||||
use crate::{Error, HttpRequest, HttpResponse, HttpResponseBuilder};
|
use crate::{BoxError, Error, HttpRequest, HttpResponse, HttpResponseBuilder};
|
||||||
|
|
||||||
/// Trait implemented by types that can be converted to an HTTP response.
|
/// Trait implemented by types that can be converted to an HTTP response.
|
||||||
///
|
///
|
||||||
/// Any types that implement this trait can be used in the return type of a handler.
|
/// Any types that implement this trait can be used in the return type of a handler.
|
||||||
pub trait Responder {
|
pub trait Responder {
|
||||||
|
type Body: MessageBody + 'static;
|
||||||
|
|
||||||
/// Convert self to `HttpResponse`.
|
/// Convert self to `HttpResponse`.
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse;
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body>;
|
||||||
|
|
||||||
/// Override a status code for a Responder.
|
/// Override a status code for a Responder.
|
||||||
///
|
///
|
||||||
|
@ -59,38 +61,52 @@ pub trait Responder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for HttpResponse {
|
impl Responder for HttpResponse {
|
||||||
|
type Body = BoxBody;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for actix_http::Response<AnyBody> {
|
impl Responder for actix_http::Response<BoxBody> {
|
||||||
|
type Body = BoxBody;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
HttpResponse::from(self)
|
HttpResponse::from(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for HttpResponseBuilder {
|
impl Responder for HttpResponseBuilder {
|
||||||
|
type Body = BoxBody;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn respond_to(mut self, _: &HttpRequest) -> HttpResponse {
|
fn respond_to(mut self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
self.finish()
|
self.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for actix_http::ResponseBuilder {
|
impl Responder for actix_http::ResponseBuilder {
|
||||||
|
type Body = BoxBody;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn respond_to(mut self, _: &HttpRequest) -> HttpResponse {
|
fn respond_to(mut self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
HttpResponse::from(self.finish())
|
self.finish().map_into_boxed_body().respond_to(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Responder> Responder for Option<T> {
|
impl<T> Responder for Option<T>
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
where
|
||||||
|
T: Responder,
|
||||||
|
<T::Body as MessageBody>::Error: Into<BoxError>,
|
||||||
|
{
|
||||||
|
type Body = EitherBody<T::Body>;
|
||||||
|
|
||||||
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
match self {
|
match self {
|
||||||
Some(val) => val.respond_to(req),
|
Some(val) => val.respond_to(req).map_into_left_body(),
|
||||||
None => HttpResponse::new(StatusCode::NOT_FOUND),
|
None => HttpResponse::new(StatusCode::NOT_FOUND).map_into_right_body(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,47 +114,69 @@ impl<T: Responder> Responder for Option<T> {
|
||||||
impl<T, E> Responder for Result<T, E>
|
impl<T, E> Responder for Result<T, E>
|
||||||
where
|
where
|
||||||
T: Responder,
|
T: Responder,
|
||||||
|
<T::Body as MessageBody>::Error: Into<BoxError>,
|
||||||
E: Into<Error>,
|
E: Into<Error>,
|
||||||
{
|
{
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
type Body = EitherBody<T::Body>;
|
||||||
|
|
||||||
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
match self {
|
match self {
|
||||||
Ok(val) => val.respond_to(req),
|
Ok(val) => val.respond_to(req).map_into_left_body(),
|
||||||
Err(e) => HttpResponse::from_error(e.into()),
|
Err(err) => HttpResponse::from_error(err.into()).map_into_right_body(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Responder> Responder for (T, StatusCode) {
|
impl<T: Responder> Responder for (T, StatusCode) {
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
type Body = T::Body;
|
||||||
|
|
||||||
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
let mut res = self.0.respond_to(req);
|
let mut res = self.0.respond_to(req);
|
||||||
*res.status_mut() = self.1;
|
*res.status_mut() = self.1;
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_responder {
|
macro_rules! impl_responder_by_forward_into_base_response {
|
||||||
($res: ty, $ct: path) => {
|
($res:ty, $body:ty) => {
|
||||||
impl Responder for $res {
|
impl Responder for $res {
|
||||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
type Body = $body;
|
||||||
HttpResponse::Ok().content_type($ct).body(self)
|
|
||||||
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
|
let res: actix_http::Response<_> = self.into();
|
||||||
|
res.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($res:ty) => {
|
||||||
|
impl_responder_by_forward_into_base_response!($res, $res);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_responder_by_forward_into_base_response!(&'static [u8]);
|
||||||
|
impl_responder_by_forward_into_base_response!(Bytes);
|
||||||
|
impl_responder_by_forward_into_base_response!(BytesMut);
|
||||||
|
|
||||||
|
impl_responder_by_forward_into_base_response!(&'static str);
|
||||||
|
impl_responder_by_forward_into_base_response!(String);
|
||||||
|
|
||||||
|
macro_rules! impl_into_string_responder {
|
||||||
|
($res:ty) => {
|
||||||
|
impl Responder for $res {
|
||||||
|
type Body = String;
|
||||||
|
|
||||||
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
|
let string: String = self.into();
|
||||||
|
let res: actix_http::Response<_> = string.into();
|
||||||
|
res.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_responder!(&'static str, mime::TEXT_PLAIN_UTF_8);
|
impl_into_string_responder!(&'_ String);
|
||||||
|
impl_into_string_responder!(Cow<'_, str>);
|
||||||
impl_responder!(String, mime::TEXT_PLAIN_UTF_8);
|
|
||||||
|
|
||||||
impl_responder!(&'_ String, mime::TEXT_PLAIN_UTF_8);
|
|
||||||
|
|
||||||
impl_responder!(Cow<'_, str>, mime::TEXT_PLAIN_UTF_8);
|
|
||||||
|
|
||||||
impl_responder!(&'static [u8], mime::APPLICATION_OCTET_STREAM);
|
|
||||||
|
|
||||||
impl_responder!(Bytes, mime::APPLICATION_OCTET_STREAM);
|
|
||||||
|
|
||||||
impl_responder!(BytesMut, mime::APPLICATION_OCTET_STREAM);
|
|
||||||
|
|
||||||
/// Allows overriding status code and headers for a responder.
|
/// Allows overriding status code and headers for a responder.
|
||||||
pub struct CustomResponder<T> {
|
pub struct CustomResponder<T> {
|
||||||
|
@ -204,11 +242,17 @@ impl<T: Responder> CustomResponder<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Responder> Responder for CustomResponder<T> {
|
impl<T> Responder for CustomResponder<T>
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
where
|
||||||
|
T: Responder,
|
||||||
|
<T::Body as MessageBody>::Error: Into<BoxError>,
|
||||||
|
{
|
||||||
|
type Body = EitherBody<T::Body>;
|
||||||
|
|
||||||
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
let headers = match self.headers {
|
let headers = match self.headers {
|
||||||
Ok(headers) => headers,
|
Ok(headers) => headers,
|
||||||
Err(err) => return HttpResponse::from_error(Error::from(err)),
|
Err(err) => return HttpResponse::from_error(err).map_into_right_body(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut res = self.responder.respond_to(req);
|
let mut res = self.responder.respond_to(req);
|
||||||
|
@ -222,7 +266,7 @@ impl<T: Responder> Responder for CustomResponder<T> {
|
||||||
res.headers_mut().insert(k, v);
|
res.headers_mut().insert(k, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
res
|
res.map_into_left_body()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,11 +275,15 @@ pub(crate) mod tests {
|
||||||
use actix_service::Service;
|
use actix_service::Service;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
||||||
|
use actix_http::body::to_bytes;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::dev::AnyBody;
|
use crate::{
|
||||||
use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
|
error,
|
||||||
use crate::test::{init_service, TestRequest};
|
http::{header::CONTENT_TYPE, HeaderValue, StatusCode},
|
||||||
use crate::{error, web, App};
|
test::{assert_body_eq, init_service, TestRequest},
|
||||||
|
web, App,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_option_responder() {
|
async fn test_option_responder() {
|
||||||
|
@ -253,112 +301,116 @@ pub(crate) mod tests {
|
||||||
let req = TestRequest::with_uri("/some").to_request();
|
let req = TestRequest::with_uri("/some").to_request();
|
||||||
let resp = srv.call(req).await.unwrap();
|
let resp = srv.call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
match resp.response().body() {
|
assert_body_eq!(resp, b"some");
|
||||||
AnyBody::Bytes(ref b) => {
|
|
||||||
let bytes = b.clone();
|
|
||||||
assert_eq!(bytes, Bytes::from_static(b"some"));
|
|
||||||
}
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) trait BodyTest {
|
|
||||||
fn bin_ref(&self) -> &[u8];
|
|
||||||
fn body(&self) -> &AnyBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BodyTest for AnyBody {
|
|
||||||
fn bin_ref(&self) -> &[u8] {
|
|
||||||
match self {
|
|
||||||
AnyBody::Bytes(ref bin) => bin,
|
|
||||||
_ => unreachable!("bug in test impl"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn body(&self) -> &AnyBody {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_responder() {
|
async fn test_responder() {
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
|
|
||||||
let resp = "test".respond_to(&req);
|
let res = "test".respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp = b"test".respond_to(&req);
|
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = b"test".respond_to(&req);
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("application/octet-stream")
|
HeaderValue::from_static("application/octet-stream")
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp = "test".to_string().respond_to(&req);
|
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
Bytes::from_static(b"test"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp = (&"test".to_string()).respond_to(&req);
|
let res = "test".to_string().respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = (&"test".to_string()).respond_to(&req);
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
|
|
||||||
let s = String::from("test");
|
let s = String::from("test");
|
||||||
let resp = Cow::Borrowed(s.as_str()).respond_to(&req);
|
let res = Cow::Borrowed(s.as_str()).respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp = Cow::<'_, str>::Owned(s).respond_to(&req);
|
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
Bytes::from_static(b"test"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp = Cow::Borrowed("test").respond_to(&req);
|
let res = Cow::<'_, str>::Owned(s).respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp = Bytes::from_static(b"test").respond_to(&req);
|
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = Cow::Borrowed("test").respond_to(&req);
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = Bytes::from_static(b"test").respond_to(&req);
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("application/octet-stream")
|
HeaderValue::from_static("application/octet-stream")
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp = BytesMut::from(b"test".as_ref()).respond_to(&req);
|
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = BytesMut::from(b"test".as_ref()).respond_to(&req);
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("application/octet-stream")
|
HeaderValue::from_static("application/octet-stream")
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
|
|
||||||
// InternalError
|
// InternalError
|
||||||
let resp = error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req);
|
let res = error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
@ -368,11 +420,14 @@ pub(crate) mod tests {
|
||||||
// Result<I, E>
|
// Result<I, E>
|
||||||
let resp = Ok::<_, Error>("test".to_string()).respond_to(&req);
|
let resp = Ok::<_, Error>("test".to_string()).respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().bin_ref(), b"test");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
to_bytes(resp.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
|
|
||||||
let res = Err::<String, _>(error::InternalError::new("err", StatusCode::BAD_REQUEST))
|
let res = Err::<String, _>(error::InternalError::new("err", StatusCode::BAD_REQUEST))
|
||||||
.respond_to(&req);
|
.respond_to(&req);
|
||||||
|
@ -389,7 +444,10 @@ pub(crate) mod tests {
|
||||||
.respond_to(&req);
|
.respond_to(&req);
|
||||||
|
|
||||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||||
assert_eq!(res.body().bin_ref(), b"test");
|
assert_eq!(
|
||||||
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
|
|
||||||
let res = "test"
|
let res = "test"
|
||||||
.to_string()
|
.to_string()
|
||||||
|
@ -397,11 +455,14 @@ pub(crate) mod tests {
|
||||||
.respond_to(&req);
|
.respond_to(&req);
|
||||||
|
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(res.body().bin_ref(), b"test");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("json")
|
HeaderValue::from_static("json")
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
@ -409,17 +470,23 @@ pub(crate) mod tests {
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req);
|
let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req);
|
||||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||||
assert_eq!(res.body().bin_ref(), b"test");
|
assert_eq!(
|
||||||
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let res = ("test".to_string(), StatusCode::OK)
|
let res = ("test".to_string(), StatusCode::OK)
|
||||||
.with_header((CONTENT_TYPE, mime::APPLICATION_JSON))
|
.with_header((CONTENT_TYPE, mime::APPLICATION_JSON))
|
||||||
.respond_to(&req);
|
.respond_to(&req);
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(res.body().bin_ref(), b"test");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("application/json")
|
HeaderValue::from_static("application/json")
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefMut},
|
cell::{Ref, RefMut},
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
error::Error as StdError,
|
|
||||||
future::Future,
|
future::Future,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::{AnyBody, BodyStream},
|
body::{BodyStream, BoxBody, MessageBody},
|
||||||
http::{
|
http::{
|
||||||
header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue},
|
header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue},
|
||||||
ConnectionType, Error as HttpError, StatusCode,
|
ConnectionType, Error as HttpError, StatusCode,
|
||||||
|
@ -26,14 +25,14 @@ 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.
|
||||||
///
|
///
|
||||||
/// This type can be used to construct an instance of `Response` through a builder-like pattern.
|
/// This type can be used to construct an instance of `Response` through a builder-like pattern.
|
||||||
pub struct HttpResponseBuilder {
|
pub struct HttpResponseBuilder {
|
||||||
res: Option<Response<AnyBody>>,
|
res: Option<Response<BoxBody>>,
|
||||||
err: Option<HttpError>,
|
err: Option<HttpError>,
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
cookies: Option<CookieJar>,
|
cookies: Option<CookieJar>,
|
||||||
|
@ -44,7 +43,7 @@ impl HttpResponseBuilder {
|
||||||
/// Create response builder
|
/// Create response builder
|
||||||
pub fn new(status: StatusCode) -> Self {
|
pub fn new(status: StatusCode) -> Self {
|
||||||
Self {
|
Self {
|
||||||
res: Some(Response::new(status)),
|
res: Some(Response::with_body(status, BoxBody::new(()))),
|
||||||
err: None,
|
err: None,
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
cookies: None,
|
cookies: None,
|
||||||
|
@ -299,7 +298,6 @@ impl HttpResponseBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutable reference to a the response's extensions
|
/// Mutable reference to a the response's extensions
|
||||||
#[inline]
|
|
||||||
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
||||||
self.res
|
self.res
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
@ -307,18 +305,20 @@ impl HttpResponseBuilder {
|
||||||
.extensions_mut()
|
.extensions_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a body and generate `Response`.
|
/// Set a body and build the `HttpResponse`.
|
||||||
///
|
///
|
||||||
/// `HttpResponseBuilder` can not be used after this call.
|
/// `HttpResponseBuilder` can not be used after this call.
|
||||||
#[inline]
|
pub fn body<B>(&mut self, body: B) -> HttpResponse<BoxBody>
|
||||||
pub fn body<B: Into<AnyBody>>(&mut self, body: B) -> HttpResponse<AnyBody> {
|
where
|
||||||
match self.message_body(body.into()) {
|
B: MessageBody + 'static,
|
||||||
Ok(res) => res,
|
{
|
||||||
|
match self.message_body(body) {
|
||||||
|
Ok(res) => res.map_into_boxed_body(),
|
||||||
Err(err) => HttpResponse::from_error(err),
|
Err(err) => HttpResponse::from_error(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a body and generate `Response`.
|
/// Set a body and build the `HttpResponse`.
|
||||||
///
|
///
|
||||||
/// `HttpResponseBuilder` can not be used after this call.
|
/// `HttpResponseBuilder` can not be used after this call.
|
||||||
pub fn message_body<B>(&mut self, body: B) -> Result<HttpResponse<B>, Error> {
|
pub fn message_body<B>(&mut self, body: B) -> Result<HttpResponse<B>, Error> {
|
||||||
|
@ -332,7 +332,7 @@ impl HttpResponseBuilder {
|
||||||
.expect("cannot reuse response builder")
|
.expect("cannot reuse response builder")
|
||||||
.set_body(body);
|
.set_body(body);
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)] // mut is only unused when cookies are disabled
|
||||||
let mut res = HttpResponse::from(res);
|
let mut res = HttpResponse::from(res);
|
||||||
|
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
|
@ -348,19 +348,19 @@ impl HttpResponseBuilder {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a streaming body and generate `Response`.
|
/// Set a streaming body and build the `HttpResponse`.
|
||||||
///
|
///
|
||||||
/// `HttpResponseBuilder` can not be used after this call.
|
/// `HttpResponseBuilder` can not be used after this call.
|
||||||
#[inline]
|
#[inline]
|
||||||
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(AnyBody::new_boxed(BodyStream::new(stream)))
|
self.body(BodyStream::new(stream))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a json body and generate `Response`
|
/// Set a JSON body and build the `HttpResponse`.
|
||||||
///
|
///
|
||||||
/// `HttpResponseBuilder` can not be used after this call.
|
/// `HttpResponseBuilder` can not be used after this call.
|
||||||
pub fn json(&mut self, value: impl Serialize) -> HttpResponse {
|
pub fn json(&mut self, value: impl Serialize) -> HttpResponse {
|
||||||
|
@ -376,18 +376,18 @@ impl HttpResponseBuilder {
|
||||||
self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
|
self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.body(AnyBody::from(body))
|
self.body(body)
|
||||||
}
|
}
|
||||||
Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)),
|
Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set an empty body and generate `Response`
|
/// Set an empty body and build the `HttpResponse`.
|
||||||
///
|
///
|
||||||
/// `HttpResponseBuilder` can not be used after this call.
|
/// `HttpResponseBuilder` can not be used after this call.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn finish(&mut self) -> HttpResponse {
|
pub fn finish(&mut self) -> HttpResponse {
|
||||||
self.body(AnyBody::empty())
|
self.body(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method construct new `HttpResponseBuilder`
|
/// This method construct new `HttpResponseBuilder`
|
||||||
|
@ -416,7 +416,7 @@ impl From<HttpResponseBuilder> for HttpResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<HttpResponseBuilder> for Response<AnyBody> {
|
impl From<HttpResponseBuilder> for Response<BoxBody> {
|
||||||
fn from(mut builder: HttpResponseBuilder) -> Self {
|
fn from(mut builder: HttpResponseBuilder) -> Self {
|
||||||
builder.finish().into()
|
builder.finish().into()
|
||||||
}
|
}
|
||||||
|
@ -435,12 +435,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]
|
||||||
|
@ -475,7 +472,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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::{AnyBody, MessageBody},
|
body::{BoxBody, EitherBody, MessageBody},
|
||||||
http::{header::HeaderMap, StatusCode},
|
http::{header::HeaderMap, StatusCode},
|
||||||
Extensions, Response, ResponseHead,
|
Extensions, Response, ResponseHead,
|
||||||
};
|
};
|
||||||
|
@ -25,12 +25,12 @@ use {
|
||||||
use crate::{error::Error, HttpResponseBuilder};
|
use crate::{error::Error, HttpResponseBuilder};
|
||||||
|
|
||||||
/// An outgoing response.
|
/// An outgoing response.
|
||||||
pub struct HttpResponse<B = AnyBody> {
|
pub struct HttpResponse<B = BoxBody> {
|
||||||
res: Response<B>,
|
res: Response<B>,
|
||||||
pub(crate) error: Option<Error>,
|
pub(crate) error: Option<Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpResponse<AnyBody> {
|
impl HttpResponse<BoxBody> {
|
||||||
/// Constructs a response.
|
/// Constructs a response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(status: StatusCode) -> Self {
|
pub fn new(status: StatusCode) -> Self {
|
||||||
|
@ -227,8 +227,26 @@ impl<B> HttpResponse<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: into_body equivalent
|
// TODO: docs for the body map methods below
|
||||||
// TODO: into_boxed_body
|
|
||||||
|
#[inline]
|
||||||
|
pub fn map_into_left_body<R>(self) -> HttpResponse<EitherBody<B, R>> {
|
||||||
|
self.map_body(|_, body| EitherBody::left(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn map_into_right_body<L>(self) -> HttpResponse<EitherBody<L, B>> {
|
||||||
|
self.map_body(|_, body| EitherBody::right(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn map_into_boxed_body(self) -> HttpResponse<BoxBody>
|
||||||
|
where
|
||||||
|
B: MessageBody + 'static,
|
||||||
|
{
|
||||||
|
// TODO: avoid double boxing with down-casting, if it improves perf
|
||||||
|
self.map_body(|_, body| BoxBody::new(body))
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract response body
|
/// Extract response body
|
||||||
pub fn into_body(self) -> B {
|
pub fn into_body(self) -> B {
|
||||||
|
@ -273,14 +291,14 @@ impl<B> From<HttpResponse<B>> for Response<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future is only implemented for AnyBody payload type because it's the most useful for making
|
// Future is only implemented for BoxBody payload type because it's the most useful for making
|
||||||
// simple handlers without async blocks. Making it generic over all MessageBody types requires a
|
// simple handlers without async blocks. Making it generic over all MessageBody types requires a
|
||||||
// future impl on Response which would cause it's body field to be, undesirably, Option<B>.
|
// future impl on Response which would cause it's body field to be, undesirably, Option<B>.
|
||||||
//
|
//
|
||||||
// This impl is not particularly efficient due to the Response construction and should probably
|
// This impl is not particularly efficient due to the Response construction and should probably
|
||||||
// not be invoked if performance is important. Prefer an async fn/block in such cases.
|
// not be invoked if performance is important. Prefer an async fn/block in such cases.
|
||||||
impl Future for HttpResponse<AnyBody> {
|
impl Future for HttpResponse<BoxBody> {
|
||||||
type Output = Result<Response<AnyBody>, Error>;
|
type Output = Result<Response<BoxBody>, Error>;
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
if let Some(err) = self.error.take() {
|
if let Some(err) = self.error.take() {
|
||||||
|
|
23
src/route.rs
23
src/route.rs
|
@ -1,19 +1,18 @@
|
||||||
#![allow(clippy::rc_buffer)] // inner value is mutated before being shared (`Rc::get_mut`)
|
use std::{future::Future, mem, rc::Rc};
|
||||||
|
|
||||||
use std::{future::Future, rc::Rc};
|
|
||||||
|
|
||||||
use actix_http::http::Method;
|
use actix_http::http::Method;
|
||||||
use actix_service::{
|
use actix_service::{
|
||||||
boxed::{self, BoxService, BoxServiceFactory},
|
boxed::{self, BoxService},
|
||||||
Service, ServiceFactory, ServiceFactoryExt,
|
fn_service, Service, ServiceFactory, ServiceFactoryExt,
|
||||||
};
|
};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
body::MessageBody,
|
||||||
guard::{self, Guard},
|
guard::{self, Guard},
|
||||||
handler::{handler_service, Handler},
|
handler::{handler_service, Handler},
|
||||||
service::{ServiceRequest, ServiceResponse},
|
service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse},
|
||||||
Error, FromRequest, HttpResponse, Responder,
|
BoxError, Error, FromRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Resource route definition
|
/// Resource route definition
|
||||||
|
@ -21,7 +20,7 @@ use crate::{
|
||||||
/// Route uses builder-like pattern for configuration.
|
/// Route uses builder-like pattern for configuration.
|
||||||
/// If handler is not explicitly set, default *404 Not Found* handler is used.
|
/// If handler is not explicitly set, default *404 Not Found* handler is used.
|
||||||
pub struct Route {
|
pub struct Route {
|
||||||
service: BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>,
|
service: BoxedHttpServiceFactory,
|
||||||
guards: Rc<Vec<Box<dyn Guard>>>,
|
guards: Rc<Vec<Box<dyn Guard>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,13 +29,15 @@ impl Route {
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> Route {
|
pub fn new() -> Route {
|
||||||
Route {
|
Route {
|
||||||
service: handler_service(HttpResponse::NotFound),
|
service: boxed::factory(fn_service(|req: ServiceRequest| async {
|
||||||
|
Ok(req.into_response(HttpResponse::NotFound()))
|
||||||
|
})),
|
||||||
guards: Rc::new(Vec::new()),
|
guards: Rc::new(Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn take_guards(&mut self) -> Vec<Box<dyn Guard>> {
|
pub(crate) fn take_guards(&mut self) -> Vec<Box<dyn Guard>> {
|
||||||
std::mem::take(Rc::get_mut(&mut self.guards).unwrap())
|
mem::take(Rc::get_mut(&mut self.guards).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +182,8 @@ impl Route {
|
||||||
T: FromRequest + 'static,
|
T: FromRequest + 'static,
|
||||||
R: Future + 'static,
|
R: Future + 'static,
|
||||||
R::Output: Responder + 'static,
|
R::Output: Responder + 'static,
|
||||||
|
<R::Output as Responder>::Body: MessageBody,
|
||||||
|
<<R::Output as Responder>::Body as MessageBody>::Error: Into<BoxError>,
|
||||||
{
|
{
|
||||||
self.service = handler_service(handler);
|
self.service = handler_service(handler);
|
||||||
self
|
self
|
||||||
|
|
80
src/scope.rs
80
src/scope.rs
|
@ -3,9 +3,8 @@ use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc};
|
||||||
use actix_http::Extensions;
|
use actix_http::Extensions;
|
||||||
use actix_router::{ResourceDef, Router};
|
use actix_router::{ResourceDef, Router};
|
||||||
use actix_service::{
|
use actix_service::{
|
||||||
apply, apply_fn_factory,
|
apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory,
|
||||||
boxed::{self, BoxService, BoxServiceFactory},
|
ServiceFactoryExt, Transform,
|
||||||
IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, Transform,
|
|
||||||
};
|
};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use futures_util::future::join_all;
|
use futures_util::future::join_all;
|
||||||
|
@ -13,16 +12,17 @@ use futures_util::future::join_all;
|
||||||
use crate::{
|
use crate::{
|
||||||
config::ServiceConfig,
|
config::ServiceConfig,
|
||||||
data::Data,
|
data::Data,
|
||||||
dev::{AppService, HttpServiceFactory},
|
dev::AppService,
|
||||||
guard::Guard,
|
guard::Guard,
|
||||||
rmap::ResourceMap,
|
rmap::ResourceMap,
|
||||||
service::{AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse},
|
service::{
|
||||||
|
AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory,
|
||||||
|
ServiceFactoryWrapper, ServiceRequest, ServiceResponse,
|
||||||
|
},
|
||||||
Error, Resource, Route,
|
Error, Resource, Route,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Guards = Vec<Box<dyn Guard>>;
|
type Guards = Vec<Box<dyn Guard>>;
|
||||||
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
|
||||||
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
|
||||||
|
|
||||||
/// Resources scope.
|
/// Resources scope.
|
||||||
///
|
///
|
||||||
|
@ -58,7 +58,7 @@ pub struct Scope<T = ScopeEndpoint> {
|
||||||
app_data: Option<Extensions>,
|
app_data: Option<Extensions>,
|
||||||
services: Vec<Box<dyn AppServiceFactory>>,
|
services: Vec<Box<dyn AppServiceFactory>>,
|
||||||
guards: Vec<Box<dyn Guard>>,
|
guards: Vec<Box<dyn Guard>>,
|
||||||
default: Option<Rc<HttpNewService>>,
|
default: Option<Rc<BoxedHttpServiceFactory>>,
|
||||||
external: Vec<ResourceDef>,
|
external: Vec<ResourceDef>,
|
||||||
factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
|
factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
|
||||||
}
|
}
|
||||||
|
@ -470,8 +470,14 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ScopeFactory {
|
pub struct ScopeFactory {
|
||||||
services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>,
|
services: Rc<
|
||||||
default: Rc<HttpNewService>,
|
[(
|
||||||
|
ResourceDef,
|
||||||
|
BoxedHttpServiceFactory,
|
||||||
|
RefCell<Option<Guards>>,
|
||||||
|
)],
|
||||||
|
>,
|
||||||
|
default: Rc<BoxedHttpServiceFactory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceFactory<ServiceRequest> for ScopeFactory {
|
impl ServiceFactory<ServiceRequest> for ScopeFactory {
|
||||||
|
@ -518,8 +524,8 @@ impl ServiceFactory<ServiceRequest> for ScopeFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ScopeService {
|
pub struct ScopeService {
|
||||||
router: Router<HttpService, Vec<Box<dyn Guard>>>,
|
router: Router<BoxedHttpService, Vec<Box<dyn Guard>>>,
|
||||||
default: HttpService,
|
default: BoxedHttpService,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<ServiceRequest> for ScopeService {
|
impl Service<ServiceRequest> for ScopeService {
|
||||||
|
@ -580,12 +586,11 @@ mod tests {
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dev::AnyBody,
|
|
||||||
guard,
|
guard,
|
||||||
http::{header, HeaderValue, Method, StatusCode},
|
http::{header, HeaderValue, Method, StatusCode},
|
||||||
middleware::DefaultHeaders,
|
middleware::DefaultHeaders,
|
||||||
service::{ServiceRequest, ServiceResponse},
|
service::{ServiceRequest, ServiceResponse},
|
||||||
test::{call_service, init_service, read_body, TestRequest},
|
test::{assert_body_eq, call_service, init_service, read_body, TestRequest},
|
||||||
web, App, HttpMessage, HttpRequest, HttpResponse,
|
web, App, HttpMessage, HttpRequest, HttpResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -748,20 +753,13 @@ mod tests {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/ab-project1/path1").to_request();
|
let req = TestRequest::with_uri("/ab-project1/path1").to_request();
|
||||||
let resp = srv.call(req).await.unwrap();
|
let res = srv.call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_body_eq!(res, b"project: project1");
|
||||||
match resp.response().body() {
|
|
||||||
AnyBody::Bytes(ref b) => {
|
|
||||||
let bytes = b.clone();
|
|
||||||
assert_eq!(bytes, Bytes::from_static(b"project: project1"));
|
|
||||||
}
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/aa-project1/path1").to_request();
|
let req = TestRequest::with_uri("/aa-project1/path1").to_request();
|
||||||
let resp = srv.call(req).await.unwrap();
|
let res = srv.call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
@ -849,16 +847,9 @@ mod tests {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/app/project_1/path1").to_request();
|
let req = TestRequest::with_uri("/app/project_1/path1").to_request();
|
||||||
let resp = srv.call(req).await.unwrap();
|
let res = srv.call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
assert_eq!(res.status(), StatusCode::CREATED);
|
||||||
|
assert_body_eq!(res, b"project: project_1");
|
||||||
match resp.response().body() {
|
|
||||||
AnyBody::Bytes(ref b) => {
|
|
||||||
let bytes = b.clone();
|
|
||||||
assert_eq!(bytes, Bytes::from_static(b"project: project_1"));
|
|
||||||
}
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
@ -877,20 +868,13 @@ mod tests {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/app/test/1/path1").to_request();
|
let req = TestRequest::with_uri("/app/test/1/path1").to_request();
|
||||||
let resp = srv.call(req).await.unwrap();
|
let res = srv.call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
assert_eq!(res.status(), StatusCode::CREATED);
|
||||||
|
assert_body_eq!(res, b"project: test - 1");
|
||||||
match resp.response().body() {
|
|
||||||
AnyBody::Bytes(ref b) => {
|
|
||||||
let bytes = b.clone();
|
|
||||||
assert_eq!(bytes, Bytes::from_static(b"project: test - 1"));
|
|
||||||
}
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/app/test/1/path2").to_request();
|
let req = TestRequest::with_uri("/app/test/1/path2").to_request();
|
||||||
let resp = srv.call(req).await.unwrap();
|
let res = srv.call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cmp,
|
cmp, fmt, io,
|
||||||
error::Error as StdError,
|
|
||||||
fmt, io,
|
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
net,
|
net,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
|
@ -75,15 +73,13 @@ where
|
||||||
I: IntoServiceFactory<S, Request>,
|
I: IntoServiceFactory<S, Request>,
|
||||||
|
|
||||||
S: ServiceFactory<Request, Config = AppConfig> + 'static,
|
S: ServiceFactory<Request, Config = AppConfig> + 'static,
|
||||||
// S::Future: 'static,
|
|
||||||
S::Error: Into<Error> + 'static,
|
S::Error: Into<Error> + 'static,
|
||||||
S::InitError: fmt::Debug,
|
S::InitError: fmt::Debug,
|
||||||
S::Response: Into<Response<B>> + 'static,
|
S::Response: Into<Response<B>> + 'static,
|
||||||
<S::Service as Service<Request>>::Future: 'static,
|
<S::Service as Service<Request>>::Future: 'static,
|
||||||
S::Service: 'static,
|
S::Service: 'static,
|
||||||
// S::Service: 'static,
|
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
B::Error: Into<Box<dyn StdError>>,
|
|
||||||
{
|
{
|
||||||
/// Create new HTTP server with application factory
|
/// Create new HTTP server with application factory
|
||||||
pub fn new(factory: F) -> Self {
|
pub fn new(factory: F) -> Self {
|
||||||
|
@ -656,8 +652,8 @@ fn create_tcp_listener(addr: net::SocketAddr, backlog: u32) -> io::Result<net::T
|
||||||
Ok(net::TcpListener::from(socket))
|
Ok(net::TcpListener::from(socket))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
|
||||||
/// Configure `SslAcceptorBuilder` with custom server flags.
|
/// Configure `SslAcceptorBuilder` with custom server flags.
|
||||||
|
#[cfg(feature = "openssl")]
|
||||||
fn openssl_acceptor(mut builder: SslAcceptorBuilder) -> io::Result<SslAcceptor> {
|
fn openssl_acceptor(mut builder: SslAcceptorBuilder) -> io::Result<SslAcceptor> {
|
||||||
builder.set_alpn_select_callback(|_, protocols| {
|
builder.set_alpn_select_callback(|_, protocols| {
|
||||||
const H2: &[u8] = b"\x02h2";
|
const H2: &[u8] = b"\x02h2";
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
use std::cell::{Ref, RefMut};
|
use std::{
|
||||||
use std::rc::Rc;
|
cell::{Ref, RefMut},
|
||||||
use std::{fmt, net};
|
fmt, net,
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body::{AnyBody, MessageBody},
|
body::{BoxBody, EitherBody, MessageBody},
|
||||||
http::{HeaderMap, Method, StatusCode, Uri, Version},
|
http::{HeaderMap, Method, StatusCode, Uri, Version},
|
||||||
Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead,
|
Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead,
|
||||||
};
|
};
|
||||||
use actix_router::{IntoPatterns, Path, Patterns, Resource, ResourceDef, Url};
|
use actix_router::{IntoPatterns, Path, Patterns, Resource, ResourceDef, Url};
|
||||||
use actix_service::{IntoServiceFactory, ServiceFactory};
|
use actix_service::{
|
||||||
|
boxed::{BoxService, BoxServiceFactory},
|
||||||
|
IntoServiceFactory, ServiceFactory,
|
||||||
|
};
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
use cookie::{Cookie, ParseError as CookieParseError};
|
use cookie::{Cookie, ParseError as CookieParseError};
|
||||||
|
|
||||||
|
@ -21,6 +26,10 @@ use crate::{
|
||||||
Error, HttpRequest, HttpResponse,
|
Error, HttpRequest, HttpResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub(crate) type BoxedHttpService = BoxService<ServiceRequest, ServiceResponse<BoxBody>, Error>;
|
||||||
|
pub(crate) type BoxedHttpServiceFactory =
|
||||||
|
BoxServiceFactory<(), ServiceRequest, ServiceResponse<BoxBody>, Error, ()>;
|
||||||
|
|
||||||
pub trait HttpServiceFactory {
|
pub trait HttpServiceFactory {
|
||||||
fn register(self, config: &mut AppService);
|
fn register(self, config: &mut AppService);
|
||||||
}
|
}
|
||||||
|
@ -326,12 +335,12 @@ impl fmt::Debug for ServiceRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A service level response wrapper.
|
/// A service level response wrapper.
|
||||||
pub struct ServiceResponse<B = AnyBody> {
|
pub struct ServiceResponse<B = BoxBody> {
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
response: HttpResponse<B>,
|
response: HttpResponse<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceResponse<AnyBody> {
|
impl ServiceResponse<BoxBody> {
|
||||||
/// Create service response from the error
|
/// Create service response from the error
|
||||||
pub fn from_err<E: Into<Error>>(err: E, request: HttpRequest) -> Self {
|
pub fn from_err<E: Into<Error>>(err: E, request: HttpRequest) -> Self {
|
||||||
let response = HttpResponse::from_error(err);
|
let response = HttpResponse::from_error(err);
|
||||||
|
@ -401,6 +410,7 @@ impl<B> ServiceResponse<B> {
|
||||||
|
|
||||||
impl<B> ServiceResponse<B> {
|
impl<B> ServiceResponse<B> {
|
||||||
/// Set a new body
|
/// Set a new body
|
||||||
|
#[inline]
|
||||||
pub fn map_body<F, B2>(self, f: F) -> ServiceResponse<B2>
|
pub fn map_body<F, B2>(self, f: F) -> ServiceResponse<B2>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut ResponseHead, B) -> B2,
|
F: FnOnce(&mut ResponseHead, B) -> B2,
|
||||||
|
@ -412,6 +422,24 @@ impl<B> ServiceResponse<B> {
|
||||||
request: self.request,
|
request: self.request,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn map_into_left_body<R>(self) -> ServiceResponse<EitherBody<B, R>> {
|
||||||
|
self.map_body(|_, body| EitherBody::left(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn map_into_right_body<L>(self) -> ServiceResponse<EitherBody<L, B>> {
|
||||||
|
self.map_body(|_, body| EitherBody::right(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn map_into_boxed_body(self) -> ServiceResponse<BoxBody>
|
||||||
|
where
|
||||||
|
B: MessageBody + 'static,
|
||||||
|
{
|
||||||
|
self.map_body(|_, body| BoxBody::new(body))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B> From<ServiceResponse<B>> for HttpResponse<B> {
|
impl<B> From<ServiceResponse<B>> for HttpResponse<B> {
|
||||||
|
|
24
src/test.rs
24
src/test.rs
|
@ -4,7 +4,6 @@ use std::{borrow::Cow, net::SocketAddr, rc::Rc};
|
||||||
|
|
||||||
pub use actix_http::test::TestBuffer;
|
pub use actix_http::test::TestBuffer;
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
body,
|
|
||||||
http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version},
|
http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version},
|
||||||
test::TestRequest as HttpTestRequest,
|
test::TestRequest as HttpTestRequest,
|
||||||
Extensions, Request,
|
Extensions, Request,
|
||||||
|
@ -20,9 +19,10 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||||
use crate::cookie::{Cookie, CookieJar};
|
use crate::cookie::{Cookie, CookieJar};
|
||||||
use crate::{
|
use crate::{
|
||||||
app_service::AppInitServiceState,
|
app_service::AppInitServiceState,
|
||||||
|
body::{self, BoxBody, MessageBody},
|
||||||
config::AppConfig,
|
config::AppConfig,
|
||||||
data::Data,
|
data::Data,
|
||||||
dev::{AnyBody, MessageBody, Payload},
|
dev::Payload,
|
||||||
http::header::ContentType,
|
http::header::ContentType,
|
||||||
rmap::ResourceMap,
|
rmap::ResourceMap,
|
||||||
service::{ServiceRequest, ServiceResponse},
|
service::{ServiceRequest, ServiceResponse},
|
||||||
|
@ -32,14 +32,14 @@ use crate::{
|
||||||
|
|
||||||
/// Create service that always responds with `HttpResponse::Ok()` and no body.
|
/// Create service that always responds with `HttpResponse::Ok()` and no body.
|
||||||
pub fn ok_service(
|
pub fn ok_service(
|
||||||
) -> impl Service<ServiceRequest, Response = ServiceResponse<AnyBody>, Error = Error> {
|
) -> impl Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> {
|
||||||
default_service(StatusCode::OK)
|
default_service(StatusCode::OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create service that always responds with given status code and no body.
|
/// Create service that always responds with given status code and no body.
|
||||||
pub fn default_service(
|
pub fn default_service(
|
||||||
status_code: StatusCode,
|
status_code: StatusCode,
|
||||||
) -> impl Service<ServiceRequest, Response = ServiceResponse<AnyBody>, Error = Error> {
|
) -> impl Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> {
|
||||||
(move |req: ServiceRequest| {
|
(move |req: ServiceRequest| {
|
||||||
ok(req.into_response(HttpResponseBuilder::new(status_code).finish()))
|
ok(req.into_response(HttpResponseBuilder::new(status_code).finish()))
|
||||||
})
|
})
|
||||||
|
@ -632,6 +632,22 @@ impl TestRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reduces boilerplate code when testing expected response payloads.
|
||||||
|
#[cfg(test)]
|
||||||
|
macro_rules! assert_body_eq {
|
||||||
|
($res:ident, $expected:expr) => {
|
||||||
|
assert_eq!(
|
||||||
|
::actix_http::body::to_bytes($res.into_body())
|
||||||
|
.await
|
||||||
|
.expect("body read should have succeeded"),
|
||||||
|
Bytes::from_static($expected),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) use assert_body_eq;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
|
@ -12,7 +12,7 @@ use futures_core::ready;
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dev,
|
body, dev,
|
||||||
web::{Form, Json},
|
web::{Form, Json},
|
||||||
Error, FromRequest, HttpRequest, HttpResponse, Responder,
|
Error, FromRequest, HttpRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
|
@ -146,10 +146,12 @@ where
|
||||||
L: Responder,
|
L: Responder,
|
||||||
R: Responder,
|
R: Responder,
|
||||||
{
|
{
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
type Body = body::EitherBody<L::Body, R::Body>;
|
||||||
|
|
||||||
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
match self {
|
match self {
|
||||||
Either::Left(a) => a.respond_to(req),
|
Either::Left(a) => a.respond_to(req).map_into_left_body(),
|
||||||
Either::Right(b) => b.respond_to(req),
|
Either::Right(b) => b.respond_to(req).map_into_right_body(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,9 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||||
#[cfg(feature = "__compress")]
|
#[cfg(feature = "__compress")]
|
||||||
use crate::dev::Decompress;
|
use crate::dev::Decompress;
|
||||||
use crate::{
|
use crate::{
|
||||||
error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web, Error,
|
body::EitherBody, error::UrlencodedError, extract::FromRequest,
|
||||||
HttpMessage, HttpRequest, HttpResponse, Responder,
|
http::header::CONTENT_LENGTH, web, Error, HttpMessage, HttpRequest, HttpResponse,
|
||||||
|
Responder,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// URL encoded payload extractor and responder.
|
/// URL encoded payload extractor and responder.
|
||||||
|
@ -180,12 +181,21 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
|
||||||
|
|
||||||
/// See [here](#responder) for example of usage as a handler return type.
|
/// See [here](#responder) for example of usage as a handler return type.
|
||||||
impl<T: Serialize> Responder for Form<T> {
|
impl<T: Serialize> Responder for Form<T> {
|
||||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
type Body = EitherBody<String>;
|
||||||
|
|
||||||
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
match serde_urlencoded::to_string(&self.0) {
|
match serde_urlencoded::to_string(&self.0) {
|
||||||
Ok(body) => HttpResponse::Ok()
|
Ok(body) => match HttpResponse::Ok()
|
||||||
.content_type(mime::APPLICATION_WWW_FORM_URLENCODED)
|
.content_type(mime::APPLICATION_WWW_FORM_URLENCODED)
|
||||||
.body(body),
|
.message_body(body)
|
||||||
Err(err) => HttpResponse::from_error(UrlencodedError::Serialize(err)),
|
{
|
||||||
|
Ok(res) => res.map_into_left_body(),
|
||||||
|
Err(err) => HttpResponse::from_error(err).map_into_right_body(),
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(err) => {
|
||||||
|
HttpResponse::from_error(UrlencodedError::Serialize(err)).map_into_right_body()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -408,11 +418,14 @@ mod tests {
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::{
|
|
||||||
header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
|
|
||||||
StatusCode,
|
|
||||||
};
|
|
||||||
use crate::test::TestRequest;
|
use crate::test::TestRequest;
|
||||||
|
use crate::{
|
||||||
|
http::{
|
||||||
|
header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
|
||||||
|
StatusCode,
|
||||||
|
},
|
||||||
|
test::assert_body_eq,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
||||||
struct Info {
|
struct Info {
|
||||||
|
@ -520,15 +533,13 @@ mod tests {
|
||||||
hello: "world".to_string(),
|
hello: "world".to_string(),
|
||||||
counter: 123,
|
counter: 123,
|
||||||
});
|
});
|
||||||
let resp = form.respond_to(&req);
|
let res = form.respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("application/x-www-form-urlencoded")
|
HeaderValue::from_static("application/x-www-form-urlencoded")
|
||||||
);
|
);
|
||||||
|
assert_body_eq!(res, b"hello=world&counter=123");
|
||||||
use crate::responder::tests::BodyTest;
|
|
||||||
assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
|
|
@ -19,6 +19,7 @@ use actix_http::Payload;
|
||||||
#[cfg(feature = "__compress")]
|
#[cfg(feature = "__compress")]
|
||||||
use crate::dev::Decompress;
|
use crate::dev::Decompress;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
body::EitherBody,
|
||||||
error::{Error, JsonPayloadError},
|
error::{Error, JsonPayloadError},
|
||||||
extract::FromRequest,
|
extract::FromRequest,
|
||||||
http::header::CONTENT_LENGTH,
|
http::header::CONTENT_LENGTH,
|
||||||
|
@ -116,12 +117,21 @@ impl<T: Serialize> Serialize for Json<T> {
|
||||||
///
|
///
|
||||||
/// If serialization failed
|
/// If serialization failed
|
||||||
impl<T: Serialize> Responder for Json<T> {
|
impl<T: Serialize> Responder for Json<T> {
|
||||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
type Body = EitherBody<String>;
|
||||||
|
|
||||||
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
match serde_json::to_string(&self.0) {
|
match serde_json::to_string(&self.0) {
|
||||||
Ok(body) => HttpResponse::Ok()
|
Ok(body) => match HttpResponse::Ok()
|
||||||
.content_type(mime::APPLICATION_JSON)
|
.content_type(mime::APPLICATION_JSON)
|
||||||
.body(body),
|
.message_body(body)
|
||||||
Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)),
|
{
|
||||||
|
Ok(res) => res.map_into_left_body(),
|
||||||
|
Err(err) => HttpResponse::from_error(err).map_into_right_body(),
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(err) => {
|
||||||
|
HttpResponse::from_error(JsonPayloadError::Serialize(err)).map_into_right_body()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,7 +454,7 @@ mod tests {
|
||||||
header::{self, CONTENT_LENGTH, CONTENT_TYPE},
|
header::{self, CONTENT_LENGTH, CONTENT_TYPE},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
},
|
},
|
||||||
test::{load_body, TestRequest},
|
test::{assert_body_eq, load_body, TestRequest},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
@ -472,15 +482,13 @@ mod tests {
|
||||||
let j = Json(MyObject {
|
let j = Json(MyObject {
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
});
|
});
|
||||||
let resp = j.respond_to(&req);
|
let res = j.respond_to(&req);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
res.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
header::HeaderValue::from_static("application/json")
|
header::HeaderValue::from_static("application/json")
|
||||||
);
|
);
|
||||||
|
assert_body_eq!(res, b"{\"name\":\"test\"}");
|
||||||
use crate::responder::tests::BodyTest;
|
|
||||||
assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
|
|
@ -90,7 +90,7 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [here](#usage) for example of usage as an extractor.
|
/// See [here](#Examples) for example of usage as an extractor.
|
||||||
impl<T> FromRequest for Path<T>
|
impl<T> FromRequest for Path<T>
|
||||||
where
|
where
|
||||||
T: de::DeserializeOwned,
|
T: de::DeserializeOwned,
|
||||||
|
|
|
@ -43,12 +43,12 @@ use crate::{
|
||||||
/// Ok(format!("Request Body Bytes:\n{:?}", bytes))
|
/// Ok(format!("Request Body Bytes:\n{:?}", bytes))
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Payload(crate::dev::Payload);
|
pub struct Payload(dev::Payload);
|
||||||
|
|
||||||
impl Payload {
|
impl Payload {
|
||||||
/// Unwrap to inner Payload type.
|
/// Unwrap to inner Payload type.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_inner(self) -> crate::dev::Payload {
|
pub fn into_inner(self) -> dev::Payload {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ impl Stream for Payload {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [here](#usage) for example of usage as an extractor.
|
/// See [here](#Examples) for example of usage as an extractor.
|
||||||
impl FromRequest for Payload {
|
impl FromRequest for Payload {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Payload, Error>>;
|
type Future = Ready<Result<Payload, Error>>;
|
||||||
|
|
|
@ -105,7 +105,7 @@ impl<T: fmt::Display> fmt::Display for Query<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [here](#usage) for example of usage as an extractor.
|
/// See [here](#Examples) for example of usage as an extractor.
|
||||||
impl<T: DeserializeOwned> FromRequest for Query<T> {
|
impl<T: DeserializeOwned> FromRequest for Query<T> {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Self, Error>>;
|
type Future = Ready<Result<Self, Error>>;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
//! Essentials helper functions and types for application registration.
|
//! Essentials helper functions and types for application registration.
|
||||||
|
|
||||||
use std::future::Future;
|
use std::{error::Error as StdError, future::Future};
|
||||||
|
|
||||||
use actix_http::http::Method;
|
use actix_http::http::Method;
|
||||||
use actix_router::IntoPatterns;
|
use actix_router::IntoPatterns;
|
||||||
pub use bytes::{Buf, BufMut, Bytes, BytesMut};
|
pub use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::BlockingError, extract::FromRequest, handler::Handler, resource::Resource,
|
body::MessageBody, error::BlockingError, extract::FromRequest, handler::Handler,
|
||||||
responder::Responder, route::Route, scope::Scope, service::WebService,
|
resource::Resource, responder::Responder, route::Route, scope::Scope, service::WebService,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::config::ServiceConfig;
|
pub use crate::config::ServiceConfig;
|
||||||
|
@ -145,6 +145,8 @@ where
|
||||||
I: FromRequest + 'static,
|
I: FromRequest + 'static,
|
||||||
R: Future + 'static,
|
R: Future + 'static,
|
||||||
R::Output: Responder + 'static,
|
R::Output: Responder + 'static,
|
||||||
|
<R::Output as Responder>::Body: MessageBody + 'static,
|
||||||
|
<<R::Output as Responder>::Body as MessageBody>::Error: Into<Box<dyn StdError + 'static>>,
|
||||||
{
|
{
|
||||||
Route::new().to(handler)
|
Route::new().to(handler)
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,13 +200,10 @@ async fn test_body_encoding_override() {
|
||||||
.body(STR)
|
.body(STR)
|
||||||
})))
|
})))
|
||||||
.service(web::resource("/raw").route(web::to(|| {
|
.service(web::resource("/raw").route(web::to(|| {
|
||||||
let body = actix_web::dev::AnyBody::Bytes(STR.into());
|
|
||||||
let mut response =
|
let mut response =
|
||||||
HttpResponse::with_body(actix_web::http::StatusCode::OK, body);
|
HttpResponse::with_body(actix_web::http::StatusCode::OK, STR);
|
||||||
|
|
||||||
response.encoding(ContentEncoding::Deflate);
|
response.encoding(ContentEncoding::Deflate);
|
||||||
|
response.map_into_boxed_body()
|
||||||
response
|
|
||||||
})))
|
})))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue