Skip to content

Commit 6a7cea8

Browse files
Merge pull request #1201 from miwig/compress-responses
Site: compress responses
2 parents 9cf0ecd + 012f1a9 commit 6a7cea8

File tree

3 files changed

+100
-6
lines changed

3 files changed

+100
-6
lines changed

Cargo.lock

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ http = "0.2"
2020
home = "0.5"
2121
chrono = "0.4"
2222
rmp-serde = "0.15"
23+
brotli = "3.3.3"
2324
semver = "1.0"
2425
ring = "0.16.10"
2526
hex = "0.4.2"

site/src/server.rs

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use brotli::enc::BrotliEncoderParams;
2+
use brotli::BrotliCompress;
13
use std::collections::HashMap;
24
use std::net::SocketAddr;
35
use std::path::Path;
@@ -134,6 +136,7 @@ impl Server {
134136
async fn handle_fallible_get_async<F, R, S, E>(
135137
&self,
136138
req: &Request,
139+
compression: &Option<BrotliEncoderParams>,
137140
handler: F,
138141
) -> Result<Response, ServerError>
139142
where
@@ -152,7 +155,7 @@ impl Server {
152155
.header_typed(ContentType::json())
153156
.header_typed(CacheControl::new().with_no_cache().with_no_store());
154157
let body = serde_json::to_vec(&result).unwrap();
155-
response.body(hyper::Body::from(body)).unwrap()
158+
maybe_compressed_response(response, body, compression)
156159
}
157160
Err(err) => http::Response::builder()
158161
.status(StatusCode::INTERNAL_SERVER_ERROR)
@@ -313,6 +316,24 @@ async fn serve_req(server: Server, req: Request) -> Result<Response, ServerError
313316
let path = req.uri().path().to_owned();
314317
let path = path.as_str();
315318

319+
let allow_compression = req
320+
.headers()
321+
.get(hyper::header::ACCEPT_ENCODING)
322+
.map_or(false, |e| e.to_str().unwrap().contains("br"));
323+
324+
let compression = if allow_compression {
325+
let mut brotli = BrotliEncoderParams::default();
326+
// In tests on /perf/graphs and /perf/get, quality = 2 reduces size by 20-40% compared to 0,
327+
// while quality = 4 takes 80% longer but reduces size by less than 5% compared to 2.
328+
// Around 4-5 is sometimes said to be "smaller and faster than gzip".
329+
// [Google's default is 6](https://github.com/google/ngx_brotli#brotli_comp_level),
330+
// higher levels offer only small size savings but are much slower.
331+
brotli.quality = 2;
332+
Some(brotli)
333+
} else {
334+
None
335+
};
336+
316337
if let Some(response) = handle_fs_path(path) {
317338
return Ok(response);
318339
}
@@ -353,13 +374,17 @@ async fn serve_req(server: Server, req: Request) -> Result<Response, ServerError
353374
"/perf/graph" => {
354375
let query = check!(parse_query_string(req.uri()));
355376
return server
356-
.handle_fallible_get_async(&req, |c| request_handlers::handle_graph(query, c))
377+
.handle_fallible_get_async(&req, &compression, |c| {
378+
request_handlers::handle_graph(query, c)
379+
})
357380
.await;
358381
}
359382
"/perf/graphs" => {
360383
let query = check!(parse_query_string(req.uri()));
361384
return server
362-
.handle_fallible_get_async(&req, |c| request_handlers::handle_graphs(query, c))
385+
.handle_fallible_get_async(&req, &compression, |c| {
386+
request_handlers::handle_graphs(query, c)
387+
})
363388
.await;
364389
}
365390
"/perf/metrics" => {
@@ -404,6 +429,7 @@ async fn serve_req(server: Server, req: Request) -> Result<Response, ServerError
404429
crate::comparison::handle_compare(check!(parse_body(&body)), &ctxt)
405430
.await
406431
.map_err(|e| e.to_string()),
432+
&compression,
407433
)),
408434
"/perf/collected" => {
409435
if !server.check_auth(&req) {
@@ -412,7 +438,10 @@ async fn serve_req(server: Server, req: Request) -> Result<Response, ServerError
412438
.body(hyper::Body::empty())
413439
.unwrap());
414440
}
415-
Ok(to_response(request_handlers::handle_collected().await))
441+
Ok(to_response(
442+
request_handlers::handle_collected().await,
443+
&compression,
444+
))
416445
}
417446
"/perf/github-hook" => {
418447
if !verify_gh(&ctxt.config, &req, &body) {
@@ -435,6 +464,7 @@ async fn serve_req(server: Server, req: Request) -> Result<Response, ServerError
435464
match event.as_str() {
436465
"issue_comment" => Ok(to_response(
437466
request_handlers::handle_github(check!(parse_body(&body)), ctxt.clone()).await,
467+
&compression,
438468
)),
439469
_ => Ok(http::Response::builder()
440470
.status(StatusCode::OK)
@@ -444,9 +474,11 @@ async fn serve_req(server: Server, req: Request) -> Result<Response, ServerError
444474
}
445475
"/perf/self-profile" => Ok(to_response(
446476
request_handlers::handle_self_profile(check!(parse_body(&body)), &ctxt).await,
477+
&compression,
447478
)),
448479
"/perf/self-profile-raw" => Ok(to_response(
449480
request_handlers::handle_self_profile_raw(check!(parse_body(&body)), &ctxt).await,
481+
&compression,
450482
)),
451483
"/perf/bootstrap" => Ok(
452484
match request_handlers::handle_bootstrap(check!(parse_body(&body)), &ctxt).await {
@@ -594,7 +626,7 @@ fn verify_gh_sig(cfg: &Config, header: &str, body: &[u8]) -> Option<bool> {
594626
Some(false)
595627
}
596628

597-
fn to_response<S>(result: ServerResult<S>) -> Response
629+
fn to_response<S>(result: ServerResult<S>, compression: &Option<BrotliEncoderParams>) -> Response
598630
where
599631
S: Serialize,
600632
{
@@ -604,7 +636,7 @@ where
604636
.header_typed(ContentType::octet_stream())
605637
.header_typed(CacheControl::new().with_no_cache().with_no_store());
606638
let body = rmp_serde::to_vec_named(&result).unwrap();
607-
response.body(hyper::Body::from(body)).unwrap()
639+
maybe_compressed_response(response, body, compression)
608640
}
609641
Err(err) => http::Response::builder()
610642
.status(StatusCode::INTERNAL_SERVER_ERROR)
@@ -615,6 +647,30 @@ where
615647
}
616648
}
617649

650+
fn maybe_compressed_response(
651+
response: http::response::Builder,
652+
body: Vec<u8>,
653+
compression: &Option<BrotliEncoderParams>,
654+
) -> Response {
655+
match compression {
656+
None => response.body(hyper::Body::from(body)).unwrap(),
657+
Some(brotli_params) => {
658+
let compressed = compress_bytes(&body, brotli_params);
659+
let response = response.header(
660+
hyper::header::CONTENT_ENCODING,
661+
hyper::header::HeaderValue::from_static("br"),
662+
);
663+
response.body(hyper::Body::from(compressed)).unwrap()
664+
}
665+
}
666+
}
667+
668+
fn compress_bytes(mut bytes: &[u8], brotli_params: &BrotliEncoderParams) -> Vec<u8> {
669+
let mut compressed = Vec::with_capacity(bytes.len());
670+
BrotliCompress(&mut bytes, &mut compressed, brotli_params).unwrap();
671+
compressed
672+
}
673+
618674
fn to_triage_response(result: ServerResult<api::triage::Response>) -> Response {
619675
match result {
620676
Ok(result) => {

0 commit comments

Comments
 (0)