mirror of https://github.com/rwf2/Rocket.git
Clean up 'compression' module and documentation.
This commit is contained in:
parent
6a55aa7253
commit
0a3960b031
|
@ -80,7 +80,7 @@ r2d2-memcache = { version = "0.3", optional = true }
|
|||
time = { version = "0.1.40", optional = true }
|
||||
|
||||
# Compression dependencies
|
||||
brotli = { version = "2.5", optional = true }
|
||||
brotli = { version = "3.3", optional = true }
|
||||
flate2 = { version = "1.0", optional = true }
|
||||
|
||||
[target.'cfg(debug_assertions)'.dependencies]
|
||||
|
|
|
@ -1,22 +1,15 @@
|
|||
//! Automatic response compression.
|
||||
//!
|
||||
//! See the [`Compression`](compression::fairing::Compression) type for further
|
||||
//! details.
|
||||
|
||||
use rocket::config::{ConfigError, Value};
|
||||
use rocket::fairing::{Fairing, Info, Kind};
|
||||
use rocket::http::MediaType;
|
||||
use rocket::Rocket;
|
||||
use rocket::{Request, Response};
|
||||
|
||||
crate use super::CompressionUtils;
|
||||
|
||||
crate struct Context {
|
||||
crate exclusions: Vec<MediaType>,
|
||||
struct Context {
|
||||
exclusions: Vec<MediaType>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
crate fn new() -> Context {
|
||||
impl Default for Context {
|
||||
fn default() -> Context {
|
||||
Context {
|
||||
exclusions: vec![
|
||||
MediaType::parse_flexible("application/gzip").unwrap(),
|
||||
|
@ -28,56 +21,30 @@ impl Context {
|
|||
],
|
||||
}
|
||||
}
|
||||
crate fn with_exclusions(excls: Vec<MediaType>) -> Context {
|
||||
Context { exclusions: excls }
|
||||
}
|
||||
}
|
||||
|
||||
/// The Compression type implements brotli and gzip compression for responses in
|
||||
/// accordance with the Accept-Encoding header. If accepted, brotli compression
|
||||
/// is preferred over gzip.
|
||||
/// Compresses all responses with Brotli or Gzip compression.
|
||||
///
|
||||
/// In the brotli compression mode (using the
|
||||
/// [rust-brotli](https://github.com/dropbox/rust-brotli) crate), quality is set
|
||||
/// to 2 in order to achieve fast compression with a compression ratio similar
|
||||
/// to gzip. When appropriate, brotli's text and font compression modes are
|
||||
/// used.
|
||||
/// Compression is done in the same manner as the [`Compress`](super::Compress)
|
||||
/// responder.
|
||||
///
|
||||
/// In the gzip compression mode (using the
|
||||
/// [flate2](https://github.com/alexcrichton/flate2-rs) crate), quality is set
|
||||
/// to the default (9) in order to have good compression ratio.
|
||||
/// By default, the fairing does not compress responses with a `Content-Type`
|
||||
/// matching any of the following:
|
||||
///
|
||||
/// This fairing does not compress responses that already have a
|
||||
/// `Content-Encoding` header.
|
||||
///
|
||||
/// This fairing ignores the responses with a `Content-Type` matching any of
|
||||
/// the following default types:
|
||||
///
|
||||
/// - application/gzip
|
||||
/// - application/brotli
|
||||
/// - image/*
|
||||
/// - video/*
|
||||
/// - application/wasm
|
||||
/// - application/octet-stream
|
||||
/// - `application/gzip`
|
||||
/// - `application/zip`
|
||||
/// - `image/*`
|
||||
/// - `video/*`
|
||||
/// - `application/wasm`
|
||||
/// - `application/octet-stream`
|
||||
///
|
||||
/// The excluded types can be changed changing the `compress.exclude` Rocket
|
||||
/// configuration property.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// To use, add the `brotli_compression` feature, the `gzip_compression`
|
||||
/// feature, or the `compression` feature (to enable both algorithms) to the
|
||||
/// `rocket_contrib` dependencies section of your `Cargo.toml`:
|
||||
///
|
||||
/// ```toml,ignore
|
||||
/// [dependencies.rocket_contrib]
|
||||
/// version = "*"
|
||||
/// default-features = false
|
||||
/// features = ["compression"]
|
||||
/// ```
|
||||
///
|
||||
/// Then, ensure that the compression [fairing](/rocket/fairing/) is attached to
|
||||
/// your Rocket application:
|
||||
/// Attach the compression [fairing](/rocket/fairing/) to your Rocket
|
||||
/// application:
|
||||
///
|
||||
/// ```rust
|
||||
/// extern crate rocket;
|
||||
|
@ -117,7 +84,7 @@ impl Compression {
|
|||
/// }
|
||||
/// ```
|
||||
pub fn fairing() -> Compression {
|
||||
Compression { 0: () }
|
||||
Compression(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,45 +97,29 @@ impl Fairing for Compression {
|
|||
}
|
||||
|
||||
fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
|
||||
let mut ctxt = Context::new();
|
||||
let mut ctxt = Context::default();
|
||||
|
||||
match rocket.config().get_table("compress").and_then(|t| {
|
||||
t.get("exclude")
|
||||
.ok_or(ConfigError::Missing(String::from("exclude")))
|
||||
t.get("exclude").ok_or_else(|| ConfigError::Missing(String::from("exclude")))
|
||||
}) {
|
||||
Ok(excls) => match excls.as_array() {
|
||||
Some(excls) => {
|
||||
let mut error = false;
|
||||
let mut exclusions_vec = Vec::with_capacity(excls.len());
|
||||
for e in excls {
|
||||
match e {
|
||||
Value::String(s) => match MediaType::parse_flexible(s) {
|
||||
Some(media_type) => exclusions_vec.push(media_type),
|
||||
None => {
|
||||
error = true;
|
||||
warn_!(
|
||||
"Exclusions must be valid content types, using default compression exclusions '{:?}'",
|
||||
ctxt.exclusions
|
||||
);
|
||||
break;
|
||||
ctxt.exclusions = excls.iter().flat_map(|ex| {
|
||||
if let Value::String(s) = ex {
|
||||
let mt = MediaType::parse_flexible(s);
|
||||
if mt.is_none() {
|
||||
warn_!("Ignoring invalid media type '{:?}'", s);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
error = true;
|
||||
warn_!(
|
||||
"Exclusions must be strings, using default compression exclusions '{:?}'",
|
||||
ctxt.exclusions
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !error {
|
||||
ctxt = Context::with_exclusions(exclusions_vec);
|
||||
mt
|
||||
} else {
|
||||
warn_!("Ignoring non-string media type '{:?}'", ex);
|
||||
None
|
||||
}
|
||||
}).collect();
|
||||
}
|
||||
None => {
|
||||
warn_!(
|
||||
"Exclusions must be an array of strings, using default compression exclusions '{:?}'",
|
||||
"Exclusions is not an array; using default compression exclusions '{:?}'",
|
||||
ctxt.exclusions
|
||||
);
|
||||
}
|
||||
|
@ -187,6 +138,10 @@ impl Fairing for Compression {
|
|||
}
|
||||
|
||||
fn on_response(&self, request: &Request, response: &mut Response) {
|
||||
CompressionUtils::compress_response(request, response, true);
|
||||
let context = request
|
||||
.guard::<::rocket::State<Context>>()
|
||||
.expect("Compression Context registered in on_attach");
|
||||
|
||||
super::CompressionUtils::compress_response(request, response, &context.exclusions);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +1,49 @@
|
|||
//! `Compression` fairing and `Compressed` responder to automatically and
|
||||
//! on demand respectively compressing responses.
|
||||
//! Gzip and Brotli response compression.
|
||||
//!
|
||||
//! See the [`Compression`](compression::Compression) and
|
||||
//! [`Compress`](compression::Compress) types for further details.
|
||||
//!
|
||||
//! # Enabling
|
||||
//!
|
||||
//! This module is only available when one of the `brotli_compression`,
|
||||
//! `gzip_compression`, or `compression` features is enabled. Enable
|
||||
//! one of these in `Cargo.toml` as follows:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies.rocket_contrib]
|
||||
//! version = "0.4.0"
|
||||
//! default-features = false
|
||||
//! features = ["compression"]
|
||||
//! ```
|
||||
#[cfg(feature="brotli_compression")] extern crate brotli;
|
||||
#[cfg(feature="gzip_compression")] extern crate flate2;
|
||||
|
||||
mod fairing;
|
||||
mod responder;
|
||||
|
||||
pub use self::fairing::Compression;
|
||||
pub use self::responder::Compressed;
|
||||
pub use self::responder::Compress;
|
||||
|
||||
crate use self::fairing::Context;
|
||||
use rocket::http::hyper::header::{ContentEncoding, Encoding};
|
||||
use rocket::{Request, Response};
|
||||
use std::io::Read;
|
||||
|
||||
use rocket::http::MediaType;
|
||||
use rocket::http::hyper::header::{ContentEncoding, Encoding};
|
||||
use rocket::{Request, Response};
|
||||
|
||||
#[cfg(feature = "brotli_compression")]
|
||||
use brotli::enc::backward_references::BrotliEncoderMode;
|
||||
use self::brotli::enc::backward_references::BrotliEncoderMode;
|
||||
|
||||
#[cfg(feature = "gzip_compression")]
|
||||
use flate2::read::GzEncoder;
|
||||
use self::flate2::read::GzEncoder;
|
||||
|
||||
crate struct CompressionUtils;
|
||||
struct CompressionUtils;
|
||||
|
||||
impl CompressionUtils {
|
||||
fn accepts_encoding(request: &Request, encoding: &str) -> bool {
|
||||
request
|
||||
.headers()
|
||||
.get("Accept-Encoding")
|
||||
.flat_map(|accept| accept.split(","))
|
||||
.flat_map(|accept| accept.split(','))
|
||||
.map(|accept| accept.trim())
|
||||
.any(|accept| accept == encoding)
|
||||
}
|
||||
|
@ -44,10 +63,10 @@ impl CompressionUtils {
|
|||
|
||||
fn skip_encoding(
|
||||
content_type: &Option<rocket::http::ContentType>,
|
||||
context: &rocket::State<Context>,
|
||||
exclusions: &[MediaType],
|
||||
) -> bool {
|
||||
match content_type {
|
||||
Some(content_type) => context.exclusions.iter().any(|exc_media_type| {
|
||||
Some(content_type) => exclusions.iter().any(|exc_media_type| {
|
||||
if exc_media_type.sub() == "*" {
|
||||
*exc_media_type.top() == *content_type.top()
|
||||
} else {
|
||||
|
@ -58,26 +77,22 @@ impl CompressionUtils {
|
|||
}
|
||||
}
|
||||
|
||||
fn compress_response(request: &Request, response: &mut Response, respect_excludes: bool) {
|
||||
fn compress_response(request: &Request, response: &mut Response, exclusions: &[MediaType]) {
|
||||
if CompressionUtils::already_encoded(response) {
|
||||
return;
|
||||
}
|
||||
|
||||
let content_type = response.content_type();
|
||||
|
||||
if respect_excludes {
|
||||
let context = request
|
||||
.guard::<::rocket::State<Context>>()
|
||||
.expect("Compression Context registered in on_attach");
|
||||
|
||||
if CompressionUtils::skip_encoding(&content_type, &context) {
|
||||
if CompressionUtils::skip_encoding(&content_type, exclusions) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Compression is done when the request accepts brotli or gzip encoding
|
||||
// and the corresponding feature is enabled
|
||||
if cfg!(feature = "brotli_compression") && CompressionUtils::accepts_encoding(request, "br")
|
||||
{
|
||||
#[cfg(feature = "brotli_compression")]
|
||||
{
|
||||
if let Some(plain) = response.take_body() {
|
||||
let content_type_top = content_type.as_ref().map(|ct| ct.top());
|
||||
|
@ -98,8 +113,11 @@ impl CompressionUtils {
|
|||
Encoding::EncodingExt("br".into()),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if cfg!(feature = "gzip_compression")
|
||||
&& CompressionUtils::accepts_encoding(request, "gzip")
|
||||
{
|
||||
#[cfg(feature = "gzip_compression")]
|
||||
{
|
||||
if let Some(plain) = response.take_body() {
|
||||
let compressor = GzEncoder::new(plain.into_inner(), flate2::Compression::default());
|
||||
|
@ -108,4 +126,5 @@ impl CompressionUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,56 +1,47 @@
|
|||
//! Response on demand compression.
|
||||
//!
|
||||
//! See the [`Compression`](compression::responder::Compressed) type for
|
||||
//! further details.
|
||||
|
||||
use rocket::response::{self, Responder, Response};
|
||||
use rocket::Request;
|
||||
|
||||
crate use super::CompressionUtils;
|
||||
use super::CompressionUtils;
|
||||
|
||||
/// Compress a `Responder` response ignoring the compression exclusions.
|
||||
/// Compresses responses with Brotli or Gzip compression.
|
||||
///
|
||||
/// Delegates the remainder of the response to the wrapped `Responder`.
|
||||
/// The `Compress` type implements brotli and gzip compression for responses in
|
||||
/// accordance with the `Accept-Encoding` header. If accepted, brotli
|
||||
/// compression is preferred over gzip.
|
||||
///
|
||||
/// In the brotli compression mode (using the
|
||||
/// [rust-brotli](https://github.com/dropbox/rust-brotli) crate), quality is set
|
||||
/// to 2 in order to achieve fast compression with a compression ratio similar
|
||||
/// to gzip. When appropriate, brotli's text and font compression modes are
|
||||
/// used.
|
||||
///
|
||||
/// In the gzip compression mode (using the
|
||||
/// [flate2](https://github.com/alexcrichton/flate2-rs) crate), quality is set
|
||||
/// to the default (9) in order to have good compression ratio.
|
||||
///
|
||||
/// Responses that already have a `Content-Encoding` header are not compressed.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// To use, add the `brotli_compression` feature, the `gzip_compression`
|
||||
/// feature, or the `compression` feature (to enable both algorithms) to the
|
||||
/// `rocket_contrib` dependencies section of your `Cargo.toml`:
|
||||
///
|
||||
/// ```toml,ignore
|
||||
/// [dependencies.rocket_contrib]
|
||||
/// version = "*"
|
||||
/// default-features = false
|
||||
/// features = ["compression"]
|
||||
/// ```
|
||||
///
|
||||
/// Then, compress the desired response wrapping a `Responder` inside
|
||||
/// `Compressed`:
|
||||
/// Compress responses by wrapping a `Responder` inside `Compress`:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket_contrib::compression::Compressed;
|
||||
/// use rocket_contrib::compression::Compress;
|
||||
///
|
||||
/// # #[allow(unused_variables)]
|
||||
/// let response = Compressed("Hi.");
|
||||
/// let response = Compress("Hi.");
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Compressed<R>(pub R);
|
||||
pub struct Compress<R>(pub R);
|
||||
|
||||
impl<'r, R: Responder<'r>> Compressed<R> {
|
||||
pub fn new(response: R) -> Compressed<R> {
|
||||
Compressed { 0: response }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, R: Responder<'r>> Responder<'r> for Compressed<R> {
|
||||
impl<'r, R: Responder<'r>> Responder<'r> for Compress<R> {
|
||||
#[inline(always)]
|
||||
fn respond_to(self, request: &Request) -> response::Result<'r> {
|
||||
let mut response = Response::build()
|
||||
.merge(self.0.respond_to(request)?)
|
||||
.finalize();
|
||||
|
||||
CompressionUtils::compress_response(request, &mut response, false);
|
||||
CompressionUtils::compress_response(request, &mut response, &[]);
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
//! * [uuid](uuid) - UUID (de)serialization
|
||||
//! * [${database}_pool](databases) - Database Configuration and Pooling
|
||||
//! * [helmet](helmet) - Fairing for Security and Privacy Headers
|
||||
//! * [compression](compression) - Response compression
|
||||
//!
|
||||
//! The recommend way to include features from this crate via Cargo in your
|
||||
//! project is by adding a `[dependencies.rocket_contrib]` section to your
|
||||
|
@ -51,15 +52,7 @@
|
|||
#[cfg(feature="uuid")] pub mod uuid;
|
||||
#[cfg(feature="databases")] pub mod databases;
|
||||
#[cfg(feature = "helmet")] pub mod helmet;
|
||||
#[cfg(any(feature="brotli_compression", feature="gzip_compression"))] pub mod compression;
|
||||
|
||||
#[cfg(feature="databases")] extern crate rocket_contrib_codegen;
|
||||
#[cfg(feature="databases")] #[doc(hidden)] pub use rocket_contrib_codegen::*;
|
||||
|
||||
#[cfg(any(feature="brotli_compression", feature="gzip_compression"))]
|
||||
pub mod compression;
|
||||
|
||||
#[cfg(feature="brotli_compression")]
|
||||
extern crate brotli;
|
||||
|
||||
#[cfg(feature="gzip_compression")]
|
||||
extern crate flate2;
|
||||
|
|
|
@ -5,7 +5,7 @@ extern crate rocket;
|
|||
extern crate rocket_contrib;
|
||||
|
||||
#[cfg(all(feature = "brotli_compression", feature = "gzip_compression"))]
|
||||
mod compressed_tests {
|
||||
mod compress_responder_tests {
|
||||
extern crate brotli;
|
||||
extern crate flate2;
|
||||
|
||||
|
@ -14,8 +14,7 @@ mod compressed_tests {
|
|||
use rocket::http::{ContentType, Header};
|
||||
use rocket::local::Client;
|
||||
use rocket::response::{Content, Response};
|
||||
use rocket::routes;
|
||||
use rocket_contrib::compression::Compressed;
|
||||
use rocket_contrib::compression::Compress;
|
||||
|
||||
use std::io::Cursor;
|
||||
use std::io::Read;
|
||||
|
@ -26,29 +25,29 @@ mod compressed_tests {
|
|||
in order to have to read more than one buffer when gzipping. こんにちは!";
|
||||
|
||||
#[get("/")]
|
||||
pub fn index() -> Compressed<String> {
|
||||
Compressed::new(String::from(HELLO))
|
||||
pub fn index() -> Compress<String> {
|
||||
Compress(String::from(HELLO))
|
||||
}
|
||||
|
||||
#[get("/font")]
|
||||
pub fn font() -> Compressed<Content<&'static str>> {
|
||||
Compressed::new(Content(ContentType::WOFF, HELLO))
|
||||
pub fn font() -> Compress<Content<&'static str>> {
|
||||
Compress(Content(ContentType::WOFF, HELLO))
|
||||
}
|
||||
|
||||
#[get("/image")]
|
||||
pub fn image() -> Compressed<Content<&'static str>> {
|
||||
Compressed::new(Content(ContentType::PNG, HELLO))
|
||||
pub fn image() -> Compress<Content<&'static str>> {
|
||||
Compress(Content(ContentType::PNG, HELLO))
|
||||
}
|
||||
|
||||
#[get("/already_encoded")]
|
||||
pub fn already_encoded() -> Compressed<Response<'static>> {
|
||||
pub fn already_encoded() -> Compress<Response<'static>> {
|
||||
let mut encoder = GzEncoder::new(
|
||||
Cursor::new(String::from(HELLO)),
|
||||
flate2::Compression::default(),
|
||||
);
|
||||
let mut encoded = Vec::new();
|
||||
encoder.read_to_end(&mut encoded).unwrap();
|
||||
Compressed::new(
|
||||
Compress(
|
||||
Response::build()
|
||||
.header(ContentEncoding(vec![Encoding::Gzip]))
|
||||
.sized_body(Cursor::new(encoded))
|
||||
|
@ -57,8 +56,8 @@ mod compressed_tests {
|
|||
}
|
||||
|
||||
#[get("/identity")]
|
||||
pub fn identity() -> Compressed<Response<'static>> {
|
||||
Compressed::new(
|
||||
pub fn identity() -> Compress<Response<'static>> {
|
||||
Compress(
|
||||
Response::build()
|
||||
.header(ContentEncoding(vec![Encoding::Identity]))
|
||||
.sized_body(Cursor::new(String::from(HELLO)))
|
|
@ -5,7 +5,7 @@ extern crate rocket;
|
|||
extern crate rocket_contrib;
|
||||
|
||||
#[cfg(all(feature = "brotli_compression", feature = "gzip_compression"))]
|
||||
mod compression_tests {
|
||||
mod compression_fairing_tests {
|
||||
extern crate brotli;
|
||||
extern crate flate2;
|
||||
|
||||
|
@ -14,8 +14,8 @@ mod compression_tests {
|
|||
use rocket::http::Status;
|
||||
use rocket::http::{ContentType, Header};
|
||||
use rocket::local::Client;
|
||||
use rocket::response::Response;
|
||||
use rocket::routes;
|
||||
use rocket::response::{Content, Response};
|
||||
use rocket_contrib::compression::Compression;
|
||||
|
||||
use std::io::Cursor;
|
||||
use std::io::Read;
|
||||
|
@ -31,27 +31,18 @@ mod compression_tests {
|
|||
}
|
||||
|
||||
#[get("/font")]
|
||||
pub fn font() -> Response<'static> {
|
||||
Response::build()
|
||||
.header(ContentType::WOFF)
|
||||
.sized_body(Cursor::new(String::from(HELLO)))
|
||||
.finalize()
|
||||
pub fn font() -> Content<&'static str> {
|
||||
Content(ContentType::WOFF, HELLO)
|
||||
}
|
||||
|
||||
#[get("/image")]
|
||||
pub fn image() -> Response<'static> {
|
||||
Response::build()
|
||||
.header(ContentType::PNG)
|
||||
.sized_body(Cursor::new(String::from(HELLO)))
|
||||
.finalize()
|
||||
pub fn image() -> Content<&'static str> {
|
||||
Content(ContentType::PNG, HELLO)
|
||||
}
|
||||
|
||||
#[get("/tar")]
|
||||
pub fn tar() -> Response<'static> {
|
||||
Response::build()
|
||||
.header(ContentType::TAR)
|
||||
.sized_body(Cursor::new(String::from(HELLO)))
|
||||
.finalize()
|
||||
pub fn tar() -> Content<&'static str> {
|
||||
Content(ContentType::TAR, HELLO)
|
||||
}
|
||||
|
||||
#[get("/already_encoded")]
|
||||
|
@ -82,7 +73,7 @@ mod compression_tests {
|
|||
"/",
|
||||
routes![index, font, image, tar, already_encoded, identity],
|
||||
)
|
||||
.attach(rocket_contrib::compression::Compression::fairing())
|
||||
.attach(Compression::fairing())
|
||||
}
|
||||
|
||||
fn rocket_tar_exception() -> rocket::Rocket {
|
||||
|
@ -94,7 +85,7 @@ mod compression_tests {
|
|||
|
||||
rocket::custom(config)
|
||||
.mount("/", routes![image, tar])
|
||||
.attach(rocket_contrib::compression::Compression::fairing())
|
||||
.attach(Compression::fairing())
|
||||
}
|
||||
|
||||
#[test]
|
|
@ -79,6 +79,8 @@ if [ "$1" = "--contrib" ]; then
|
|||
redis_pool
|
||||
mongodb_pool
|
||||
memcache_pool
|
||||
brotli_compression
|
||||
gzip_compression
|
||||
)
|
||||
|
||||
pushd "${CONTRIB_LIB_ROOT}" > /dev/null 2>&1
|
||||
|
|
Loading…
Reference in New Issue