Now let's take a silly one
1

Configure Feed

Select the types of activity you want to include in your feed.

at main 7.3 kB View raw
1use http::Response; 2use http::header::CONTENT_TYPE; 3use http_body::Body; 4use tower_http::compression::CompressionLayer; 5use tower_http::compression::predicate::{And, Predicate, SizeAbove}; 6 7const MIN_COMPRESS_BYTES: u64 = 256; 8 9#[derive(Clone, Copy)] 10pub(crate) struct CompressibleResponse; 11 12impl Predicate for CompressibleResponse { 13 fn should_compress<B>(&self, response: &Response<B>) -> bool 14 where 15 B: Body, 16 { 17 response 18 .headers() 19 .get(CONTENT_TYPE) 20 .and_then(|value| value.to_str().ok()) 21 .map(|value| { 22 value 23 .split(';') 24 .next() 25 .unwrap_or(value) 26 .trim() 27 .to_ascii_lowercase() 28 }) 29 .is_some_and(|base| { 30 matches!( 31 base.as_str(), 32 "application/json" | "application/x-git-upload-pack-advertisement" 33 ) 34 }) 35 } 36} 37 38pub(crate) fn layer() -> CompressionLayer<And<SizeAbove, CompressibleResponse>> { 39 CompressionLayer::new() 40 .compress_when(SizeAbove::new(MIN_COMPRESS_BYTES).and(CompressibleResponse)) 41} 42 43#[cfg(test)] 44mod tests { 45 use axum::Router; 46 use axum::response::IntoResponse; 47 use axum::routing::get; 48 use http::StatusCode; 49 use http::header::{ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_RANGE, CONTENT_TYPE, RANGE}; 50 use tower::ServiceExt; 51 52 use super::layer; 53 54 async fn json_body() -> impl IntoResponse { 55 ([(CONTENT_TYPE, "application/json")], "x".repeat(4096)) 56 } 57 58 async fn advertisement() -> impl IntoResponse { 59 ( 60 [(CONTENT_TYPE, "application/x-git-upload-pack-advertisement")], 61 "x".repeat(4096), 62 ) 63 } 64 65 async fn pack_result() -> impl IntoResponse { 66 ( 67 [(CONTENT_TYPE, "application/x-git-upload-pack-result")], 68 "x".repeat(4096), 69 ) 70 } 71 72 async fn targz_archive() -> impl IntoResponse { 73 ([(CONTENT_TYPE, "application/gzip")], "x".repeat(4096)) 74 } 75 76 async fn zip_archive() -> impl IntoResponse { 77 ([(CONTENT_TYPE, "application/zip")], "x".repeat(4096)) 78 } 79 80 async fn small_json() -> impl IntoResponse { 81 ([(CONTENT_TYPE, "application/json")], "{}") 82 } 83 84 async fn cased_json() -> impl IntoResponse { 85 ( 86 [(CONTENT_TYPE, "Application/JSON; charset=utf-8")], 87 "x".repeat(4096), 88 ) 89 } 90 91 async fn partial_archive() -> impl IntoResponse { 92 ( 93 StatusCode::PARTIAL_CONTENT, 94 [ 95 (CONTENT_TYPE, "application/gzip"), 96 (CONTENT_RANGE, "bytes 0-3/4096"), 97 ], 98 "xxxx", 99 ) 100 } 101 102 fn app() -> Router { 103 Router::new() 104 .route("/json", get(json_body)) 105 .route("/adv", get(advertisement)) 106 .route("/pack", get(pack_result)) 107 .route("/targz", get(targz_archive)) 108 .route("/zip", get(zip_archive)) 109 .route("/small", get(small_json)) 110 .route("/cased", get(cased_json)) 111 .route("/partial", get(partial_archive)) 112 .layer(layer()) 113 } 114 115 async fn content_encoding(path: &str, accept: Option<&str>) -> Option<String> { 116 let mut builder = http::Request::builder().method("GET").uri(path); 117 if let Some(value) = accept { 118 builder = builder.header(ACCEPT_ENCODING, value); 119 } 120 let request = builder.body(axum::body::Body::empty()).unwrap(); 121 app() 122 .oneshot(request) 123 .await 124 .unwrap() 125 .headers() 126 .get(CONTENT_ENCODING) 127 .map(|value| value.to_str().unwrap().to_string()) 128 } 129 130 #[tokio::test] 131 async fn json_and_advertisement_compress_but_pack_bytes_pass_through() { 132 let negotiated = ["zstd", "br", "gzip"]; 133 let json = content_encoding("/json", Some("zstd, br, gzip")).await; 134 assert!( 135 json.as_deref().is_some_and(|enc| negotiated.contains(&enc)), 136 "json negotiates an encoding, got {json:?}" 137 ); 138 let adv = content_encoding("/adv", Some("zstd, br, gzip")).await; 139 assert!( 140 adv.as_deref().is_some_and(|enc| negotiated.contains(&enc)), 141 "the ref advertisement negotiates an encoding, got {adv:?}" 142 ); 143 assert_eq!( 144 content_encoding("/pack", Some("zstd, br, gzip")).await, 145 None, 146 "an already-compressed pack stream is never re-encoded" 147 ); 148 } 149 150 #[tokio::test] 151 async fn already_compressed_archives_pass_through() { 152 assert_eq!( 153 content_encoding("/targz", Some("zstd, br, gzip")).await, 154 None, 155 "a gzip archive is never re-encoded" 156 ); 157 assert_eq!( 158 content_encoding("/zip", Some("zstd, br, gzip")).await, 159 None, 160 "a zip archive is never re-encoded" 161 ); 162 } 163 164 #[tokio::test] 165 async fn zstd_outranks_brotli_outranks_gzip_on_equal_quality() { 166 assert_eq!( 167 content_encoding("/json", Some("gzip, br, zstd")) 168 .await 169 .as_deref(), 170 Some("zstd"), 171 "zstd wins over brotli and gzip at equal q" 172 ); 173 assert_eq!( 174 content_encoding("/json", Some("gzip, br")).await.as_deref(), 175 Some("br"), 176 "brotli wins over gzip at equal q" 177 ); 178 assert_eq!( 179 content_encoding("/json", Some("gzip")).await.as_deref(), 180 Some("gzip"), 181 "gzip serves the client that offers only gzip" 182 ); 183 } 184 185 #[tokio::test] 186 async fn nothing_compresses_without_accept_encoding_or_below_the_floor() { 187 assert_eq!(content_encoding("/json", None).await, None); 188 assert_eq!( 189 content_encoding("/small", Some("zstd, br, gzip")).await, 190 None, 191 "a body below the size floor is left alone" 192 ); 193 } 194 195 #[tokio::test] 196 async fn a_mixed_case_content_type_still_compresses() { 197 let negotiated = ["zstd", "br", "gzip"]; 198 let cased = content_encoding("/cased", Some("zstd, br, gzip")).await; 199 assert!( 200 cased 201 .as_deref() 202 .is_some_and(|enc| negotiated.contains(&enc)), 203 "content-type matching is case insensitive, got {cased:?}" 204 ); 205 } 206 207 #[tokio::test] 208 async fn a_partial_archive_passes_through_with_its_range_intact() { 209 let mut builder = http::Request::builder().method("GET").uri("/partial"); 210 builder = builder.header(ACCEPT_ENCODING, "zstd, br, gzip"); 211 builder = builder.header(RANGE, "bytes=0-3"); 212 let response = app() 213 .oneshot(builder.body(axum::body::Body::empty()).unwrap()) 214 .await 215 .unwrap(); 216 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); 217 assert_eq!( 218 response.headers().get(CONTENT_RANGE).unwrap(), 219 "bytes 0-3/4096", 220 "the range survives the compression layer untouched" 221 ); 222 assert_eq!( 223 response.headers().get(CONTENT_ENCODING), 224 None, 225 "a partial archive is never re-encoded" 226 ); 227 } 228}