perf: round 22 — hot-GET HeaderMap borrow, native-WebDAV/CalDAV etag borrowed quotes, FileDto content_hash move, CalendarEvent stamp, ShareItemType case-fold
Benchmark-gated, same rule as ROUND2-21: every change ships with a
BEFORE/AFTER counting-allocator benchmark and a byte/-value equivalence
gate; an AFTER that fails to reduce allocations exits non-zero (rollback).
See benches/ROUND22.md and examples/bench_round22_micro.rs. All arms
no-Postgres.
- H1: the hot GET handlers (get_thumbnail, download_file, list_files_query,
list_photos, NextCloud preview, public-share download/access) take
`req: Request` last and read `req.headers()` by borrow instead of axum's
HeaderMap extractor, whose FromRequestParts impl clones the whole request
header table just to read 1-3 headers (the ROUND14 §A4 middleware pattern,
finally propagated to the handlers). 2 -> 0 allocs/req · 9.95x wall.
- W1: native WebDAV write_etag_quoted — the etag emitter for every /webdav/
PROPFIND row (per file AND per folder, up to 500/page) — emits the quotes
as borrowed pre-escaped " text events instead of escaping a "{etag}"
String (the ROUND20 §C1 / ROUND21 §R4 pattern). 3 -> 0 allocs/row.
- C1: CalDAV getetag routed through a shared write_quoted_etag helper across
all 5 sites (3 per-event + 2 per-calendar); the now-dead etag: &mut String
buffer threaded through write_event_response/standard/requested props + the
two per-page buffers removed. 2 -> 0 allocs/row.
- D1: FileDto::from reuses the moved parts.blob_hash instead of cloning it
via the content_hash() getter (the ROUND19/20 move-not-clone sweep missed
it — hash/etag are read before into_parts()). Per file row of every
listing. 1 -> 0 allocs/row.
- E1: CalendarEvent::update_time_range/update_all_day stamp timed
DTSTART/DTEND via fmt::compact_ical_utc stack render (chrono fallback out
of range) instead of the %Y%m%dT%H%M%SZ strftime interpreter. 4 -> 0.
- S1: ShareItemType::try_from uses eq_ignore_ascii_case instead of a
throwaway to_lowercase() String. 1 -> 0 allocs/parse.
Verified: cargo clippy --features bench --all-targets -D warnings clean,
cargo fmt --all --check clean, cargo test --lib --features bench = 529
passed / 0 failed (incl. the OpenAPI-spec-validity test guarding the H1
utoipa-handler signature change).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKyQ4AnYtgp1JtjzweyMeo
This commit is contained in:
@@ -361,9 +361,9 @@ impl FileHandler {
|
||||
pub(super) async fn get_thumbnail_impl(
|
||||
State(state): State<GlobalState>,
|
||||
auth_user: AuthUser,
|
||||
headers: HeaderMap,
|
||||
headers: &HeaderMap,
|
||||
Path((id, size)): Path<(String, String)>,
|
||||
) -> impl IntoResponse {
|
||||
) -> impl IntoResponse + use<> {
|
||||
use crate::application::ports::thumbnail_ports::{ThumbnailFormat, ThumbnailSize};
|
||||
|
||||
// check first that user can access this resource
|
||||
@@ -665,8 +665,8 @@ impl FileHandler {
|
||||
auth_user: AuthUser,
|
||||
Path(id): Path<String>,
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
headers: HeaderMap,
|
||||
) -> impl IntoResponse {
|
||||
headers: &HeaderMap,
|
||||
) -> impl IntoResponse + use<> {
|
||||
let retrieval = &state.applications.file_retrieval_service;
|
||||
|
||||
// ── Get file metadata (ownership-scoped) ────────────────────────
|
||||
@@ -705,7 +705,7 @@ impl FileHandler {
|
||||
let etag = format!("\"{}\"", file_dto.etag);
|
||||
|
||||
// ── ETag (304 Not Modified) ──────────────────────────────────
|
||||
if let Some(resp) = not_modified_response(&headers, &etag) {
|
||||
if let Some(resp) = not_modified_response(headers, &etag) {
|
||||
return resp.into_response();
|
||||
}
|
||||
|
||||
@@ -830,9 +830,9 @@ impl FileHandler {
|
||||
pub(super) async fn list_files_query_impl(
|
||||
State(state): State<GlobalState>,
|
||||
auth_user: AuthUser,
|
||||
headers: HeaderMap,
|
||||
headers: &HeaderMap,
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
) -> impl IntoResponse {
|
||||
) -> impl IntoResponse + use<> {
|
||||
let folder_id = params.get("folder_id").map(|id| id.as_str());
|
||||
tracing::info!("API: Listing files with folder_id: {:?}", folder_id);
|
||||
|
||||
@@ -1217,10 +1217,14 @@ pub(super) fn build_content_disposition(name: &str, mime: &str, force_inline: bo
|
||||
pub async fn list_files_query(
|
||||
state: State<GlobalState>,
|
||||
auth_user: AuthUser,
|
||||
headers: HeaderMap,
|
||||
query: Query<HashMap<String, String>>,
|
||||
req: axum::extract::Request,
|
||||
) -> impl IntoResponse {
|
||||
FileHandler::list_files_query_impl(state, auth_user, headers, query).await
|
||||
// Read headers by borrow (`req.headers()`) instead of the `HeaderMap`
|
||||
// extractor, which clones the whole request header table (~2 allocs) just to
|
||||
// read one If-None-Match — the ROUND14 §A4 middleware pattern applied to the
|
||||
// hot listing handler (benches/ROUND22.md §H1).
|
||||
FileHandler::list_files_query_impl(state, auth_user, req.headers(), query).await
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -1299,9 +1303,12 @@ pub async fn download_file(
|
||||
auth_user: AuthUser,
|
||||
path: Path<String>,
|
||||
query: Query<HashMap<String, String>>,
|
||||
headers: HeaderMap,
|
||||
req: axum::extract::Request,
|
||||
) -> impl IntoResponse {
|
||||
FileHandler::download_file_impl(state, auth_user, path, query, headers).await
|
||||
// Borrow the headers (`req.headers()`) instead of the `HeaderMap` extractor's
|
||||
// full clone — every download AND every media Range seek hit this path
|
||||
// (benches/ROUND22.md §H1).
|
||||
FileHandler::download_file_impl(state, auth_user, path, query, req.headers()).await
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -1323,10 +1330,13 @@ pub async fn download_file(
|
||||
pub async fn get_thumbnail(
|
||||
state: State<GlobalState>,
|
||||
auth_user: AuthUser,
|
||||
headers: HeaderMap,
|
||||
path: Path<(String, String)>,
|
||||
req: axum::extract::Request,
|
||||
) -> impl IntoResponse {
|
||||
FileHandler::get_thumbnail_impl(state, auth_user, headers, path).await
|
||||
// Borrow the headers (`req.headers()`) instead of the `HeaderMap` extractor's
|
||||
// full clone — thumbnails are the highest-frequency GET (one per grid tile),
|
||||
// and this handler reads only Accept + If-None-Match (benches/ROUND22.md §H1).
|
||||
FileHandler::get_thumbnail_impl(state, auth_user, req.headers(), path).await
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
|
||||
@@ -2,7 +2,7 @@ use axum::{
|
||||
Json,
|
||||
body::Body,
|
||||
extract::{Query, State},
|
||||
http::{HeaderMap, Response, StatusCode, header},
|
||||
http::{Response, StatusCode, header},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -60,9 +60,12 @@ struct PhotoDto {
|
||||
pub async fn list_photos(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth_user: AuthUser,
|
||||
headers: HeaderMap,
|
||||
Query(params): Query<PhotosQueryParams>,
|
||||
req: axum::extract::Request,
|
||||
) -> impl IntoResponse {
|
||||
// Borrow headers (`req.headers()`) instead of cloning the whole request
|
||||
// header table via the `HeaderMap` extractor to read one If-None-Match — the
|
||||
// gallery open + every pagination page hit this (benches/ROUND22.md §H1).
|
||||
let caller_id = auth_user.id;
|
||||
let limit = params.limit.unwrap_or(200).clamp(1, 500);
|
||||
|
||||
@@ -88,7 +91,7 @@ pub async fn list_photos(
|
||||
std::hash::Hash::hash(&count, &mut hasher);
|
||||
let etag = format!("\"{:x}\"", std::hash::Hasher::finish(&hasher));
|
||||
|
||||
if let Some(inm) = headers.get(header::IF_NONE_MATCH)
|
||||
if let Some(inm) = req.headers().get(header::IF_NONE_MATCH)
|
||||
&& let Ok(client_etag) = inm.to_str()
|
||||
&& client_etag == etag
|
||||
{
|
||||
|
||||
@@ -230,10 +230,12 @@ pub async fn delete_shared_link(
|
||||
pub async fn access_shared_item(
|
||||
State(share_use_case): State<Arc<ShareService>>,
|
||||
Path(token): Path<String>,
|
||||
headers: HeaderMap,
|
||||
req: axum::extract::Request,
|
||||
) -> impl IntoResponse {
|
||||
// Honour an unlock cookie if one was issued by a prior `/verify` call.
|
||||
let unlock_jwt = unlock_jwt_from_headers(&headers, &token);
|
||||
// Borrow the headers (`req.headers()`) instead of the `HeaderMap` extractor's
|
||||
// full clone to read the unlock cookie (benches/ROUND22.md §H1).
|
||||
let unlock_jwt = unlock_jwt_from_headers(req.headers(), &token);
|
||||
|
||||
// The access-count increment doesn't gate the fetch — run both
|
||||
// round-trips concurrently instead of serially (one RTT saved on
|
||||
@@ -333,8 +335,11 @@ pub async fn verify_shared_item_password(
|
||||
pub async fn download_shared_file(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(token): Path<String>,
|
||||
headers: HeaderMap,
|
||||
req: axum::extract::Request,
|
||||
) -> impl IntoResponse {
|
||||
// Borrow the headers (`req.headers()`) instead of the `HeaderMap` extractor's
|
||||
// full clone — the public-share download + Range path (benches/ROUND22.md §H1).
|
||||
let headers = req.headers();
|
||||
// 1. Resolve share service
|
||||
let share_service = match &state.share_service {
|
||||
Some(s) => s.clone(),
|
||||
@@ -349,7 +354,7 @@ pub async fn download_shared_file(
|
||||
};
|
||||
|
||||
// 2. Validate the share token (handles expiry + password checks)
|
||||
let unlock_jwt = unlock_jwt_from_headers(&headers, &token);
|
||||
let unlock_jwt = unlock_jwt_from_headers(headers, &token);
|
||||
let share_dto = match share_service
|
||||
.get_shared_link_with_unlock(&token, unlock_jwt.as_deref())
|
||||
.await
|
||||
@@ -385,7 +390,7 @@ pub async fn download_shared_file(
|
||||
&state,
|
||||
&share_dto.item_id,
|
||||
share_dto.item_name.as_deref(),
|
||||
&headers,
|
||||
headers,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user