feat: cargo feature for compression and compression static (#201)
* feat: cargo feature for compression and compression static
compression = ["compression-brotli", "compression-deflate", "compression-gzip", "compression-zstd"]
* docs: describe compression cargo feature [skip ci]
Diff
Cargo.toml | 11 ++++++++--
docs/content/building-from-source.md | 33 +++++++++++++++++++++++-------
src/compression.rs | 39 ++++++++++++++++++++++++++---------
src/compression_static.rs | 3 +++-
src/handler.rs | 11 +++++++---
src/lib.rs | 2 ++-
src/server.rs | 8 +++++++-
src/settings/cli.rs | 6 +++--
src/settings/file.rs | 2 ++-
src/settings/mod.rs | 8 +++++++-
src/static_files.rs | 41 ++++++++++++++++++++++++++-----------
tests/compression_static.rs | 4 ++++-
tests/static_files.rs | 9 +++++---
13 files changed, 138 insertions(+), 39 deletions(-)
@@ -37,13 +37,20 @@ path = "src/bin/server.rs"
doc = false
[features]
default = ["http2"]
default = ["compression", "http2"]
tls = ["tokio-rustls"]
http2 = ["tls"]
compression = ["compression-brotli", "compression-deflate", "compression-gzip", "compression-zstd"]
compression-brotli = ["async-compression/brotli"]
compression-deflate = ["async-compression/deflate"]
compression-gzip = ["async-compression/deflate"]
compression-zstd = ["async-compression/zstd"]
[dependencies]
anyhow = "1.0"
async-compression = { version = "0.3", default-features = false, features = ["brotli", "deflate", "gzip", "zstd", "tokio"] }
async-compression = { version = "0.3", default-features = false, optional = true, features = ["brotli", "deflate", "gzip", "zstd", "tokio"] }
bcrypt = "0.14"
bytes = "1.4"
form_urlencoded = "1.1"
@@ -21,22 +21,41 @@ Finally, the release binary should be available at `target/release/static-web-se
!!! info "Don't use the project's `Makefile`"
Please don't use the project's `Makefile` since it's only intended for development and some on-demand tasks.
## Building documentation from source
## Cargo features
All HTML documentation is located in the `docs/` project's directory and is built using [Material for MkDocs](https://github.com/squidfunk/mkdocs-material).
When building from the source, all features are enabled by default.
However, you can disable just the ones you don't need from the lists below.
It's only necessary to have [Docker](https://www.docker.com/get-started/) installed.
**Deafult** |
[**HTTP2/TLS**](./features/http2-tls.md) |
[**Compression**](./features/compression.md) |
## Cargo features
### Disable all default features
Some features are optional when running or building from the source.
For example, if you want to run without the default features like `http2` just try.
For example, if you want to run or build SWS without the default features like `compression`, `http2`, etc then just try:
```sh
# run
cargo run --no-default-features -- -h
# or build
cargo build --release --no-default-features
```
For more optional features take a look a the `[features]` section of the `cargo.toml` file adjusting them on demand.
## Building documentation from source
All HTML documentation is located in the `docs/` project's directory and is built using [Material for MkDocs](https://github.com/squidfunk/mkdocs-material).
It's only necessary to have [Docker](https://www.docker.com/get-started/) installed.
### Building documentation
@@ -8,7 +8,15 @@
use async_compression::tokio::bufread::{BrotliEncoder, DeflateEncoder, GzipEncoder, ZstdEncoder};
#[cfg(feature = "compression-brotli")]
use async_compression::tokio::bufread::BrotliEncoder;
#[cfg(feature = "compression-deflate")]
use async_compression::tokio::bufread::DeflateEncoder;
#[cfg(feature = "compression-gzip")]
use async_compression::tokio::bufread::GzipEncoder;
#[cfg(feature = "compression-zstd")]
use async_compression::tokio::bufread::ZstdEncoder;
use bytes::Bytes;
use futures_util::Stream;
use headers::{AcceptEncoding, ContentCoding, ContentType, HeaderMap, HeaderMapExt};
@@ -52,16 +60,8 @@ pub const TEXT_MIME_TYPES: [&str; 24] = [
"application/wasm",
];
pub fn get_prefered_encoding(headers: &HeaderMap<HeaderValue>) -> Option<ContentCoding> {
if let Some(ref accept_encoding) = headers.typed_get::<AcceptEncoding>() {
return accept_encoding.prefered_encoding();
}
None
}
pub fn auto(
@@ -84,18 +84,25 @@ pub fn auto(
}
}
#[cfg(feature = "compression-gzip")]
if encoding == ContentCoding::GZIP {
let (head, body) = resp.into_parts();
return Ok(gzip(head, body.into()));
}
#[cfg(feature = "compression-deflate")]
if encoding == ContentCoding::DEFLATE {
let (head, body) = resp.into_parts();
return Ok(deflate(head, body.into()));
}
#[cfg(feature = "compression-brotli")]
if encoding == ContentCoding::BROTLI {
let (head, body) = resp.into_parts();
return Ok(brotli(head, body.into()));
}
#[cfg(feature = "compression-zstd")]
if encoding == ContentCoding::ZSTD {
let (head, body) = resp.into_parts();
return Ok(zstd(head, body.into()));
@@ -107,6 +114,7 @@ pub fn auto(
#[cfg(feature = "compression-gzip")]
pub fn gzip(
mut head: http::response::Parts,
body: CompressableBody<Body, hyper::Error>,
@@ -122,6 +130,7 @@ pub fn gzip(
#[cfg(feature = "compression-deflate")]
pub fn deflate(
mut head: http::response::Parts,
body: CompressableBody<Body, hyper::Error>,
@@ -142,6 +151,7 @@ pub fn deflate(
#[cfg(feature = "compression-brotli")]
pub fn brotli(
mut head: http::response::Parts,
body: CompressableBody<Body, hyper::Error>,
@@ -160,6 +170,7 @@ pub fn brotli(
#[cfg(feature = "compression-zstd")]
pub fn zstd(
mut head: http::response::Parts,
body: CompressableBody<Body, hyper::Error>,
@@ -184,6 +195,14 @@ pub fn create_encoding_header(existing: Option<HeaderValue>, coding: ContentCodi
coding.into()
}
pub fn get_prefered_encoding(headers: &HeaderMap<HeaderValue>) -> Option<ContentCoding> {
if let Some(ref accept_encoding) = headers.typed_get::<AcceptEncoding>() {
return accept_encoding.prefered_encoding();
}
None
}
#[pin_project]
@@ -38,10 +38,13 @@ pub async fn precompressed_variant<'a>(
let comp_ext = match compression::get_prefered_encoding(headers) {
#[cfg(feature = "compression-gzip")]
Some(ContentCoding::GZIP | ContentCoding::DEFLATE) => "gz",
#[cfg(feature = "compression-brotli")]
Some(ContentCoding::BROTLI) => "br",
#[cfg(feature = "compression-zstd")]
Some(ContentCoding::ZSTD) => "zst",
_ => {
tracing::trace!(
@@ -10,8 +10,11 @@ use headers::HeaderValue;
use hyper::{header::WWW_AUTHENTICATE, Body, Request, Response, StatusCode};
use std::{future::Future, net::IpAddr, net::SocketAddr, path::PathBuf, sync::Arc};
#[cfg(feature = "compression")]
use crate::compression;
use crate::{
basic_auth, compression, control_headers, cors, custom_headers,
basic_auth, control_headers, cors, custom_headers,
directory_listing::DirListFmt,
error_page,
exts::http::MethodExt,
@@ -229,7 +232,7 @@ impl RequestHandler {
})
.await
{
Ok((mut resp, is_precompressed)) => {
Ok((mut resp, _is_precompressed)) => {
if let Some(cors_headers) = cors_headers {
if !cors_headers.is_empty() {
@@ -241,6 +244,7 @@ impl RequestHandler {
}
#[cfg(feature = "compression")]
if self.opts.compression || compression_static {
resp.headers_mut().append(
hyper::header::VARY,
@@ -249,7 +253,8 @@ impl RequestHandler {
}
if self.opts.compression && !is_precompressed {
#[cfg(feature = "compression")]
if self.opts.compression && !_is_precompressed {
resp = match compression::auto(method, headers, resp) {
Ok(res) => res,
Err(err) => {
@@ -82,7 +82,9 @@ extern crate serde;
pub mod basic_auth;
#[cfg(feature = "compression")]
pub mod compression;
#[cfg(feature = "compression")]
pub mod compression_static;
pub mod control_headers;
pub mod cors;
@@ -166,11 +166,19 @@ impl Server {
tracing::info!("security headers: enabled={}", security_headers);
#[cfg(not(feature = "compression"))]
let compression = false;
#[cfg(feature = "compression")]
let compression = general.compression;
#[cfg(feature = "compression")]
tracing::info!("auto compression: enabled={}", compression);
#[cfg(not(feature = "compression"))]
let compression_static = false;
#[cfg(feature = "compression")]
let compression_static = general.compression_static;
#[cfg(feature = "compression")]
tracing::info!("compression static: enabled={}", compression_static);
@@ -157,6 +157,7 @@ pub struct General {
pub http2_tls_key: Option<PathBuf>,
#[cfg(feature = "compression")]
#[structopt(
long,
short = "x",
@@ -164,16 +165,17 @@ pub struct General {
default_value = "true",
env = "SERVER_COMPRESSION"
)]
pub compression: bool,
#[cfg(feature = "compression")]
#[structopt(
long,
parse(try_from_str),
default_value = "false",
env = "SERVER_COMPRESSION_STATIC"
)]
pub compression_static: bool,
@@ -117,9 +117,11 @@ pub struct General {
pub cache_control_headers: Option<bool>,
#[cfg(feature = "compression")]
pub compression: Option<bool>,
#[cfg(feature = "compression")]
pub compression_static: Option<bool>,
@@ -77,8 +77,12 @@ impl Settings {
let mut log_level = opts.log_level;
let mut config_file = opts.config_file.clone();
let mut cache_control_headers = opts.cache_control_headers;
#[cfg(feature = "compression")]
let mut compression = opts.compression;
#[cfg(feature = "compression")]
let mut compression_static = opts.compression_static;
let mut page404 = opts.page404;
let mut page50x = opts.page50x;
#[cfg(feature = "http2")]
@@ -143,9 +147,11 @@ impl Settings {
if let Some(v) = general.cache_control_headers {
cache_control_headers = v
}
#[cfg(feature = "compression")]
if let Some(v) = general.compression {
compression = v
}
#[cfg(feature = "compression")]
if let Some(v) = general.compression_static {
compression_static = v
}
@@ -324,7 +330,9 @@ impl Settings {
log_level,
config_file,
cache_control_headers,
#[cfg(feature = "compression")]
compression,
#[cfg(feature = "compression")]
compression_static,
page404,
page50x,
@@ -26,10 +26,12 @@ use std::path::{Component, Path, PathBuf};
use std::pin::Pin;
use std::task::{Context, Poll};
use crate::directory_listing::DirListFmt;
#[cfg(feature = "compression")]
use crate::compression_static;
use crate::exts::http::{MethodExt, HTTP_SUPPORTED_METHODS};
use crate::exts::path::PathExt;
use crate::{compression_static, directory_listing, Result};
use crate::{directory_listing, directory_listing::DirListFmt, Result};
pub struct HandleOpts<'a> {
@@ -198,8 +200,8 @@ fn suffix_file_html_metadata(file_path: &mut PathBuf) -> (&mut PathBuf, Option<M
async fn composed_file_metadata<'a>(
mut file_path: &'a mut PathBuf,
headers: &'a HeaderMap<HeaderValue>,
compression_static: bool,
_headers: &'a HeaderMap<HeaderValue>,
_compression_static: bool,
) -> Result<FileMetadata<'a>, StatusCode> {
tracing::trace!("getting metadata for file {}", file_path.display());
@@ -211,9 +213,10 @@ async fn composed_file_metadata<'a>(
file_path.push("index.html");
if compression_static {
#[cfg(feature = "compression")]
if _compression_static {
if let Some(p) =
compression_static::precompressed_variant(file_path, headers).await
compression_static::precompressed_variant(file_path, _headers).await
{
return Ok(FileMetadata {
file_path,
@@ -243,9 +246,10 @@ async fn composed_file_metadata<'a>(
}
} else {
if compression_static {
#[cfg(feature = "compression")]
if _compression_static {
if let Some(p) =
compression_static::precompressed_variant(file_path, headers).await
compression_static::precompressed_variant(file_path, _headers).await
{
return Ok(FileMetadata {
file_path,
@@ -266,8 +270,10 @@ async fn composed_file_metadata<'a>(
}
Err(err) => {
if compression_static {
if let Some(p) = compression_static::precompressed_variant(file_path, headers).await
#[cfg(feature = "compression")]
if _compression_static {
if let Some(p) =
compression_static::precompressed_variant(file_path, _headers).await
{
return Ok(FileMetadata {
file_path,
@@ -283,6 +289,8 @@ async fn composed_file_metadata<'a>(
let new_meta: Option<Metadata>;
(file_path, new_meta) = suffix_file_html_metadata(file_path);
#[cfg(feature = "compression")]
match new_meta {
Some(new_meta) => {
return Ok(FileMetadata {
@@ -294,9 +302,9 @@ async fn composed_file_metadata<'a>(
}
_ => {
if compression_static {
if _compression_static {
if let Some(p) =
compression_static::precompressed_variant(file_path, headers).await
compression_static::precompressed_variant(file_path, _headers).await
{
return Ok(FileMetadata {
file_path,
@@ -308,6 +316,15 @@ async fn composed_file_metadata<'a>(
}
}
}
#[cfg(not(feature = "compression"))]
if let Some(new_meta) = new_meta {
return Ok(FileMetadata {
file_path,
metadata: new_meta,
is_dir: false,
precompressed_variant: None,
});
}
Err(err)
}
@@ -3,6 +3,7 @@
#![deny(rust_2018_idioms)]
#![deny(dead_code)]
#[cfg(feature = "compression")]
#[cfg(test)]
mod tests {
use bytes::Bytes;
@@ -42,6 +43,7 @@ mod tests {
dir_listing_order: 6,
dir_listing_format: &DirListFmt::Html,
redirect_trailing_slash: true,
#[cfg(feature = "compression")]
compression_static: true,
ignore_hidden_files: false,
})
@@ -96,6 +98,7 @@ mod tests {
dir_listing_order: 6,
dir_listing_format: &DirListFmt::Html,
redirect_trailing_slash: true,
#[cfg(feature = "compression")]
compression_static: true,
ignore_hidden_files: false,
})
@@ -147,6 +150,7 @@ mod tests {
dir_listing_order: 6,
dir_listing_format: &DirListFmt::Html,
redirect_trailing_slash: true,
#[cfg(feature = "compression")]
compression_static: true,
ignore_hidden_files: false,
})
@@ -11,8 +11,10 @@ mod tests {
use std::fs;
use std::path::PathBuf;
#[cfg(feature = "compression")]
use static_web_server::compression;
use static_web_server::{
compression,
directory_listing::DirListFmt,
static_files::{self, HandleOpts},
};
@@ -575,9 +577,10 @@ mod tests {
}
}
#[cfg(feature = "compression")]
#[tokio::test]
async fn handle_file_compressions() {
let encodings = ["gzip", "deflate", "br", "xyz"];
let encodings = ["gzip", "deflate", "br", "zstd", "xyz"];
let method = &Method::GET;
for enc in encodings {
@@ -612,7 +615,7 @@ mod tests {
match enc {
"gzip" | "deflate" | "br" => {
"gzip" | "deflate" | "br" | "zstd" => {
assert!(res.headers().get("content-length").is_none());
assert_eq!(res.headers()["content-encoding"], enc);
}