mempool: general improvements

This commit is contained in:
nym21
2026-04-28 18:46:37 +02:00
parent 66494c081c
commit f1749472e7
95 changed files with 2545 additions and 2670 deletions
@@ -1,98 +0,0 @@
use axum::{
body::Bytes,
http::{HeaderMap, HeaderValue, header},
};
/// HTTP content encoding for pre-compressed caching.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContentEncoding {
Brotli,
Gzip,
Zstd,
Identity,
}
impl ContentEncoding {
/// Negotiate the best encoding from the Accept-Encoding header.
/// Priority: zstd > br > gzip > identity.
/// zstd is preferred over brotli: ~3-5x faster compression at comparable ratios.
/// Respects q=0 (RFC 9110 §12.5.3): encodings explicitly rejected are never selected.
pub fn negotiate(headers: &HeaderMap) -> Self {
let accept = match headers.get(header::ACCEPT_ENCODING) {
Some(v) => v,
None => return Self::Identity,
};
let s = match accept.to_str() {
Ok(s) => s,
Err(_) => return Self::Identity,
};
let mut best = Self::Identity;
for part in s.split(',') {
let mut iter = part.split(';');
let name = iter.next().unwrap_or("").trim();
let rejected = iter.any(|p| {
let p = p.trim();
p == "q=0" || p == "q=0.0" || p == "q=0.00" || p == "q=0.000"
});
if rejected {
continue;
}
match name {
"zstd" => return Self::Zstd,
"br" => best = Self::Brotli,
"gzip" if matches!(best, Self::Identity) => best = Self::Gzip,
_ => {}
}
}
best
}
/// Compress bytes with this encoding. Identity returns bytes unchanged.
pub fn compress(self, bytes: Bytes) -> Bytes {
match self {
Self::Identity => bytes,
Self::Brotli => {
use std::io::Write;
let mut output = Vec::with_capacity(bytes.len() / 2);
{
let mut writer = brotli::CompressorWriter::new(&mut output, 4096, 4, 22);
writer.write_all(&bytes).expect("brotli compression failed");
}
Bytes::from(output)
}
Self::Gzip => {
use flate2::write::GzEncoder;
use std::io::Write;
let mut encoder = GzEncoder::new(
Vec::with_capacity(bytes.len() / 2),
flate2::Compression::new(3),
);
encoder.write_all(&bytes).expect("gzip compression failed");
Bytes::from(encoder.finish().expect("gzip finish failed"))
}
Self::Zstd => {
Bytes::from(zstd::encode_all(bytes.as_ref(), 3).expect("zstd compression failed"))
}
}
}
/// Wire name used for Content-Encoding header and cache key suffix.
#[inline]
pub fn as_str(self) -> &'static str {
match self {
Self::Brotli => "br",
Self::Gzip => "gzip",
Self::Zstd => "zstd",
Self::Identity => "identity",
}
}
#[inline]
pub(crate) fn header_value(self) -> Option<HeaderValue> {
match self {
Self::Identity => None,
_ => Some(HeaderValue::from_static(self.as_str())),
}
}
}
+42 -19
View File
@@ -3,8 +3,6 @@ use axum::http::{
header::{self, IF_NONE_MATCH},
};
use super::ContentEncoding;
pub trait HeaderMapExtended {
fn has_etag(&self, etag: &str) -> bool;
fn insert_etag(&mut self, etag: &str);
@@ -14,8 +12,6 @@ pub trait HeaderMapExtended {
fn insert_content_disposition_attachment(&mut self, filename: &str);
fn insert_content_encoding(&mut self, encoding: ContentEncoding);
fn insert_content_type_application_json(&mut self);
fn insert_content_type_text_csv(&mut self);
@@ -27,18 +23,17 @@ pub trait HeaderMapExtended {
impl HeaderMapExtended for HeaderMap {
fn has_etag(&self, etag: &str) -> bool {
self.get(IF_NONE_MATCH).is_some_and(|v| {
let s = v.as_bytes();
// Match both quoted and unquoted: "etag" or etag
s == etag.as_bytes()
|| (s.len() == etag.len() + 2
&& s[0] == b'"'
&& s[s.len() - 1] == b'"'
&& &s[1..s.len() - 1] == etag.as_bytes())
let raw = v.as_bytes();
let target = etag.as_bytes();
raw == b"*"
|| raw
.split(|&b| b == b',')
.any(|entry| normalize_etag(entry) == target)
})
}
fn insert_etag(&mut self, etag: &str) {
self.insert(header::ETAG, format!("\"{etag}\"").parse().unwrap());
self.insert(header::ETAG, format!("W/\"{etag}\"").parse().unwrap());
}
fn insert_cache_control(&mut self, value: &str) {
@@ -61,13 +56,6 @@ impl HeaderMapExtended for HeaderMap {
);
}
fn insert_content_encoding(&mut self, encoding: ContentEncoding) {
if let Some(value) = encoding.header_value() {
self.insert(header::CONTENT_ENCODING, value);
self.insert_vary_accept_encoding();
}
}
fn insert_content_type_application_json(&mut self) {
self.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
}
@@ -85,3 +73,38 @@ impl HeaderMapExtended for HeaderMap {
self.insert("Sunset", sunset.parse().unwrap());
}
}
fn normalize_etag(entry: &[u8]) -> &[u8] {
let s = entry.trim_ascii();
let s = s.strip_prefix(b"W/").unwrap_or(s);
s.strip_prefix(b"\"")
.and_then(|s| s.strip_suffix(b"\""))
.unwrap_or(s)
}
#[cfg(test)]
mod tests {
use super::*;
use axum::http::HeaderValue;
fn map(if_none_match: &str) -> HeaderMap {
let mut h = HeaderMap::new();
h.insert(IF_NONE_MATCH, HeaderValue::from_str(if_none_match).unwrap());
h
}
#[test]
fn matches_weak_strong_wildcard_and_list() {
assert!(map("W/\"s1-abc\"").has_etag("s1-abc"));
assert!(map("\"s1-abc\"").has_etag("s1-abc"));
assert!(map("*").has_etag("anything"));
assert!(map("W/\"a\", W/\"s1-abc\"").has_etag("s1-abc"));
assert!(map(" W/\"s1-abc\" ").has_etag("s1-abc"));
}
#[test]
fn rejects_mismatch_and_missing() {
assert!(!map("W/\"other\"").has_etag("s1-abc"));
assert!(!HeaderMap::new().has_etag("s1-abc"));
}
}
-2
View File
@@ -1,9 +1,7 @@
mod encoding;
mod header_map;
mod response;
mod transform_operation;
pub use encoding::*;
pub use header_map::*;
pub use response::*;
pub use transform_operation::*;
+8 -19
View File
@@ -1,30 +1,18 @@
use axum::{
body::Body,
body::{Body, Bytes},
http::{HeaderMap, Response, StatusCode, header},
response::IntoResponse,
};
use serde::Serialize;
use super::header_map::HeaderMapExtended;
use crate::cache::CacheParams;
fn new_json_cached<T: Serialize>(value: T, params: &CacheParams) -> Response<Body> {
let bytes = serde_json::to_vec(&value).unwrap();
let mut response = Response::builder().body(bytes.into()).unwrap();
let h = response.headers_mut();
h.insert_content_type_application_json();
params.apply_to(h);
response
}
pub trait ResponseExtended
where
Self: Sized,
{
fn new_not_modified(params: &CacheParams) -> Self;
fn static_json<T>(headers: &HeaderMap, value: T) -> Self
where
T: Serialize;
fn static_json_bytes(headers: &HeaderMap, bytes: Bytes) -> Self;
fn static_bytes(
headers: &HeaderMap,
bytes: &'static [u8],
@@ -40,15 +28,16 @@ impl ResponseExtended for Response<Body> {
response
}
fn static_json<T>(headers: &HeaderMap, value: T) -> Self
where
T: Serialize,
{
fn static_json_bytes(headers: &HeaderMap, bytes: Bytes) -> Self {
let params = CacheParams::deploy();
if params.matches_etag(headers) {
return Self::new_not_modified(&params);
}
new_json_cached(value, &params)
let mut response = Response::new(Body::from(bytes));
let h = response.headers_mut();
h.insert_content_type_application_json();
params.apply_to(h);
response
}
fn static_bytes(