Skip to content

Commit

Permalink
module rework.
Browse files Browse the repository at this point in the history
  • Loading branch information
fakeshadow committed Jan 20, 2024
1 parent 626b182 commit 3064941
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 181 deletions.
88 changes: 47 additions & 41 deletions web/src/middleware/compress.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
use http_encoding::{encoder, Coder, ContentEncoding};
//! compression middleware
use crate::{
body::{BodyStream, NONE_BODY_HINT},
http::{header::HeaderMap, BorrowReq, WebResponse},
service::{ready::ReadyService, Service},
};
use crate::service::Service;

/// A compress middleware look into [WebRequest]'s `Accept-Encoding` header and
/// apply according compression to [WebResponse]'s body according to enabled compress feature.
Expand All @@ -15,55 +11,65 @@ use crate::{
/// by it must be able to handle it's mutation or utilize [TypeEraser] to erase the mutation.
///
/// [WebRequest]: crate::http::WebRequest
/// [WebResponse]: crate::http::WebResponse
/// [TypeEraser]: crate::middleware::eraser::TypeEraser
#[derive(Clone)]
pub struct Compress;

impl<S, E> Service<Result<S, E>> for Compress {
type Response = CompressService<S>;
type Response = service::CompressService<S>;
type Error = E;

async fn call(&self, res: Result<S, E>) -> Result<Self::Response, Self::Error> {
res.map(CompressService)
res.map(service::CompressService)
}
}

pub struct CompressService<S>(S);

impl<S, Req, ResB> Service<Req> for CompressService<S>
where
Req: BorrowReq<HeaderMap>,
S: Service<Req, Response = WebResponse<ResB>>,
ResB: BodyStream,
{
type Response = WebResponse<Coder<ResB>>;
type Error = S::Error;

async fn call(&self, req: Req) -> Result<Self::Response, Self::Error> {
let mut encoding = ContentEncoding::from_headers(req.borrow());
let res = self.0.call(req).await?;

// TODO: expose encoding filter as public api.
match res.body().size_hint() {
(low, Some(up)) if low == up && low < 64 => encoding = ContentEncoding::NoOp,
// this variant is a crate hack. see NONE_BODY_HINT for detail.
NONE_BODY_HINT => encoding = ContentEncoding::NoOp,
_ => {}
mod service {
use http_encoding::{encoder, Coder, ContentEncoding};

use crate::{
body::{BodyStream, NONE_BODY_HINT},
http::{header::HeaderMap, BorrowReq, WebResponse},
service::{ready::ReadyService, Service},
};

pub struct CompressService<S>(pub(super) S);

impl<S, Req, ResB> Service<Req> for CompressService<S>
where
Req: BorrowReq<HeaderMap>,
S: Service<Req, Response = WebResponse<ResB>>,
ResB: BodyStream,
{
type Response = WebResponse<Coder<ResB>>;
type Error = S::Error;

async fn call(&self, req: Req) -> Result<Self::Response, Self::Error> {
let mut encoding = ContentEncoding::from_headers(req.borrow());
let res = self.0.call(req).await?;

// TODO: expose encoding filter as public api.
match res.body().size_hint() {
(low, Some(up)) if low == up && low < 64 => encoding = ContentEncoding::NoOp,
// this variant is a crate hack. see NONE_BODY_HINT for detail.
NONE_BODY_HINT => encoding = ContentEncoding::NoOp,
_ => {}
}

Ok(encoder(res, encoding))
}

Ok(encoder(res, encoding))
}
}

impl<S> ReadyService for CompressService<S>
where
S: ReadyService,
{
type Ready = S::Ready;
impl<S> ReadyService for CompressService<S>
where
S: ReadyService,
{
type Ready = S::Ready;

#[inline]
async fn ready(&self) -> Self::Ready {
self.0.ready().await
#[inline]
async fn ready(&self) -> Self::Ready {
self.0.ready().await
}
}
}

Expand Down
127 changes: 67 additions & 60 deletions web/src/middleware/decompress.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
use core::{cell::RefCell, convert::Infallible};
//! decompression middleware
use http_encoding::{error::EncodingError, Coder};

use crate::{
body::BodyStream,
context::WebContext,
http::{const_header_value::TEXT_UTF8, header::CONTENT_TYPE, Request, StatusCode, WebResponse},
service::{pipeline::PipelineE, ready::ReadyService, Service},
};
use crate::service::Service;

/// A decompress middleware look into [WebContext]'s `Content-Encoding` header and
/// apply according decompression to it according to enabled compress feature.
Expand All @@ -17,74 +10,88 @@ use crate::{
/// `Decompress` would mutate request body type from `B` to `Coder<B>`. Service enclosed
/// by it must be able to handle it's mutation or utilize [TypeEraser] to erase the mutation.
///
/// [WebContext]: crate::WebContext
/// [TypeEraser]: crate::middleware::eraser::TypeEraser
#[derive(Clone)]
pub struct Decompress;

impl<S, E> Service<Result<S, E>> for Decompress {
type Response = DecompressService<S>;
type Response = service::DecompressService<S>;
type Error = E;

async fn call(&self, res: Result<S, E>) -> Result<Self::Response, Self::Error> {
res.map(DecompressService)
res.map(service::DecompressService)
}
}

pub struct DecompressService<S>(S);

pub type DecompressServiceError<E> = PipelineE<EncodingError, E>;

impl<'r, S, C, B, Res, Err> Service<WebContext<'r, C, B>> for DecompressService<S>
where
B: BodyStream + Default,
S: for<'rs> Service<WebContext<'rs, C, Coder<B>>, Response = Res, Error = Err>,
{
type Response = Res;
type Error = DecompressServiceError<Err>;

async fn call(&self, mut ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
let (parts, ext) = ctx.take_request().into_parts();
let state = ctx.ctx;
let (ext, body) = ext.replace_body(());
let req = Request::from_parts(parts, ());

let decoder = http_encoding::try_decoder(req.headers(), body).map_err(DecompressServiceError::First)?;
let mut body = RefCell::new(decoder);
let mut req = req.map(|_| ext);

self.0
.call(WebContext::new(&mut req, &mut body, state))
.await
.map_err(|e| {
// restore original body as error path of other services may have use of it.
let body = body.into_inner().into_inner();
*ctx.body_borrow_mut() = body;
DecompressServiceError::Second(e)
})
mod service {
use core::{cell::RefCell, convert::Infallible};

use http_encoding::{error::EncodingError, Coder};

use crate::{
body::BodyStream,
context::WebContext,
http::{const_header_value::TEXT_UTF8, header::CONTENT_TYPE, Request, StatusCode, WebResponse},
service::{pipeline::PipelineE, ready::ReadyService, Service},
};

pub struct DecompressService<S>(pub(super) S);

pub type DecompressServiceError<E> = PipelineE<EncodingError, E>;

impl<'r, S, C, B, Res, Err> Service<WebContext<'r, C, B>> for DecompressService<S>
where
B: BodyStream + Default,
S: for<'rs> Service<WebContext<'rs, C, Coder<B>>, Response = Res, Error = Err>,
{
type Response = Res;
type Error = DecompressServiceError<Err>;

async fn call(&self, mut ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
let (parts, ext) = ctx.take_request().into_parts();
let state = ctx.ctx;
let (ext, body) = ext.replace_body(());
let req = Request::from_parts(parts, ());

let decoder = http_encoding::try_decoder(req.headers(), body).map_err(DecompressServiceError::First)?;
let mut body = RefCell::new(decoder);
let mut req = req.map(|_| ext);

self.0
.call(WebContext::new(&mut req, &mut body, state))
.await
.map_err(|e| {
// restore original body as error path of other services may have use of it.
let body = body.into_inner().into_inner();
*ctx.body_borrow_mut() = body;
DecompressServiceError::Second(e)
})
}
}
}

impl<S> ReadyService for DecompressService<S>
where
S: ReadyService,
{
type Ready = S::Ready;
impl<S> ReadyService for DecompressService<S>
where
S: ReadyService,
{
type Ready = S::Ready;

#[inline]
async fn ready(&self) -> Self::Ready {
self.0.ready().await
#[inline]
async fn ready(&self) -> Self::Ready {
self.0.ready().await
}
}
}

impl<'r, C, B> Service<WebContext<'r, C, B>> for EncodingError {
type Response = WebResponse;
type Error = Infallible;
impl<'r, C, B> Service<WebContext<'r, C, B>> for EncodingError {
type Response = WebResponse;
type Error = Infallible;

async fn call(&self, req: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
let mut res = req.into_response(format!("{self}"));
res.headers_mut().insert(CONTENT_TYPE, TEXT_UTF8);
*res.status_mut() = StatusCode::UNSUPPORTED_MEDIA_TYPE;
Ok(res)
async fn call(&self, req: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
let mut res = req.into_response(format!("{self}"));
res.headers_mut().insert(CONTENT_TYPE, TEXT_UTF8);
*res.status_mut() = StatusCode::UNSUPPORTED_MEDIA_TYPE;
Ok(res)
}
}
}

Expand Down
Loading

0 comments on commit 3064941

Please sign in to comment.