mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-01 00:44:26 +02:00
body ergonomics v3 (#2468)
This commit is contained in:
56
src/app.rs
56
src/app.rs
@ -1,37 +1,35 @@
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, rc::Rc};
|
||||
|
||||
use actix_http::body::{AnyBody, MessageBody};
|
||||
use actix_http::{Extensions, Request};
|
||||
use actix_service::boxed::{self, BoxServiceFactory};
|
||||
use actix_http::{
|
||||
body::{BoxBody, MessageBody},
|
||||
Extensions, Request,
|
||||
};
|
||||
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 crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
|
||||
use crate::config::ServiceConfig;
|
||||
use crate::data::{Data, DataFactory, FnDataFactory};
|
||||
use crate::dev::ResourceDef;
|
||||
use crate::error::Error;
|
||||
use crate::resource::Resource;
|
||||
use crate::route::Route;
|
||||
use crate::service::{
|
||||
AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest,
|
||||
ServiceResponse,
|
||||
use crate::{
|
||||
app_service::{AppEntry, AppInit, AppRoutingFactory},
|
||||
config::ServiceConfig,
|
||||
data::{Data, DataFactory, FnDataFactory},
|
||||
dev::ResourceDef,
|
||||
error::Error,
|
||||
resource::Resource,
|
||||
route::Route,
|
||||
service::{
|
||||
AppServiceFactory, BoxedHttpServiceFactory, HttpServiceFactory, ServiceFactoryWrapper,
|
||||
ServiceRequest, ServiceResponse,
|
||||
},
|
||||
};
|
||||
|
||||
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
|
||||
/// Application builder - structure that follows the builder pattern
|
||||
/// for building application instances.
|
||||
pub struct App<T, B> {
|
||||
endpoint: T,
|
||||
services: Vec<Box<dyn AppServiceFactory>>,
|
||||
default: Option<Rc<HttpNewService>>,
|
||||
default: Option<Rc<BoxedHttpServiceFactory>>,
|
||||
factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||
data_factories: Vec<FnDataFactory>,
|
||||
external: Vec<ResourceDef>,
|
||||
@ -39,7 +37,7 @@ pub struct App<T, B> {
|
||||
_phantom: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl App<AppEntry, AnyBody> {
|
||||
impl App<AppEntry, BoxBody> {
|
||||
/// Create application builder. Application can be configured with a builder-like pattern.
|
||||
#[allow(clippy::new_without_default)]
|
||||
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
|
||||
F: IntoServiceFactory<U, ServiceRequest>,
|
||||
U: ServiceFactory<
|
||||
@ -298,10 +296,12 @@ where
|
||||
> + 'static,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
// create and configure default resource
|
||||
self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err(
|
||||
|e| log::error!("Can not construct default service: {:?}", e),
|
||||
))));
|
||||
let svc = svc
|
||||
.into_factory()
|
||||
.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
|
||||
}
|
||||
|
@ -2,10 +2,7 @@ use std::{cell::RefCell, mem, rc::Rc};
|
||||
|
||||
use actix_http::{Extensions, Request};
|
||||
use actix_router::{Path, ResourceDef, Router, Url};
|
||||
use actix_service::{
|
||||
boxed::{self, BoxService, BoxServiceFactory},
|
||||
fn_service, Service, ServiceFactory,
|
||||
};
|
||||
use actix_service::{boxed, fn_service, Service, ServiceFactory};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use futures_util::future::join_all;
|
||||
|
||||
@ -15,13 +12,14 @@ use crate::{
|
||||
guard::Guard,
|
||||
request::{HttpRequest, HttpRequestPool},
|
||||
rmap::ResourceMap,
|
||||
service::{AppServiceFactory, ServiceRequest, ServiceResponse},
|
||||
service::{
|
||||
AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, ServiceRequest,
|
||||
ServiceResponse,
|
||||
},
|
||||
Error, HttpResponse,
|
||||
};
|
||||
|
||||
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>`.
|
||||
/// It also executes data factories.
|
||||
@ -39,7 +37,7 @@ where
|
||||
pub(crate) extensions: RefCell<Option<Extensions>>,
|
||||
pub(crate) async_data_factories: Rc<[FnDataFactory]>,
|
||||
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) external: RefCell<Vec<ResourceDef>>,
|
||||
}
|
||||
@ -230,8 +228,14 @@ where
|
||||
}
|
||||
|
||||
pub struct AppRoutingFactory {
|
||||
services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>,
|
||||
default: Rc<HttpNewService>,
|
||||
services: Rc<
|
||||
[(
|
||||
ResourceDef,
|
||||
BoxedHttpServiceFactory,
|
||||
RefCell<Option<Guards>>,
|
||||
)],
|
||||
>,
|
||||
default: Rc<BoxedHttpServiceFactory>,
|
||||
}
|
||||
|
||||
impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
||||
@ -279,8 +283,8 @@ impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
||||
|
||||
/// The Actix Web router default entry point.
|
||||
pub struct AppRouting {
|
||||
router: Router<HttpService, Guards>,
|
||||
default: HttpService,
|
||||
router: Router<BoxedHttpService, Guards>,
|
||||
default: BoxedHttpService,
|
||||
}
|
||||
|
||||
impl Service<ServiceRequest> for AppRouting {
|
||||
|
@ -284,7 +284,7 @@ mod tests {
|
||||
async fn test_data_from_arc() {
|
||||
let data_new = Data::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]
|
||||
|
53
src/dev.rs
53
src/dev.rs
@ -1,7 +1,7 @@
|
||||
//! 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
|
||||
//! 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};
|
||||
#[doc(hidden)]
|
||||
@ -14,11 +14,6 @@ pub use crate::types::form::UrlEncoded;
|
||||
pub use crate::types::json::JsonBody;
|
||||
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_router::{Path, ResourceDef, ResourcePath, Url};
|
||||
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,
|
||||
};
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
pub use actix_http::encoding::Decoder as Decompress;
|
||||
|
||||
use crate::http::header::ContentEncoding;
|
||||
use actix_http::ResponseBuilder;
|
||||
|
||||
use actix_router::Patterns;
|
||||
|
||||
@ -62,7 +59,7 @@ pub trait BodyEncoding {
|
||||
fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self;
|
||||
}
|
||||
|
||||
impl BodyEncoding for ResponseBuilder {
|
||||
impl BodyEncoding for actix_http::ResponseBuilder {
|
||||
fn get_encoding(&self) -> Option<ContentEncoding> {
|
||||
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> {
|
||||
self.extensions().get::<Enc>().map(|enc| enc.0)
|
||||
}
|
||||
@ -105,3 +102,41 @@ impl<B> BodyEncoding for crate::HttpResponse<B> {
|
||||
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 actix_http::{body::AnyBody, Response};
|
||||
use actix_http::{body::BoxBody, Response};
|
||||
|
||||
use crate::{HttpResponse, ResponseError};
|
||||
|
||||
@ -69,8 +69,8 @@ impl<T: ResponseError + 'static> From<T> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for Response<AnyBody> {
|
||||
fn from(err: Error) -> Response<AnyBody> {
|
||||
impl From<Error> for Response<BoxBody> {
|
||||
fn from(err: Error) -> Response<BoxBody> {
|
||||
err.error_response().into()
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
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 crate::{Error, HttpRequest, HttpResponse, Responder, ResponseError};
|
||||
@ -84,11 +88,10 @@ where
|
||||
let mut buf = BytesMut::new().writer();
|
||||
let _ = write!(buf, "{}", self);
|
||||
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/plain; charset=utf-8"),
|
||||
);
|
||||
res.set_body(AnyBody::from(buf.into_inner()))
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
|
||||
res.set_body(BoxBody::new(buf.into_inner()))
|
||||
}
|
||||
|
||||
InternalErrorType::Response(ref resp) => {
|
||||
@ -106,7 +109,9 @@ impl<T> Responder for InternalError<T>
|
||||
where
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ mod tests {
|
||||
let resp_body: &mut dyn MB = &mut body;
|
||||
let body = resp_body.downcast_ref::<String>().unwrap();
|
||||
assert_eq!(body, "hello cast");
|
||||
let body = &mut resp_body.downcast_mut::<String>().unwrap();
|
||||
let body = resp_body.downcast_mut::<String>().unwrap();
|
||||
body.push('!');
|
||||
let body = resp_body.downcast_ref::<String>().unwrap();
|
||||
assert_eq!(body, "hello cast!");
|
||||
|
@ -6,11 +6,17 @@ use std::{
|
||||
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 crate::error::{downcast_dyn, downcast_get_type_id};
|
||||
use crate::{helpers, HttpResponse};
|
||||
use crate::{
|
||||
error::{downcast_dyn, downcast_get_type_id},
|
||||
helpers, HttpResponse,
|
||||
};
|
||||
|
||||
/// Errors that can generate responses.
|
||||
// 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
|
||||
/// `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 buf = BytesMut::new();
|
||||
let _ = write!(helpers::MutWriter(&mut buf), "{}", self);
|
||||
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/plain; charset=utf-8"),
|
||||
);
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
|
||||
res.set_body(AnyBody::from(buf))
|
||||
res.set_body(BoxBody::new(buf))
|
||||
}
|
||||
|
||||
downcast_get_type_id!();
|
||||
@ -86,8 +90,8 @@ impl ResponseError for actix_http::Error {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::new(self.status_code()).set_body(self.to_string().into())
|
||||
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||
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 {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
Response::from(self).into()
|
||||
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||
Response::from(self).map_into_boxed_body().into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,21 +1,21 @@
|
||||
use std::future::Future;
|
||||
|
||||
use actix_service::{
|
||||
boxed::{self, BoxServiceFactory},
|
||||
fn_service,
|
||||
};
|
||||
use actix_service::{boxed, fn_service};
|
||||
|
||||
use crate::{
|
||||
service::{ServiceRequest, ServiceResponse},
|
||||
Error, FromRequest, HttpResponse, Responder,
|
||||
body::MessageBody,
|
||||
service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse},
|
||||
BoxError, FromRequest, HttpResponse, Responder,
|
||||
};
|
||||
|
||||
/// 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
|
||||
/// that can be converted into an [`HttpResponse`] (that is, it impls the [`Responder`] trait).
|
||||
/// extracted from a request (i.e., [`impl FromRequest`]) and returns a type that can be converted
|
||||
/// 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
|
||||
/// 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
|
||||
where
|
||||
R: Future,
|
||||
@ -24,29 +24,44 @@ where
|
||||
fn call(&self, param: T) -> R;
|
||||
}
|
||||
|
||||
pub fn handler_service<F, T, R>(
|
||||
handler: F,
|
||||
) -> BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>
|
||||
pub(crate) fn handler_service<F, T, R>(handler: F) -> BoxedHttpServiceFactory
|
||||
where
|
||||
F: Handler<T, R>,
|
||||
T: FromRequest,
|
||||
R: Future,
|
||||
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| {
|
||||
let handler = handler.clone();
|
||||
|
||||
async move {
|
||||
let (req, mut payload) = req.into_parts();
|
||||
|
||||
let res = match T::from_request(&req, &mut payload).await {
|
||||
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))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/// 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)* } => {
|
||||
impl<Func, $($param,)* Res> Handler<($($param,)*), Res> for Func
|
||||
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
|
||||
/// 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.
|
||||
///
|
||||
/// [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;
|
||||
// TODO: is exposing the error directly really needed
|
||||
pub use crate::types::{Either, EitherExtractError};
|
||||
|
||||
pub(crate) type BoxError = Box<dyn std::error::Error>;
|
||||
|
@ -1,13 +1,12 @@
|
||||
//! For middleware documentation, see [`Compat`].
|
||||
|
||||
use std::{
|
||||
error::Error as StdError,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_http::body::{AnyBody, MessageBody};
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::{Service, Transform};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use pin_project_lite::pin_project;
|
||||
@ -123,10 +122,9 @@ pub trait MapServiceResponseBody {
|
||||
impl<B> MapServiceResponseBody for ServiceResponse<B>
|
||||
where
|
||||
B: MessageBody + Unpin + 'static,
|
||||
B::Error: Into<Box<dyn StdError + 'static>>,
|
||||
{
|
||||
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::{
|
||||
body::{AnyBody, MessageBody},
|
||||
body::{EitherBody, MessageBody},
|
||||
encoding::Encoder,
|
||||
http::header::{ContentEncoding, ACCEPT_ENCODING},
|
||||
StatusCode,
|
||||
};
|
||||
use actix_service::{Service, Transform};
|
||||
use actix_utils::future::{ok, Either, Ready};
|
||||
use bytes::Bytes;
|
||||
use futures_core::ready;
|
||||
use once_cell::sync::Lazy;
|
||||
use pin_project_lite::pin_project;
|
||||
@ -62,7 +61,7 @@ where
|
||||
B: MessageBody,
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
{
|
||||
type Response = ServiceResponse<AnyBody<Encoder<B>>>;
|
||||
type Response = ServiceResponse<EitherBody<Encoder<B>>>;
|
||||
type Error = Error;
|
||||
type Transform = CompressMiddleware<S>;
|
||||
type InitError = ();
|
||||
@ -112,7 +111,7 @@ where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
B: MessageBody,
|
||||
{
|
||||
type Response = ServiceResponse<AnyBody<Encoder<B>>>;
|
||||
type Response = ServiceResponse<EitherBody<Encoder<B>>>;
|
||||
type Error = 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
|
||||
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, _| {
|
||||
let body_bytes = Bytes::from(SUPPORTED_ALGORITHM_NAMES.as_bytes());
|
||||
|
||||
Encoder::response(
|
||||
ContentEncoding::Identity,
|
||||
head,
|
||||
AnyBody::Bytes(body_bytes),
|
||||
)
|
||||
});
|
||||
|
||||
Either::right(ok(req.into_response(res)))
|
||||
Either::right(ok(req
|
||||
.into_response(res)
|
||||
.map_into_boxed_body()
|
||||
.map_into_right_body()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -179,7 +174,7 @@ where
|
||||
B: MessageBody,
|
||||
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> {
|
||||
let this = self.project();
|
||||
@ -193,10 +188,11 @@ where
|
||||
};
|
||||
|
||||
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 crate::{
|
||||
dev::{BodySize, MessageBody},
|
||||
body::{BodySize, MessageBody},
|
||||
http::HeaderName,
|
||||
service::{ServiceRequest, ServiceResponse},
|
||||
Error, HttpResponse, Result,
|
||||
|
@ -1,32 +1,29 @@
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::rc::Rc;
|
||||
use std::{cell::RefCell, fmt, future::Future, rc::Rc};
|
||||
|
||||
use actix_http::Extensions;
|
||||
use actix_router::{IntoPatterns, Patterns};
|
||||
use actix_service::boxed::{self, BoxService, BoxServiceFactory};
|
||||
use actix_service::{
|
||||
apply, apply_fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory,
|
||||
apply, apply_fn_factory, boxed, fn_service, IntoServiceFactory, Service, ServiceFactory,
|
||||
ServiceFactoryExt, Transform,
|
||||
};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use futures_util::future::join_all;
|
||||
|
||||
use crate::{
|
||||
body::MessageBody,
|
||||
data::Data,
|
||||
dev::{ensure_leading_slash, AppService, HttpServiceFactory, ResourceDef},
|
||||
dev::{ensure_leading_slash, AppService, ResourceDef},
|
||||
guard::Guard,
|
||||
handler::Handler,
|
||||
responder::Responder,
|
||||
route::{Route, RouteService},
|
||||
service::{ServiceRequest, ServiceResponse},
|
||||
Error, FromRequest, HttpResponse,
|
||||
service::{
|
||||
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 in turn has at least one route.
|
||||
@ -56,7 +53,7 @@ pub struct Resource<T = ResourceEndpoint> {
|
||||
routes: Vec<Route>,
|
||||
app_data: Option<Extensions>,
|
||||
guards: Vec<Box<dyn Guard>>,
|
||||
default: HttpNewService,
|
||||
default: BoxedHttpServiceFactory,
|
||||
factory_ref: Rc<RefCell<Option<ResourceFactory>>>,
|
||||
}
|
||||
|
||||
@ -242,6 +239,8 @@ where
|
||||
I: FromRequest + 'static,
|
||||
R: Future + '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
|
||||
@ -422,7 +421,7 @@ where
|
||||
|
||||
pub struct ResourceFactory {
|
||||
routes: Vec<Route>,
|
||||
default: HttpNewService,
|
||||
default: BoxedHttpServiceFactory,
|
||||
}
|
||||
|
||||
impl ServiceFactory<ServiceRequest> for ResourceFactory {
|
||||
@ -454,7 +453,7 @@ impl ServiceFactory<ServiceRequest> for ResourceFactory {
|
||||
|
||||
pub struct ResourceService {
|
||||
routes: Vec<RouteService>,
|
||||
default: HttpService,
|
||||
default: BoxedHttpService,
|
||||
}
|
||||
|
||||
impl Service<ServiceRequest> for ResourceService {
|
||||
|
299
src/responder.rs
299
src/responder.rs
@ -1,19 +1,21 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use actix_http::{
|
||||
body::AnyBody,
|
||||
body::{BoxBody, EitherBody, MessageBody},
|
||||
http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode},
|
||||
};
|
||||
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.
|
||||
///
|
||||
/// Any types that implement this trait can be used in the return type of a handler.
|
||||
pub trait Responder {
|
||||
type Body: MessageBody + 'static;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
@ -59,38 +61,52 @@ pub trait Responder {
|
||||
}
|
||||
|
||||
impl Responder for HttpResponse {
|
||||
type Body = BoxBody;
|
||||
|
||||
#[inline]
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for actix_http::Response<AnyBody> {
|
||||
impl Responder for actix_http::Response<BoxBody> {
|
||||
type Body = BoxBody;
|
||||
|
||||
#[inline]
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
HttpResponse::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for HttpResponseBuilder {
|
||||
type Body = BoxBody;
|
||||
|
||||
#[inline]
|
||||
fn respond_to(mut self, _: &HttpRequest) -> HttpResponse {
|
||||
fn respond_to(mut self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
self.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for actix_http::ResponseBuilder {
|
||||
type Body = BoxBody;
|
||||
|
||||
#[inline]
|
||||
fn respond_to(mut self, _: &HttpRequest) -> HttpResponse {
|
||||
HttpResponse::from(self.finish())
|
||||
fn respond_to(mut self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
self.finish().map_into_boxed_body().respond_to(req)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Responder> Responder for Option<T> {
|
||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||
impl<T> Responder for Option<T>
|
||||
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 {
|
||||
Some(val) => val.respond_to(req),
|
||||
None => HttpResponse::new(StatusCode::NOT_FOUND),
|
||||
Some(val) => val.respond_to(req).map_into_left_body(),
|
||||
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>
|
||||
where
|
||||
T: Responder,
|
||||
<T::Body as MessageBody>::Error: Into<BoxError>,
|
||||
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 {
|
||||
Ok(val) => val.respond_to(req),
|
||||
Err(e) => HttpResponse::from_error(e.into()),
|
||||
Ok(val) => val.respond_to(req).map_into_left_body(),
|
||||
Err(err) => HttpResponse::from_error(err.into()).map_into_right_body(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
*res.status_mut() = self.1;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_responder {
|
||||
($res: ty, $ct: path) => {
|
||||
macro_rules! impl_responder_by_forward_into_base_response {
|
||||
($res:ty, $body:ty) => {
|
||||
impl Responder for $res {
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
HttpResponse::Ok().content_type($ct).body(self)
|
||||
type Body = $body;
|
||||
|
||||
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_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);
|
||||
impl_into_string_responder!(&'_ String);
|
||||
impl_into_string_responder!(Cow<'_, str>);
|
||||
|
||||
/// Allows overriding status code and headers for a responder.
|
||||
pub struct CustomResponder<T> {
|
||||
@ -204,11 +242,17 @@ impl<T: Responder> CustomResponder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Responder> Responder for CustomResponder<T> {
|
||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||
impl<T> Responder for CustomResponder<T>
|
||||
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 {
|
||||
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);
|
||||
@ -222,7 +266,7 @@ impl<T: Responder> Responder for CustomResponder<T> {
|
||||
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 bytes::{Bytes, BytesMut};
|
||||
|
||||
use actix_http::body::to_bytes;
|
||||
|
||||
use super::*;
|
||||
use crate::dev::AnyBody;
|
||||
use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
|
||||
use crate::test::{init_service, TestRequest};
|
||||
use crate::{error, web, App};
|
||||
use crate::{
|
||||
error,
|
||||
http::{header::CONTENT_TYPE, HeaderValue, StatusCode},
|
||||
test::{assert_body_eq, init_service, TestRequest},
|
||||
web, App,
|
||||
};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_option_responder() {
|
||||
@ -253,112 +301,116 @@ pub(crate) mod tests {
|
||||
let req = TestRequest::with_uri("/some").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
match resp.response().body() {
|
||||
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
|
||||
}
|
||||
assert_body_eq!(resp, b"some");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_responder() {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
|
||||
let resp = "test".respond_to(&req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
let res = "test".respond_to(&req);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
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!(
|
||||
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")
|
||||
);
|
||||
|
||||
let resp = "test".to_string().respond_to(&req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
to_bytes(res.into_body()).await.unwrap(),
|
||||
Bytes::from_static(b"test"),
|
||||
);
|
||||
|
||||
let resp = (&"test".to_string()).respond_to(&req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
let res = "test".to_string().respond_to(&req);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
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 = (&"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 resp = Cow::Borrowed(s.as_str()).respond_to(&req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
let res = Cow::Borrowed(s.as_str()).respond_to(&req);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
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!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
to_bytes(res.into_body()).await.unwrap(),
|
||||
Bytes::from_static(b"test"),
|
||||
);
|
||||
|
||||
let resp = Cow::Borrowed("test").respond_to(&req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
let res = Cow::<'_, str>::Owned(s).respond_to(&req);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
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!(
|
||||
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")
|
||||
);
|
||||
|
||||
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!(
|
||||
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")
|
||||
);
|
||||
assert_eq!(
|
||||
to_bytes(res.into_body()).await.unwrap(),
|
||||
Bytes::from_static(b"test"),
|
||||
);
|
||||
|
||||
// InternalError
|
||||
let resp = error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req);
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
let res = error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req);
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@ -368,11 +420,14 @@ pub(crate) mod tests {
|
||||
// Result<I, E>
|
||||
let resp = Ok::<_, Error>("test".to_string()).respond_to(&req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
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))
|
||||
.respond_to(&req);
|
||||
@ -389,7 +444,10 @@ pub(crate) mod tests {
|
||||
.respond_to(&req);
|
||||
|
||||
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"
|
||||
.to_string()
|
||||
@ -397,11 +455,14 @@ pub(crate) mod tests {
|
||||
.respond_to(&req);
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("json")
|
||||
);
|
||||
assert_eq!(
|
||||
to_bytes(res.into_body()).await.unwrap(),
|
||||
Bytes::from_static(b"test"),
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@ -409,17 +470,23 @@ pub(crate) mod tests {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req);
|
||||
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 res = ("test".to_string(), StatusCode::OK)
|
||||
.with_header((CONTENT_TYPE, mime::APPLICATION_JSON))
|
||||
.respond_to(&req);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.body().bin_ref(), b"test");
|
||||
assert_eq!(
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
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::{
|
||||
cell::{Ref, RefMut},
|
||||
convert::TryInto,
|
||||
error::Error as StdError,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_http::{
|
||||
body::{AnyBody, BodyStream},
|
||||
body::{BodyStream, BoxBody, MessageBody},
|
||||
http::{
|
||||
header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue},
|
||||
ConnectionType, Error as HttpError, StatusCode,
|
||||
@ -26,14 +25,14 @@ use cookie::{Cookie, CookieJar};
|
||||
|
||||
use crate::{
|
||||
error::{Error, JsonPayloadError},
|
||||
HttpResponse,
|
||||
BoxError, HttpResponse,
|
||||
};
|
||||
|
||||
/// An HTTP response builder.
|
||||
///
|
||||
/// This type can be used to construct an instance of `Response` through a builder-like pattern.
|
||||
pub struct HttpResponseBuilder {
|
||||
res: Option<Response<AnyBody>>,
|
||||
res: Option<Response<BoxBody>>,
|
||||
err: Option<HttpError>,
|
||||
#[cfg(feature = "cookies")]
|
||||
cookies: Option<CookieJar>,
|
||||
@ -44,7 +43,7 @@ impl HttpResponseBuilder {
|
||||
/// Create response builder
|
||||
pub fn new(status: StatusCode) -> Self {
|
||||
Self {
|
||||
res: Some(Response::new(status)),
|
||||
res: Some(Response::with_body(status, BoxBody::new(()))),
|
||||
err: None,
|
||||
#[cfg(feature = "cookies")]
|
||||
cookies: None,
|
||||
@ -299,7 +298,6 @@ impl HttpResponseBuilder {
|
||||
}
|
||||
|
||||
/// Mutable reference to a the response's extensions
|
||||
#[inline]
|
||||
pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
|
||||
self.res
|
||||
.as_mut()
|
||||
@ -307,18 +305,20 @@ impl HttpResponseBuilder {
|
||||
.extensions_mut()
|
||||
}
|
||||
|
||||
/// Set a body and generate `Response`.
|
||||
/// Set a body and build the `HttpResponse`.
|
||||
///
|
||||
/// `HttpResponseBuilder` can not be used after this call.
|
||||
#[inline]
|
||||
pub fn body<B: Into<AnyBody>>(&mut self, body: B) -> HttpResponse<AnyBody> {
|
||||
match self.message_body(body.into()) {
|
||||
Ok(res) => res,
|
||||
pub fn body<B>(&mut self, body: B) -> HttpResponse<BoxBody>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
match self.message_body(body) {
|
||||
Ok(res) => res.map_into_boxed_body(),
|
||||
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.
|
||||
pub fn message_body<B>(&mut self, body: B) -> Result<HttpResponse<B>, Error> {
|
||||
@ -332,7 +332,7 @@ impl HttpResponseBuilder {
|
||||
.expect("cannot reuse response builder")
|
||||
.set_body(body);
|
||||
|
||||
#[allow(unused_mut)]
|
||||
#[allow(unused_mut)] // mut is only unused when cookies are disabled
|
||||
let mut res = HttpResponse::from(res);
|
||||
|
||||
#[cfg(feature = "cookies")]
|
||||
@ -348,19 +348,19 @@ impl HttpResponseBuilder {
|
||||
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.
|
||||
#[inline]
|
||||
pub fn streaming<S, E>(&mut self, stream: S) -> HttpResponse
|
||||
where
|
||||
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.
|
||||
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.body(AnyBody::from(body))
|
||||
self.body(body)
|
||||
}
|
||||
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.
|
||||
#[inline]
|
||||
pub fn finish(&mut self) -> HttpResponse {
|
||||
self.body(AnyBody::empty())
|
||||
self.body(())
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
builder.finish().into()
|
||||
}
|
||||
@ -435,12 +435,9 @@ mod tests {
|
||||
use actix_http::body;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
dev::AnyBody,
|
||||
http::{
|
||||
header::{self, HeaderValue, CONTENT_TYPE},
|
||||
StatusCode,
|
||||
},
|
||||
use crate::http::{
|
||||
header::{self, HeaderValue, CONTENT_TYPE},
|
||||
StatusCode,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@ -475,7 +472,7 @@ mod tests {
|
||||
fn test_content_type() {
|
||||
let resp = HttpResponseBuilder::new(StatusCode::OK)
|
||||
.content_type("text/plain")
|
||||
.body(AnyBody::empty());
|
||||
.body(Bytes::new());
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_http::{
|
||||
body::{AnyBody, MessageBody},
|
||||
body::{BoxBody, EitherBody, MessageBody},
|
||||
http::{header::HeaderMap, StatusCode},
|
||||
Extensions, Response, ResponseHead,
|
||||
};
|
||||
@ -25,12 +25,12 @@ use {
|
||||
use crate::{error::Error, HttpResponseBuilder};
|
||||
|
||||
/// An outgoing response.
|
||||
pub struct HttpResponse<B = AnyBody> {
|
||||
pub struct HttpResponse<B = BoxBody> {
|
||||
res: Response<B>,
|
||||
pub(crate) error: Option<Error>,
|
||||
}
|
||||
|
||||
impl HttpResponse<AnyBody> {
|
||||
impl HttpResponse<BoxBody> {
|
||||
/// Constructs a response.
|
||||
#[inline]
|
||||
pub fn new(status: StatusCode) -> Self {
|
||||
@ -227,8 +227,26 @@ impl<B> HttpResponse<B> {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: into_body equivalent
|
||||
// TODO: into_boxed_body
|
||||
// TODO: docs for the body map methods below
|
||||
|
||||
#[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
|
||||
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
|
||||
// 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
|
||||
// not be invoked if performance is important. Prefer an async fn/block in such cases.
|
||||
impl Future for HttpResponse<AnyBody> {
|
||||
type Output = Result<Response<AnyBody>, Error>;
|
||||
impl Future for HttpResponse<BoxBody> {
|
||||
type Output = Result<Response<BoxBody>, Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
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, rc::Rc};
|
||||
use std::{future::Future, mem, rc::Rc};
|
||||
|
||||
use actix_http::http::Method;
|
||||
use actix_service::{
|
||||
boxed::{self, BoxService, BoxServiceFactory},
|
||||
Service, ServiceFactory, ServiceFactoryExt,
|
||||
boxed::{self, BoxService},
|
||||
fn_service, Service, ServiceFactory, ServiceFactoryExt,
|
||||
};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
|
||||
use crate::{
|
||||
body::MessageBody,
|
||||
guard::{self, Guard},
|
||||
handler::{handler_service, Handler},
|
||||
service::{ServiceRequest, ServiceResponse},
|
||||
Error, FromRequest, HttpResponse, Responder,
|
||||
service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse},
|
||||
BoxError, Error, FromRequest, HttpResponse, Responder,
|
||||
};
|
||||
|
||||
/// Resource route definition
|
||||
@ -21,7 +20,7 @@ use crate::{
|
||||
/// Route uses builder-like pattern for configuration.
|
||||
/// If handler is not explicitly set, default *404 Not Found* handler is used.
|
||||
pub struct Route {
|
||||
service: BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>,
|
||||
service: BoxedHttpServiceFactory,
|
||||
guards: Rc<Vec<Box<dyn Guard>>>,
|
||||
}
|
||||
|
||||
@ -30,13 +29,15 @@ impl Route {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> 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()),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
R: Future + '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
|
||||
|
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_router::{ResourceDef, Router};
|
||||
use actix_service::{
|
||||
apply, apply_fn_factory,
|
||||
boxed::{self, BoxService, BoxServiceFactory},
|
||||
IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, Transform,
|
||||
apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory,
|
||||
ServiceFactoryExt, Transform,
|
||||
};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use futures_util::future::join_all;
|
||||
@ -13,16 +12,17 @@ use futures_util::future::join_all;
|
||||
use crate::{
|
||||
config::ServiceConfig,
|
||||
data::Data,
|
||||
dev::{AppService, HttpServiceFactory},
|
||||
dev::AppService,
|
||||
guard::Guard,
|
||||
rmap::ResourceMap,
|
||||
service::{AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse},
|
||||
service::{
|
||||
AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory,
|
||||
ServiceFactoryWrapper, ServiceRequest, ServiceResponse,
|
||||
},
|
||||
Error, Resource, Route,
|
||||
};
|
||||
|
||||
type Guards = Vec<Box<dyn Guard>>;
|
||||
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
||||
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
|
||||
/// Resources scope.
|
||||
///
|
||||
@ -58,7 +58,7 @@ pub struct Scope<T = ScopeEndpoint> {
|
||||
app_data: Option<Extensions>,
|
||||
services: Vec<Box<dyn AppServiceFactory>>,
|
||||
guards: Vec<Box<dyn Guard>>,
|
||||
default: Option<Rc<HttpNewService>>,
|
||||
default: Option<Rc<BoxedHttpServiceFactory>>,
|
||||
external: Vec<ResourceDef>,
|
||||
factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
|
||||
}
|
||||
@ -470,8 +470,14 @@ where
|
||||
}
|
||||
|
||||
pub struct ScopeFactory {
|
||||
services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>,
|
||||
default: Rc<HttpNewService>,
|
||||
services: Rc<
|
||||
[(
|
||||
ResourceDef,
|
||||
BoxedHttpServiceFactory,
|
||||
RefCell<Option<Guards>>,
|
||||
)],
|
||||
>,
|
||||
default: Rc<BoxedHttpServiceFactory>,
|
||||
}
|
||||
|
||||
impl ServiceFactory<ServiceRequest> for ScopeFactory {
|
||||
@ -518,8 +524,8 @@ impl ServiceFactory<ServiceRequest> for ScopeFactory {
|
||||
}
|
||||
|
||||
pub struct ScopeService {
|
||||
router: Router<HttpService, Vec<Box<dyn Guard>>>,
|
||||
default: HttpService,
|
||||
router: Router<BoxedHttpService, Vec<Box<dyn Guard>>>,
|
||||
default: BoxedHttpService,
|
||||
}
|
||||
|
||||
impl Service<ServiceRequest> for ScopeService {
|
||||
@ -580,12 +586,11 @@ mod tests {
|
||||
use bytes::Bytes;
|
||||
|
||||
use crate::{
|
||||
dev::AnyBody,
|
||||
guard,
|
||||
http::{header, HeaderValue, Method, StatusCode},
|
||||
middleware::DefaultHeaders,
|
||||
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,
|
||||
};
|
||||
|
||||
@ -748,20 +753,13 @@ mod tests {
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/ab-project1/path1").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
match resp.response().body() {
|
||||
AnyBody::Bytes(ref b) => {
|
||||
let bytes = b.clone();
|
||||
assert_eq!(bytes, Bytes::from_static(b"project: project1"));
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
let res = srv.call(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_body_eq!(res, b"project: project1");
|
||||
|
||||
let req = TestRequest::with_uri("/aa-project1/path1").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
let res = srv.call(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@ -849,16 +847,9 @@ mod tests {
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/app/project_1/path1").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
|
||||
match resp.response().body() {
|
||||
AnyBody::Bytes(ref b) => {
|
||||
let bytes = b.clone();
|
||||
assert_eq!(bytes, Bytes::from_static(b"project: project_1"));
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
let res = srv.call(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::CREATED);
|
||||
assert_body_eq!(res, b"project: project_1");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@ -877,20 +868,13 @@ mod tests {
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/app/test/1/path1").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
|
||||
match resp.response().body() {
|
||||
AnyBody::Bytes(ref b) => {
|
||||
let bytes = b.clone();
|
||||
assert_eq!(bytes, Bytes::from_static(b"project: test - 1"));
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
let res = srv.call(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::CREATED);
|
||||
assert_body_eq!(res, b"project: test - 1");
|
||||
|
||||
let req = TestRequest::with_uri("/app/test/1/path2").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
let res = srv.call(req).await.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
|
@ -1,8 +1,6 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
cmp,
|
||||
error::Error as StdError,
|
||||
fmt, io,
|
||||
cmp, fmt, io,
|
||||
marker::PhantomData,
|
||||
net,
|
||||
sync::{Arc, Mutex},
|
||||
@ -75,15 +73,13 @@ where
|
||||
I: IntoServiceFactory<S, Request>,
|
||||
|
||||
S: ServiceFactory<Request, Config = AppConfig> + 'static,
|
||||
// S::Future: 'static,
|
||||
S::Error: Into<Error> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
S::Service: 'static,
|
||||
// S::Service: 'static,
|
||||
|
||||
B: MessageBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
{
|
||||
/// Create new HTTP server with application factory
|
||||
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))
|
||||
}
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
/// Configure `SslAcceptorBuilder` with custom server flags.
|
||||
#[cfg(feature = "openssl")]
|
||||
fn openssl_acceptor(mut builder: SslAcceptorBuilder) -> io::Result<SslAcceptor> {
|
||||
builder.set_alpn_select_callback(|_, protocols| {
|
||||
const H2: &[u8] = b"\x02h2";
|
||||
|
@ -1,14 +1,19 @@
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::rc::Rc;
|
||||
use std::{fmt, net};
|
||||
use std::{
|
||||
cell::{Ref, RefMut},
|
||||
fmt, net,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use actix_http::{
|
||||
body::{AnyBody, MessageBody},
|
||||
body::{BoxBody, EitherBody, MessageBody},
|
||||
http::{HeaderMap, Method, StatusCode, Uri, Version},
|
||||
Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead,
|
||||
};
|
||||
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")]
|
||||
use cookie::{Cookie, ParseError as CookieParseError};
|
||||
|
||||
@ -21,6 +26,10 @@ use crate::{
|
||||
Error, HttpRequest, HttpResponse,
|
||||
};
|
||||
|
||||
pub(crate) type BoxedHttpService = BoxService<ServiceRequest, ServiceResponse<BoxBody>, Error>;
|
||||
pub(crate) type BoxedHttpServiceFactory =
|
||||
BoxServiceFactory<(), ServiceRequest, ServiceResponse<BoxBody>, Error, ()>;
|
||||
|
||||
pub trait HttpServiceFactory {
|
||||
fn register(self, config: &mut AppService);
|
||||
}
|
||||
@ -326,12 +335,12 @@ impl fmt::Debug for ServiceRequest {
|
||||
}
|
||||
|
||||
/// A service level response wrapper.
|
||||
pub struct ServiceResponse<B = AnyBody> {
|
||||
pub struct ServiceResponse<B = BoxBody> {
|
||||
request: HttpRequest,
|
||||
response: HttpResponse<B>,
|
||||
}
|
||||
|
||||
impl ServiceResponse<AnyBody> {
|
||||
impl ServiceResponse<BoxBody> {
|
||||
/// Create service response from the error
|
||||
pub fn from_err<E: Into<Error>>(err: E, request: HttpRequest) -> Self {
|
||||
let response = HttpResponse::from_error(err);
|
||||
@ -401,6 +410,7 @@ impl<B> ServiceResponse<B> {
|
||||
|
||||
impl<B> ServiceResponse<B> {
|
||||
/// Set a new body
|
||||
#[inline]
|
||||
pub fn map_body<F, B2>(self, f: F) -> ServiceResponse<B2>
|
||||
where
|
||||
F: FnOnce(&mut ResponseHead, B) -> B2,
|
||||
@ -412,6 +422,24 @@ impl<B> ServiceResponse<B> {
|
||||
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> {
|
||||
|
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;
|
||||
use actix_http::{
|
||||
body,
|
||||
http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version},
|
||||
test::TestRequest as HttpTestRequest,
|
||||
Extensions, Request,
|
||||
@ -20,9 +19,10 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||
use crate::cookie::{Cookie, CookieJar};
|
||||
use crate::{
|
||||
app_service::AppInitServiceState,
|
||||
body::{self, BoxBody, MessageBody},
|
||||
config::AppConfig,
|
||||
data::Data,
|
||||
dev::{AnyBody, MessageBody, Payload},
|
||||
dev::Payload,
|
||||
http::header::ContentType,
|
||||
rmap::ResourceMap,
|
||||
service::{ServiceRequest, ServiceResponse},
|
||||
@ -32,14 +32,14 @@ use crate::{
|
||||
|
||||
/// Create service that always responds with `HttpResponse::Ok()` and no body.
|
||||
pub fn ok_service(
|
||||
) -> impl Service<ServiceRequest, Response = ServiceResponse<AnyBody>, Error = Error> {
|
||||
) -> impl Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> {
|
||||
default_service(StatusCode::OK)
|
||||
}
|
||||
|
||||
/// Create service that always responds with given status code and no body.
|
||||
pub fn default_service(
|
||||
status_code: StatusCode,
|
||||
) -> impl Service<ServiceRequest, Response = ServiceResponse<AnyBody>, Error = Error> {
|
||||
) -> impl Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> {
|
||||
(move |req: ServiceRequest| {
|
||||
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)]
|
||||
mod tests {
|
||||
use std::time::SystemTime;
|
||||
|
@ -12,7 +12,7 @@ use futures_core::ready;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{
|
||||
dev,
|
||||
body, dev,
|
||||
web::{Form, Json},
|
||||
Error, FromRequest, HttpRequest, HttpResponse, Responder,
|
||||
};
|
||||
@ -146,10 +146,12 @@ where
|
||||
L: 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 {
|
||||
Either::Left(a) => a.respond_to(req),
|
||||
Either::Right(b) => b.respond_to(req),
|
||||
Either::Left(a) => a.respond_to(req).map_into_left_body(),
|
||||
Either::Right(b) => b.respond_to(req).map_into_right_body(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,9 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||
#[cfg(feature = "__compress")]
|
||||
use crate::dev::Decompress;
|
||||
use crate::{
|
||||
error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web, Error,
|
||||
HttpMessage, HttpRequest, HttpResponse, Responder,
|
||||
body::EitherBody, error::UrlencodedError, extract::FromRequest,
|
||||
http::header::CONTENT_LENGTH, web, Error, HttpMessage, HttpRequest, HttpResponse,
|
||||
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.
|
||||
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) {
|
||||
Ok(body) => HttpResponse::Ok()
|
||||
Ok(body) => match HttpResponse::Ok()
|
||||
.content_type(mime::APPLICATION_WWW_FORM_URLENCODED)
|
||||
.body(body),
|
||||
Err(err) => HttpResponse::from_error(UrlencodedError::Serialize(err)),
|
||||
.message_body(body)
|
||||
{
|
||||
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 super::*;
|
||||
use crate::http::{
|
||||
header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
|
||||
StatusCode,
|
||||
};
|
||||
use crate::test::TestRequest;
|
||||
use crate::{
|
||||
http::{
|
||||
header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
|
||||
StatusCode,
|
||||
},
|
||||
test::assert_body_eq,
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
||||
struct Info {
|
||||
@ -520,15 +533,13 @@ mod tests {
|
||||
hello: "world".to_string(),
|
||||
counter: 123,
|
||||
});
|
||||
let resp = form.respond_to(&req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let res = form.respond_to(&req);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/x-www-form-urlencoded")
|
||||
);
|
||||
|
||||
use crate::responder::tests::BodyTest;
|
||||
assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123");
|
||||
assert_body_eq!(res, b"hello=world&counter=123");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
|
@ -19,6 +19,7 @@ use actix_http::Payload;
|
||||
#[cfg(feature = "__compress")]
|
||||
use crate::dev::Decompress;
|
||||
use crate::{
|
||||
body::EitherBody,
|
||||
error::{Error, JsonPayloadError},
|
||||
extract::FromRequest,
|
||||
http::header::CONTENT_LENGTH,
|
||||
@ -116,12 +117,21 @@ impl<T: Serialize> Serialize for Json<T> {
|
||||
///
|
||||
/// If serialization failed
|
||||
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) {
|
||||
Ok(body) => HttpResponse::Ok()
|
||||
Ok(body) => match HttpResponse::Ok()
|
||||
.content_type(mime::APPLICATION_JSON)
|
||||
.body(body),
|
||||
Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)),
|
||||
.message_body(body)
|
||||
{
|
||||
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},
|
||||
StatusCode,
|
||||
},
|
||||
test::{load_body, TestRequest},
|
||||
test::{assert_body_eq, load_body, TestRequest},
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
@ -472,15 +482,13 @@ mod tests {
|
||||
let j = Json(MyObject {
|
||||
name: "test".to_string(),
|
||||
});
|
||||
let resp = j.respond_to(&req);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let res = j.respond_to(&req);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
res.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
header::HeaderValue::from_static("application/json")
|
||||
);
|
||||
|
||||
use crate::responder::tests::BodyTest;
|
||||
assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}");
|
||||
assert_body_eq!(res, b"{\"name\":\"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>
|
||||
where
|
||||
T: de::DeserializeOwned,
|
||||
|
@ -43,12 +43,12 @@ use crate::{
|
||||
/// Ok(format!("Request Body Bytes:\n{:?}", bytes))
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Payload(crate::dev::Payload);
|
||||
pub struct Payload(dev::Payload);
|
||||
|
||||
impl Payload {
|
||||
/// Unwrap to inner Payload type.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> crate::dev::Payload {
|
||||
pub fn into_inner(self) -> dev::Payload {
|
||||
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 {
|
||||
type Error = 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> {
|
||||
type Error = Error;
|
||||
type Future = Ready<Result<Self, Error>>;
|
||||
|
@ -1,14 +1,14 @@
|
||||
//! 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_router::IntoPatterns;
|
||||
pub use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
|
||||
use crate::{
|
||||
error::BlockingError, extract::FromRequest, handler::Handler, resource::Resource,
|
||||
responder::Responder, route::Route, scope::Scope, service::WebService,
|
||||
body::MessageBody, error::BlockingError, extract::FromRequest, handler::Handler,
|
||||
resource::Resource, responder::Responder, route::Route, scope::Scope, service::WebService,
|
||||
};
|
||||
|
||||
pub use crate::config::ServiceConfig;
|
||||
@ -145,6 +145,8 @@ where
|
||||
I: FromRequest + 'static,
|
||||
R: Future + '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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user