v0.35.1: performance, AGPL licensing and documentation overhaul

- Web Server reads static files inline; proxy middleware chain flattened
- About window with license summary and project links; AGPL-3.0-or-later
- Rewritten documentation: feature list, screenshots, performance guide
- Fix false spikes in per-Vhost request-rate charts
This commit is contained in:
gru committed 2026-11-05 18:29:56 +01:00
1 parent d3b492874b
commit eba9f89283
135 files changed
+1425 -1064

No files matched your search

+9 -12
View File
@@ -12,11 +12,11 @@ pub enum RangeSelection {
}
pub fn request_range(method: &Method, headers: &HeaderMap) -> RangeSelection {
let mut values = headers.get_all("range").iter();
let mut values = headers.get_all(::http::header::RANGE).iter();
let Some(value) = values.next() else {
return RangeSelection::None;
};
if values.next().is_some() || headers.get_all("if-range").iter().count() > 1 {
if values.next().is_some() || headers.get_all(::http::header::IF_RANGE).iter().count() > 1 {
return RangeSelection::Unsupported;
}
if method != Method::GET {
@@ -74,7 +74,7 @@ fn if_range_matches(headers: &HeaderMap, if_range: Option<&HeaderValue>) -> bool
return false;
}
return headers
.get("etag")
.get(::http::header::ETAG)
.and_then(|value| value.to_str().ok())
.is_some_and(|etag| !etag.trim().starts_with("W/") && etag.trim() == candidate);
}
@@ -82,7 +82,7 @@ fn if_range_matches(headers: &HeaderMap, if_range: Option<&HeaderValue>) -> bool
return false;
};
headers
.get("last-modified")
.get(::http::header::LAST_MODIFIED)
.and_then(|value| value.to_str().ok())
.and_then(|value| httpdate::parse_http_date(value).ok())
.is_some_and(|modified| modified <= candidate_date)
@@ -127,32 +127,29 @@ impl Hit {
let full_len = self.body.len();
self.response
.headers_mut()
.insert("accept-ranges", HeaderValue::from_static("bytes"));
.insert(::http::header::ACCEPT_RANGES, HeaderValue::from_static("bytes"));
self.response
.extensions_mut()
.insert(crate::compression::Skip);
let Some((start, end)) = resolve_byte_range(range, full_len) else {
*self.response.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE;
*self.response.body_mut() = Body::empty();
self.response.headers_mut().insert(
"content-range",
self.response.headers_mut().insert(::http::header::CONTENT_RANGE,
HeaderValue::from_str(&format!("bytes */{full_len}")).expect("generated range"),
);
self.response
.headers_mut()
.insert("content-length", HeaderValue::from_static("0"));
.insert(::http::header::CONTENT_LENGTH, HeaderValue::from_static("0"));
return self;
};
let slice = self.body.slice(start..=end);
*self.response.status_mut() = StatusCode::PARTIAL_CONTENT;
*self.response.body_mut() = Body::from(slice.clone());
self.response.headers_mut().insert(
"content-range",
self.response.headers_mut().insert(::http::header::CONTENT_RANGE,
HeaderValue::from_str(&format!("bytes {start}-{end}/{full_len}"))
.expect("generated range"),
);
self.response.headers_mut().insert(
"content-length",
self.response.headers_mut().insert(::http::header::CONTENT_LENGTH,
HeaderValue::from_str(&slice.len().to_string()).expect("generated length"),
);
self.body = slice;