//! `Middleware` for compressing response body. use std::cmp; use std::marker::PhantomData; use std::str::FromStr; use actix_http::body::MessageBody; use actix_http::encoding::Encoder; use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING}; use actix_service::{Service, Transform}; use futures::future::{ok, FutureResult}; use futures::{Async, Future, Poll}; use crate::service::{ServiceRequest, ServiceResponse}; #[derive(Debug, Clone)] /// `Middleware` for compressing response body. pub struct Compress(ContentEncoding); impl Compress { /// Create new `Compress` middleware with default encoding. pub fn new(encoding: ContentEncoding) -> Self { Compress(encoding) } } impl Default for Compress { fn default() -> Self { Compress::new(ContentEncoding::Auto) } } impl Transform for Compress where P: 'static, B: MessageBody, S: Service, Response = ServiceResponse>, S::Future: 'static, { type Request = ServiceRequest

; type Response = ServiceResponse>; type Error = S::Error; type InitError = (); type Transform = CompressMiddleware; type Future = FutureResult; fn new_transform(&self, service: S) -> Self::Future { ok(CompressMiddleware { service, encoding: self.0, }) } } pub struct CompressMiddleware { service: S, encoding: ContentEncoding, } impl Service for CompressMiddleware where P: 'static, B: MessageBody, S: Service, Response = ServiceResponse>, S::Future: 'static, { type Request = ServiceRequest

; type Response = ServiceResponse>; type Error = S::Error; type Future = CompressResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready() } fn call(&mut self, req: ServiceRequest

) -> Self::Future { // negotiate content-encoding let encoding = if let Some(val) = req.headers.get(ACCEPT_ENCODING) { if let Ok(enc) = val.to_str() { AcceptEncoding::parse(enc, self.encoding) } else { ContentEncoding::Identity } } else { ContentEncoding::Identity }; CompressResponse { encoding, fut: self.service.call(req), _t: PhantomData, } } } #[doc(hidden)] pub struct CompressResponse where P: 'static, B: MessageBody, S: Service, S::Future: 'static, { fut: S::Future, encoding: ContentEncoding, _t: PhantomData<(P, B)>, } impl Future for CompressResponse where P: 'static, B: MessageBody, S: Service, Response = ServiceResponse>, S::Future: 'static, { type Item = ServiceResponse>; type Error = S::Error; fn poll(&mut self) -> Poll { let resp = futures::try_ready!(self.fut.poll()); Ok(Async::Ready(resp.map_body(move |head, body| { Encoder::response(self.encoding, head, body) }))) } } struct AcceptEncoding { encoding: ContentEncoding, quality: f64, } impl Eq for AcceptEncoding {} impl Ord for AcceptEncoding { fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering { if self.quality > other.quality { cmp::Ordering::Less } else if self.quality < other.quality { cmp::Ordering::Greater } else { cmp::Ordering::Equal } } } impl PartialOrd for AcceptEncoding { fn partial_cmp(&self, other: &AcceptEncoding) -> Option { Some(self.cmp(other)) } } impl PartialEq for AcceptEncoding { fn eq(&self, other: &AcceptEncoding) -> bool { self.quality == other.quality } } impl AcceptEncoding { fn new(tag: &str) -> Option { let parts: Vec<&str> = tag.split(';').collect(); let encoding = match parts.len() { 0 => return None, _ => ContentEncoding::from(parts[0]), }; let quality = match parts.len() { 1 => encoding.quality(), _ => match f64::from_str(parts[1]) { Ok(q) => q, Err(_) => 0.0, }, }; Some(AcceptEncoding { encoding, quality }) } /// Parse a raw Accept-Encoding header value into an ordered list. pub fn parse(raw: &str, encoding: ContentEncoding) -> ContentEncoding { let mut encodings: Vec<_> = raw .replace(' ', "") .split(',') .map(|l| AcceptEncoding::new(l)) .collect(); encodings.sort(); for enc in encodings { if let Some(enc) = enc { if encoding == ContentEncoding::Auto { return enc.encoding; } else if encoding == enc.encoding { return encoding; } } } ContentEncoding::Identity } }