Skip to content

Commit

Permalink
Merge pull request #24 from BrowserSync/bslive-19-compression
Browse files Browse the repository at this point in the history
Add compression layer - fixes #19
  • Loading branch information
shakyShane authored Sep 8, 2024
2 parents 9fd079e + cef4281 commit 274e7cc
Show file tree
Hide file tree
Showing 24 changed files with 506 additions and 77 deletions.
63 changes: 0 additions & 63 deletions crates/bsnext_core/src/common_layers.rs

This file was deleted.

17 changes: 10 additions & 7 deletions crates/bsnext_core/src/handler_stack.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::common_layers::add_route_layers;
use crate::handlers::proxy::{proxy_handler, ProxyConfig};
use crate::optional_layers::optional_layers;
use crate::raw_loader::serve_raw_one;
use crate::serve_dir::try_many_services_dir;
use axum::handler::Handler;
Expand All @@ -8,6 +8,7 @@ use axum::routing::{any, any_service, get_service, MethodRouter};
use axum::{Extension, Router};
use bsnext_input::route::{DirRoute, FallbackRoute, Opts, ProxyRoute, RawRoute, Route, RouteKind};
use std::collections::HashMap;
use tower::Layer;
use tower_http::services::{ServeDir, ServeFile};

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -168,7 +169,7 @@ pub fn fallback_to_layered_method_router(route: FallbackRoute) -> MethodRouter {
match route.kind {
RouteKind::Raw(raw_route) => {
let svc = any_service(serve_raw_one.with_state(raw_route));
add_route_layers(svc, &route.opts)
optional_layers(svc, &route.opts)
}
RouteKind::Proxy(_new_proxy_route) => {
// todo(alpha): make a decision proxy as a fallback
Expand All @@ -179,7 +180,7 @@ pub fn fallback_to_layered_method_router(route: FallbackRoute) -> MethodRouter {
let item = DirRouteOpts::new(dir, route.opts, None);
let serve_dir_service = item.as_serve_file();
let service = get_service(serve_dir_service);
let layered = add_route_layers(service, &item.opts);
let layered = optional_layers(service, &item.opts);
layered
}
}
Expand All @@ -196,7 +197,9 @@ pub fn stack_to_router(path: &str, stack: HandlerStack) -> Router {
HandlerStack::None => unreachable!(),
HandlerStack::Raw { raw, opts } => {
let svc = any_service(serve_raw_one.with_state(raw));
Router::new().route_service(path, add_route_layers(svc, &opts))
let out = optional_layers(svc, &opts);

Router::new().route_service(path, out)
}
HandlerStack::Dirs(dirs) => {
Router::new().nest_service(path, serve_dir_layer(&dirs, Router::new()))
Expand All @@ -210,7 +213,7 @@ pub fn stack_to_router(path: &str, stack: HandlerStack) -> Router {
let proxy_with_decompression = proxy_handler.layer(Extension(proxy_config.clone()));
let as_service = any(proxy_with_decompression);

Router::new().nest_service(path, add_route_layers(as_service, &opts))
Router::new().nest_service(path, optional_layers(as_service, &opts))
}
HandlerStack::DirsProxy(dir_list, proxy) => {
let r2 = stack_to_router(
Expand All @@ -233,7 +236,7 @@ fn serve_dir_layer(dir_list_with_opts: &[DirRouteOpts], initial: Router) -> Rout
None => {
let serve_dir_service = dir_route.as_serve_dir();
let service = get_service(serve_dir_service);
let layered = add_route_layers(service, &dir_route.opts);
let layered = optional_layers(service, &dir_route.opts);
layered
}
Some(fallback) => {
Expand All @@ -243,7 +246,7 @@ fn serve_dir_layer(dir_list_with_opts: &[DirRouteOpts], initial: Router) -> Rout
.fallback(stack)
.call_fallback_on_method_not_allowed(true);
let service = any_service(serve_dir_service);
let layered = add_route_layers(service, &dir_route.opts);
let layered = optional_layers(service, &dir_route.opts);
layered
}
})
Expand Down
9 changes: 8 additions & 1 deletion crates/bsnext_core/src/handlers/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,14 @@ pub async fn proxy_handler(
// decompress requests if needed
if let Some(h) = req.extensions().get::<InjectHandling>() {
let req_accepted = h.items.iter().any(|item| item.accept_req(&req));
tracing::trace!(req.accepted = req_accepted);
tracing::trace!(
req.accepted = req_accepted,
req.accept.header = req
.headers()
.get("accept")
.map(|h| h.to_str().unwrap_or("")),
"will accept request + decompress"
);
if req_accepted {
let sv2 = any(serve_raw_one.layer(DecompressionLayer::new()));
return Ok(sv2.oneshot(req).await.into_response());
Expand Down
2 changes: 1 addition & 1 deletion crates/bsnext_core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
pub mod server;
pub mod servers_supervisor;

pub mod common_layers;
pub mod dir_loader;
mod handler_stack;
pub mod handlers;
pub mod meta;
pub mod not_found;
pub mod optional_layers;
pub mod panic_handler;
pub mod proxy_loader;
pub mod raw_loader;
Expand Down
141 changes: 141 additions & 0 deletions crates/bsnext_core/src/optional_layers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use axum::extract::{Request, State};
use axum::handler::Handler;
use axum::middleware::{map_response_with_state, Next};
use axum::response::{IntoResponse, Response};
use axum::routing::MethodRouter;
use axum::{middleware, Extension};
use axum_extra::middleware::option_layer;
use bsnext_input::route::{CompType, CompressionOpts, CorsOpts, DelayKind, DelayOpts, Opts};
use bsnext_resp::{response_modifications_layer, InjectHandling};
use http::{HeaderName, HeaderValue};
use std::collections::BTreeMap;
use std::convert::Infallible;
use std::time::Duration;
use tokio::time::sleep;
use tower::{Layer, ServiceBuilder};
use tower_http::compression::CompressionLayer;
use tower_http::cors::CorsLayer;

pub fn optional_layers(app: MethodRouter, opts: &Opts) -> MethodRouter {
let mut app = app;
let cors_enabled_layer = opts
.cors
.as_ref()
.filter(|v| **v == CorsOpts::Cors(true))
.map(|_| CorsLayer::permissive());

let compression_layer = opts.compression.as_ref().and_then(comp_opts_to_layer);

let delay_enabled_layer = opts
.delay
.as_ref()
.map(|delay| middleware::from_fn_with_state(delay.clone(), delay_mw));

let injections = opts.inject.as_injections();
let inject_layer = Some(injections.items.len())
.filter(|inj| *inj > 0)
.map(|_| middleware::from_fn(response_modifications_layer));

let set_response_headers_layer = opts
.headers
.as_ref()
.map(|headers| map_response_with_state(headers.clone(), set_resp_headers));

let optional_stack = ServiceBuilder::new()
.layer(option_layer(inject_layer))
.layer(option_layer(set_response_headers_layer))
.layer(option_layer(cors_enabled_layer))
.layer(option_layer(delay_enabled_layer));

app = app.layer(optional_stack);

// The compression layer has a different type, so needs to apply outside the optional stack
// this essentially wrapping everything.
// I'm sure there's a cleaner way...
if let Some(cl) = compression_layer {
app = app.layer(cl);
}

app.layer(Extension(InjectHandling {
items: injections.items,
}))
}

async fn delay_mw(
State(delay_opts): State<DelayOpts>,
req: Request,
next: Next,
) -> impl IntoResponse {
match delay_opts {
DelayOpts::Delay(DelayKind::Ms(ms)) => {
let res = next.run(req).await;
sleep(Duration::from_millis(ms)).await;
Ok::<_, Infallible>(res)
}
}
}

async fn set_resp_headers<B>(
State(header_map): State<BTreeMap<String, String>>,
mut response: Response<B>,
) -> Response<B> {
let headers = response.headers_mut();
for (k, v) in header_map {
let hn = HeaderName::from_bytes(k.as_bytes());
let hv = HeaderValue::from_bytes(v.as_bytes());
match (hn, hv) {
(Ok(k), Ok(v)) => {
tracing::debug!("did insert header `{}`: `{:?}`", k, v);
headers.insert(k, v);
}
(Ok(n), Err(_e)) => {
tracing::error!("invalid header value: `{}` for name: `{}`", v, n)
}
(Err(_e), Ok(v)) => {
tracing::error!("invalid header name `{}`", k)
}
(Err(_e), Err(_e2)) => {
tracing::error!("invalid header name AND value `{}:{}`", k, v)
}
}
}

response
}

fn comp_opts_to_layer(comp: &CompressionOpts) -> Option<CompressionLayer> {
match comp {
CompressionOpts::Bool(false) => None,
CompressionOpts::Bool(true) => Some(CompressionLayer::new()),
CompressionOpts::CompType(comp_type) => match comp_type {
CompType::Gzip => Some(
CompressionLayer::new()
.gzip(true)
.no_br()
.no_deflate()
.no_zstd(),
),
CompType::Br => Some(
CompressionLayer::new()
.br(true)
.no_gzip()
.no_deflate()
.no_zstd(),
),
CompType::Deflate => Some(
CompressionLayer::new()
.deflate(true)
.no_gzip()
.no_br()
.no_zstd(),
),
CompType::Zstd => Some(
CompressionLayer::new()
.zstd(true)
.no_gzip()
.no_deflate()
.no_br(),
),
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ Raw {
true,
),
headers: None,
compression: None,
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ DirsProxy(
true,
),
headers: None,
compression: None,
},
fallback_route: None,
},
Expand All @@ -37,6 +38,7 @@ DirsProxy(
true,
),
headers: None,
compression: None,
},
fallback_route: None,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ Raw {
true,
),
headers: None,
compression: None,
},
}
Loading

0 comments on commit 274e7cc

Please sign in to comment.