Skip to main content

pingap_core/
ctx.rs

1// Copyright 2024-2025 Tree xie.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::{Ja4Fingerprint, Plugin, get_request_host, now_ms};
16use ahash::AHashMap;
17use bytes::BytesMut;
18use http::StatusCode;
19use http::{HeaderName, HeaderValue};
20#[cfg(feature = "tracing")]
21use opentelemetry::{
22    Context,
23    global::{BoxedSpan, BoxedTracer, ObjectSafeSpan},
24    trace::{SpanKind, TraceContextExt, Tracer},
25};
26use pingora::cache::CacheKey;
27use pingora::http::RequestHeader;
28use pingora::protocols::Digest;
29use pingora::protocols::TimingDigest;
30use pingora::proxy::Session;
31use pingora_limits::inflight::Guard;
32use std::any::Any;
33use std::borrow::Cow;
34use std::fmt::Write;
35use std::sync::Arc;
36use std::sync::atomic::{AtomicU32, Ordering};
37use std::time::{Duration, Instant, SystemTime};
38use strum::EnumString;
39
40// Constants for time conversions in milliseconds.
41const SECOND: u64 = 1_000;
42const MINUTE: u64 = 60 * SECOND;
43const HOUR: u64 = 60 * MINUTE;
44
45#[inline]
46/// Format the duration in human readable format, checking smaller units first.
47/// e.g., ms, s, m, h.
48pub fn format_duration(buf: &mut BytesMut, ms: u64) {
49    if ms < SECOND {
50        // Format as milliseconds if less than a second.
51        buf.extend_from_slice(itoa::Buffer::new().format(ms).as_bytes());
52        buf.extend_from_slice(b"ms");
53    } else if ms < MINUTE {
54        // Format as seconds with one decimal place if less than a minute.
55        buf.extend_from_slice(
56            itoa::Buffer::new().format(ms / SECOND).as_bytes(),
57        );
58        let value = (ms % SECOND) / 100;
59        if value != 0 {
60            buf.extend_from_slice(b".");
61            buf.extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
62        }
63        buf.extend_from_slice(b"s");
64    } else if ms < HOUR {
65        // Format as minutes with one decimal place if less than an hour.
66        buf.extend_from_slice(
67            itoa::Buffer::new().format(ms / MINUTE).as_bytes(),
68        );
69        let value = ms % MINUTE * 10 / MINUTE;
70        if value != 0 {
71            buf.extend_from_slice(b".");
72            buf.extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
73        }
74        buf.extend_from_slice(b"m");
75    } else {
76        // Format as hours with one decimal place.
77        buf.extend_from_slice(itoa::Buffer::new().format(ms / HOUR).as_bytes());
78        let value = ms % HOUR * 10 / HOUR;
79        if value != 0 {
80            buf.extend_from_slice(b".");
81            buf.extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
82        }
83        buf.extend_from_slice(b"h");
84    }
85}
86
87#[derive(PartialEq)]
88pub enum ModifiedMode {
89    Upstream,
90    Response,
91}
92
93impl From<&str> for ModifiedMode {
94    fn from(value: &str) -> Self {
95        match value {
96            "upstream" => ModifiedMode::Upstream,
97            _ => ModifiedMode::Response,
98        }
99    }
100}
101
102/// Trait for modifying the response body.
103pub trait ModifyResponseBody: Sync + Send {
104    /// Handles the modification of response body data.
105    fn handle(
106        &mut self,
107        session: &Session,
108        body: &mut Option<bytes::Bytes>,
109        end_of_stream: bool,
110    ) -> pingora::Result<()>;
111    /// Returns the name of the modifier.
112    fn name(&self) -> &str {
113        "unknown"
114    }
115}
116
117/// What a plugin does with the body of the request as it is passed on to
118/// the upstream: looks at it (a digest to check) or keeps a copy of it (a
119/// mirror). It does not change it.
120///
121/// Set by the plugin at its request step
122/// ([`Ctx::add_request_body_handler`]) and called by the proxy for each
123/// chunk. A request the upstream is never asked for - answered by a later
124/// plugin, or from the cache - has its body passed to nobody, and the
125/// handler is not called.
126pub trait HandleRequestBody: Sync + Send {
127    /// A chunk of the body, `None` where there is none, and whether it is
128    /// the last. An error ends the request, and the upstream is not sent
129    /// this chunk.
130    ///
131    /// The end is not always said. A request that turns out to have no
132    /// body may get one call with `end_of_stream` or none at all (pingora
133    /// sends the header of such a request to an HTTP/2 upstream as its
134    /// end, and passes nothing through the filter). And a body with a
135    /// `Content-Length` is complete for the upstream once that many bytes
136    /// are out, which may be well before a client over HTTP/2 ends its
137    /// stream. Whoever has to act when the body is complete goes by the
138    /// length the request declares, not by this flag alone.
139    fn handle(
140        &mut self,
141        body: Option<&bytes::Bytes>,
142        end_of_stream: bool,
143    ) -> pingora::Result<()>;
144    /// The upstream is tried again: the body comes from its first byte
145    /// once more, what pingora has kept of it and then the rest.
146    fn restart(&mut self);
147}
148
149/// Information about a single client connection.
150#[derive(Default)]
151pub struct ConnectionInfo {
152    /// A unique identifier for the connection.
153    pub id: usize,
154    /// The IP address of the client.
155    pub client_ip: Option<String>,
156    /// The remote address of the client connection.
157    pub remote_addr: Option<String>,
158    /// The remote port of the client connection.
159    pub remote_port: Option<u16>,
160    /// The server address the client connected to.
161    pub server_addr: Option<String>,
162    /// The server port the client connected to.
163    pub server_port: Option<u16>,
164    /// The TLS version used for the connection, if any.
165    ///
166    /// Stored as `Cow<'static, str>` so values borrowed from pingora's
167    /// `SslDigest` (usually `&'static str`) do not allocate per request.
168    pub tls_version: Option<Cow<'static, str>>,
169    /// The TLS cipher used for the connection, if any.
170    pub tls_cipher: Option<Cow<'static, str>>,
171    /// The JA4 fingerprint of the client's ClientHello, when the server
172    /// collects it. Shared by every request on the connection.
173    pub ja4: Option<Arc<Ja4Fingerprint>>,
174    /// The certificate the client presented in the TLS handshake, on a
175    /// server that asks for one (`tls_client_ca`). It was verified
176    /// against that CA: a certificate that does not verify ends the
177    /// handshake. Shared by every request on the connection.
178    pub tls_client_cert: Option<Arc<TlsClientCert>>,
179    /// Indicates whether the connection was reused (e.g., HTTP keep-alive).
180    pub reused: bool,
181}
182
183/// What is told about the certificate of a client: enough to say who it
184/// is to an upstream, a plugin or the access log.
185#[derive(Debug, Clone, Default, PartialEq, Eq)]
186pub struct TlsClientCert {
187    /// The subject, its parts in the order the certificate has them:
188    /// `O=Example, CN=device-42`.
189    pub subject: String,
190    /// The SHA-256 of the certificate, in lower case hex.
191    pub fingerprint: String,
192    /// The serial number, in lower case hex.
193    pub serial: String,
194}
195
196/// All timing-related metrics for the request lifecycle.
197// #[derive(Default)]
198pub struct Timing {
199    /// Timestamp in milliseconds when the request was created.
200    pub created_at: Instant,
201    /// The total duration of the client connection in milliseconds.
202    /// May be large for reused connections.
203    pub connection_duration: u64,
204    /// The duration of the TLS handshake with the client in milliseconds.
205    pub tls_handshake: Option<i32>,
206    /// The total duration to connect to the upstream server in milliseconds.
207    pub upstream_connect: Option<i32>,
208    /// The duration of the TCP connection to the upstream server in milliseconds.
209    pub upstream_tcp_connect: Option<i32>,
210    /// The duration of the TLS handshake with the upstream server in milliseconds.
211    pub upstream_tls_handshake: Option<i32>,
212    /// How long the upstream connect waited for an offload thread before it
213    /// began, in milliseconds. Only present when
214    /// `basic.upstream_connect_offload_*` is on; it separates scheduling
215    /// delay in the offload pool from network latency.
216    pub upstream_connect_offload_wait: Option<i32>,
217    /// The duration the upstream server took to process the request in milliseconds.
218    pub upstream_processing: Option<i32>,
219    /// The duration from sending the request to receiving the upstream response in milliseconds.
220    pub upstream_response: Option<i32>,
221    /// From the upstream connection to the first chunk of the upstream's
222    /// response body, in milliseconds: for a stream (server-sent events,
223    /// the tokens of a model) the time to the first of it, which the
224    /// response header, sent at once, does not tell.
225    pub upstream_first_body: Option<i32>,
226    /// The total duration of the upstream connection in milliseconds.
227    pub upstream_connection_duration: Option<u64>,
228    /// The duration of the cache lookup in milliseconds.
229    pub cache_lookup: Option<i32>,
230    /// The duration spent waiting for a cache lock in milliseconds.
231    pub cache_lock: Option<i32>,
232}
233
234impl Default for Timing {
235    fn default() -> Self {
236        Self {
237            created_at: Instant::now(),
238            connection_duration: 0,
239            tls_handshake: None,
240            upstream_connect: None,
241            upstream_tcp_connect: None,
242            upstream_tls_handshake: None,
243            upstream_connect_offload_wait: None,
244            upstream_processing: None,
245            upstream_response: None,
246            upstream_first_body: None,
247            upstream_connection_duration: None,
248            cache_lookup: None,
249            cache_lock: None,
250        }
251    }
252}
253
254/// Trait for upstream instance, used to handle the upstream instance lifecycle.
255pub trait UpstreamInstance: Send + Sync {
256    fn on_transport_failure(&self, address: &str);
257    fn on_response(&self, address: &str, status: StatusCode);
258    /// Marks the request as finished on this upstream.
259    ///
260    /// Returns the number of in-flight requests still being processed *after*
261    /// releasing this one (so a zero means the upstream is idle).
262    fn completed(&self) -> i32;
263}
264
265/// Trait for location instance
266pub trait LocationInstance: Send + Sync {
267    /// Get location's name
268    fn name(&self) -> &str;
269    /// Get the upstream of location
270    fn upstream(&self) -> &str;
271    /// The upstream a request goes to when the upstream has no backend for
272    /// it, or one it could not connect to and no retry is left. Empty for
273    /// a location without one.
274    fn fallback_upstream(&self) -> &str {
275        ""
276    }
277    /// Whether the location has a rewrite rule at all, so that the uri of
278    /// a request is only kept (`Features::original_uri`) where it may be
279    /// replaced.
280    fn has_rewrite(&self) -> bool {
281        false
282    }
283    /// Rewrites the request url. Returns whether the path changed; named
284    /// captures of the rewrite pattern are added to `variables`. An error
285    /// when the rule gives something that is no path: the request is then
286    /// refused and not sent on with the path it came with.
287    fn rewrite(
288        &self,
289        header: &mut RequestHeader,
290        variables: &mut Option<AHashMap<String, String>>,
291    ) -> pingora::Result<bool>;
292    /// Returns the proxy header to upstream
293    fn headers(&self) -> Option<&Vec<(HeaderName, HeaderValue, bool)>>;
294    /// Returns the client body size limit
295    fn client_body_size_limit(&self) -> usize;
296    /// Called when the request is received from the client
297    /// Returns
298    /// `Result<(u64, i32)>` - A tuple containing:
299    ///   - The new total number of accepted requests (u64)
300    ///   - The new number of currently processing requests (i32)
301    fn on_request(&self) -> pingora::Result<(u64, i32)>;
302    /// Called when the response is received from the upstream
303    fn on_response(&self);
304    /// Puts the timeouts of the location in place of the upstream's, on the
305    /// peer of one of its requests. A location that sets none leaves the
306    /// peer as it is.
307    fn apply_timeouts(
308        &self,
309        _options: &mut pingora::upstreams::peer::PeerOptions,
310    ) {
311    }
312}
313
314/// Information about the upstream (backend) server.
315#[derive(Default)]
316pub struct UpstreamInfo {
317    /// Upstream instance
318    pub upstream_instance: Option<Arc<dyn UpstreamInstance>>,
319    /// Location instance
320    pub location_instance: Option<Arc<dyn LocationInstance>>,
321    /// The location (route) that directed the request to this upstream.
322    pub location: Arc<str>,
323    /// The name of the upstream server or group.
324    pub name: Arc<str>,
325    /// The address of the upstream server.
326    pub address: String,
327    /// Indicates if the connection to the upstream was reused.
328    pub reused: bool,
329    /// The number of requests currently being processed by the upstream.
330    pub processing_count: Option<i32>,
331    /// The current number of active connections to the upstream.
332    pub connected_count: Option<i32>,
333    /// The HTTP status code of upstream response.
334    pub status: Option<StatusCode>,
335    /// The number of retries for failed connections.
336    pub retries: u8,
337    /// The request has gone over to the fallback upstream of its location,
338    /// or is to with its next attempt.
339    pub fallback: bool,
340    /// The upstream the request left for the fallback: it could not take
341    /// it, which the dashboard counts as its error.
342    pub fallback_from: Option<Arc<str>>,
343    /// Maximum number of retries for failed connections.
344    pub max_retries: Option<u8>,
345    /// The maximum total time window allowed for an operation and all of its subsequent retries.
346    ///
347    /// The timer starts from the beginning of the **initial attempt**. Once this time window
348    /// is exceeded, no more retries will be initiated, even if the maximum number of
349    /// retries (`max_retries`) has not been reached.
350    ///
351    /// If set to `None`, there is no time limit for the retry process.
352    pub max_retry_window: Option<Duration>,
353    /// The backends this request was sent to and that failed it in a way
354    /// that is retried. The next attempt goes to another one, where there
355    /// is one.
356    pub failed_addresses: Vec<String>,
357    /// The request as one in flight on its backend, for an upstream that
358    /// chooses by that (`least_conn`): counted from the attempt to the
359    /// next one, or to the end of the request.
360    pub backend_inflight: Option<InflightGuard>,
361    /// The cookie that keeps the client on the backend it was given,
362    /// for an upstream that does so (`sticky:<cookie>`): `name=value`,
363    /// to be set on the response when the client does not have it yet,
364    /// or has it for another backend.
365    pub sticky_cookie: Option<String>,
366}
367
368/// One request in flight on a backend, for as long as this is kept: the
369/// count goes up when it is made and down when it is dropped, whichever
370/// way the request ends.
371pub struct InflightGuard(Arc<AtomicU32>);
372
373impl InflightGuard {
374    pub fn new(count: Arc<AtomicU32>) -> Self {
375        count.fetch_add(1, Ordering::Relaxed);
376        Self(count)
377    }
378}
379
380impl Drop for InflightGuard {
381    fn drop(&mut self) {
382        self.0.fetch_sub(1, Ordering::Relaxed);
383    }
384}
385
386/// What a limit has left for the client of this request, as the
387/// `X-RateLimit-*` response headers tell it.
388#[derive(Debug, Clone, Copy, PartialEq, Eq)]
389pub struct RateLimitQuota {
390    /// What the limit allows: requests in any one interval, or at once.
391    pub limit: u64,
392    /// How many of them are left, this request counted.
393    pub remaining: u64,
394    /// In how many seconds all of them are back if nothing more is sent.
395    /// `None` for a limit on concurrent requests, where that is whenever
396    /// they end.
397    pub reset: Option<u64>,
398}
399
400/// State related to the current request being processed.
401#[derive(Default)]
402pub struct RequestState {
403    /// A unique identifier for the request.
404    pub request_id: Option<String>,
405    /// The HTTP status code of the response.
406    pub status: Option<StatusCode>,
407    /// The size of the request payload in bytes.
408    pub payload_size: usize,
409    /// The inflight limiters this request is counted in, one guard each.
410    /// A limiter's count goes down again when its guard is dropped, at the
411    /// end of the request.
412    pub guards: Vec<Guard>,
413    /// The total number of requests currently being processed by the service.
414    pub processing_count: i32,
415    /// The total number of requests accepted by the service.
416    pub accepted_count: u64,
417    /// The number of requests currently being processed for this location.
418    pub location_processing_count: i32,
419    /// The total number of requests accepted for this location.
420    pub location_accepted_count: u64,
421    /// The admin answered the request: it is no part of the traffic the
422    /// dashboard shows, which would otherwise be its own polling.
423    pub admin: bool,
424    /// The request passed every filter and is on its way to an upstream.
425    /// A failure from here on closes the client's connection, whatever
426    /// the proxy says of it: pingora asks whether it may be kept only for
427    /// a request that failed in one of the filters.
428    pub proxying: bool,
429    /// A stale response of the cache is being answered for a `5xx` of
430    /// the upstream (`stale-if-error`). The proxy goes by it to tell
431    /// where the body of that answer ends, which pingora does not say on
432    /// this path: see `Server::response_body_filter`.
433    pub stale_for_status: bool,
434    /// The plugins have been told that the body of the response ends:
435    /// they are told once, whoever says it.
436    pub body_end_said: bool,
437    /// The budget to report to the client, from the limit that has the
438    /// least left of those that were asked to report theirs.
439    pub rate_limit: Option<RateLimitQuota>,
440}
441
442/// Components of the cache key that stand for something this request
443/// said - the coding it accepts, the image formats it takes - and that
444/// another request for the same url has others in the place of.
445///
446/// A `PURGE` is such another request: it names the url and says nothing of
447/// codings or formats. What it has to remove is the entry under every one
448/// of the alternatives, not only under its own.
449#[derive(Clone, Debug)]
450pub struct CacheKeyVariant {
451    /// Where in `keys` its components start.
452    pub at: usize,
453    /// How many of them there are for this request, which may be none.
454    pub len: usize,
455    /// Every list of components that can stand here, the empty one
456    /// included.
457    pub alternatives: Arc<Vec<Vec<String>>>,
458}
459
460/// Which parameters of the query a cache key is made of.
461#[derive(Debug, Clone, PartialEq)]
462pub enum CacheQueryRule {
463    /// All but these.
464    Ignore(Vec<String>),
465    /// These and no other.
466    Allow(Vec<String>),
467}
468
469impl CacheQueryRule {
470    /// The query string a cache key is made of: the parameters the rule
471    /// keeps, in the order of their names.
472    ///
473    /// `?b=2&a=1` and `?a=1&b=2` are the same page and were two entries,
474    /// and a tracking parameter (`utm_source`, `fbclid`) made one entry
475    /// per visitor. Parameters of the same name keep the order they came
476    /// in, which is theirs to mean something. Names are compared as they
477    /// are written, not decoded.
478    pub fn key_query(&self, query: &str) -> String {
479        fn name(pair: &str) -> &str {
480            pair.split('=').next().unwrap_or(pair)
481        }
482        let mut pairs: Vec<&str> = query
483            .split('&')
484            .filter(|pair| !pair.is_empty())
485            .filter(|pair| {
486                let name = name(pair);
487                match self {
488                    Self::Ignore(names) => !names.iter().any(|n| n == name),
489                    Self::Allow(names) => names.iter().any(|n| n == name),
490                }
491            })
492            .collect();
493        pairs.sort_by(|a, b| name(a).cmp(name(b)));
494        pairs.join("&")
495    }
496}
497
498/// All cache-related configuration and statistics for a request.
499#[derive(Default)]
500pub struct CacheInfo {
501    /// The namespace for cache entries.
502    pub namespace: Option<String>,
503    /// The list of keys used to generate the final cache key.
504    pub keys: Option<Vec<String>>,
505    /// Which of `keys` are one alternative among several, see
506    /// [`CacheKeyVariant`].
507    pub key_variants: Option<Vec<CacheKeyVariant>>,
508    /// Whether to respect Cache-Control headers.
509    pub check_cache_control: bool,
510    /// The maximum time-to-live for cache entries.
511    pub max_ttl: Option<Duration>,
512    /// Request headers the origin's `Vary` response header may turn into
513    /// cache variants (lowercased); `None` honours every header it names.
514    pub vary_headers: Option<Arc<Vec<String>>>,
515    /// How long a response stays fresh when the origin names no lifetime,
516    /// for the statuses that are kept by default, in place of one second.
517    pub default_ttl: Option<Duration>,
518    /// The same for single statuses, also ones that are not kept by
519    /// default: `(status, lifetime)`. A lifetime of zero keeps a status
520    /// out.
521    pub status_ttl: Option<Arc<Vec<(u16, Duration)>>>,
522    /// Which parameters of the query the cache key is made of, where
523    /// the plugin has a rule for that.
524    pub query_rule: Option<Arc<CacheQueryRule>>,
525    /// The query the key of this request was made of by that rule, from
526    /// the moment the key is made (`settle_key_query`): with the
527    /// parameters the rule leaves out gone and the others in order.
528    /// Empty for no query at all. The upstream is asked with it too, see
529    /// `ask_with_key_query`.
530    pub key_query: Option<String>,
531    /// The request headers the key is made of as well (`headers` of the
532    /// cache plugin), by name. Their values are read when the key is
533    /// made, see [`CacheInfo::header_key_components`].
534    pub key_headers: Option<Arc<Vec<String>>>,
535    /// The client asked for a copy that is checked with the origin
536    /// (`Cache-Control: no-cache`), and the plugin lets clients do that:
537    /// what is cached is revalidated before it is served.
538    pub revalidate: bool,
539    /// The number of cache read operations performed.
540    pub reading_count: Option<u32>,
541    /// The number of cache write operations performed.
542    pub writing_count: Option<u32>,
543}
544
545/// The value of a header as one component of the cache key. The
546/// components are joined with `:`, so one inside a value is written
547/// `%3A` (and `%` as `%25`): `Origin: https://a.com` with nothing after it
548/// and `Origin: https` followed by `//a.com:` were one key, and whoever
549/// sent the second chose the response the first was given.
550pub fn cache_key_slot(value: &str) -> String {
551    if !value.contains([':', '%']) {
552        return value.to_string();
553    }
554    value.replace('%', "%25").replace(':', "%3A")
555}
556
557/// What `header` has under `name`, as a component of the cache key: every
558/// line of it, as the upstream gets them. Read from the first line alone,
559/// a second one a client or a plugin had added was sent on and not in the
560/// key.
561fn header_key_slot(header: &RequestHeader, name: &str) -> String {
562    let mut values = header.headers.get_all(name).iter();
563    let Some(first) = values.next() else {
564        return String::new();
565    };
566    let mut value = String::from_utf8_lossy(first.as_bytes()).into_owned();
567    for more in values {
568        value.push_str(", ");
569        value.push_str(&String::from_utf8_lossy(more.as_bytes()));
570    }
571    cache_key_slot(&value)
572}
573
574impl CacheInfo {
575    /// The components the key has for the headers it is made of
576    /// (`headers` of the cache plugin): one for each name, kept for a
577    /// header the request does not have, and none at all for a request
578    /// that has none of them.
579    ///
580    /// The values are those of `header`, the request as it is when the
581    /// key is made: after every request plugin has run, as the query of
582    /// the key is, and so what the upstream is asked with. A plugin that
583    /// is listed after the cache has set its header by then: `jwt`
584    /// passing a claim on (`claims_to_headers`), `request_headers`. Read
585    /// when the cache plugin ran, such a header was not in the key at
586    /// all - or it was what the client itself had sent under that name -
587    /// and the page of one user was the next one's.
588    ///
589    /// A key that is made of another request, a purge, has the values of
590    /// that one: it names the variant by sending the headers itself.
591    fn header_key_components(&self, header: &RequestHeader) -> Vec<String> {
592        let Some(names) = &self.key_headers else {
593            return vec![];
594        };
595        let mut slots: Vec<String> = names
596            .iter()
597            .map(|name| header_key_slot(header, name))
598            .collect();
599        if slots.iter().all(|slot| slot.is_empty()) {
600            slots.clear();
601        }
602        slots
603    }
604
605    /// Makes the query the cache key is made of, from the request as it
606    /// is now. Called when the key is made, which is after the request
607    /// plugins have run: one that takes a parameter out of the query
608    /// (`key_auth` with `hide_credentials`) has done so by then, also
609    /// when it is listed after the cache. Taken when the cache plugin
610    /// ran, the key query had the credential in it, and the upstream was
611    /// then asked with what was meant to be hidden from it.
612    pub fn settle_key_query(&mut self, header: &RequestHeader) {
613        if let Some(rule) = &self.query_rule {
614            self.key_query =
615                Some(rule.key_query(header.uri.query().unwrap_or_default()));
616        }
617    }
618
619    /// Gives `header`, the request that goes to the upstream, the query
620    /// the cache key is made of, where that is not the request's own.
621    ///
622    /// The key says which parameters a response depends on; with the
623    /// others sent on all the same it was the upstream that decided. A
624    /// parameter spelled another way than the rule has it (`p%61ge=2`
625    /// for `page`, `x=1;page=2`) was left out of the key and read by the
626    /// upstream, and page 2 was stored as the page without a number, for
627    /// everyone. Asked with the query of the key, the upstream can not
628    /// tell two requests of one key apart.
629    ///
630    /// Only the request to the upstream is changed, not the client's:
631    /// the access log and the plugins see what was asked for. `false`
632    /// when the uri can not be made, and then the request must not be
633    /// sent as it is.
634    pub fn ask_with_key_query(&self, header: &mut RequestHeader) -> bool {
635        let made;
636        let query = match (&self.key_query, &self.query_rule) {
637            (Some(query), _) => query.as_str(),
638            // No key was made for this request, so nothing is stored
639            // either: the rule is applied all the same, for an upstream
640            // that sees one kind of query on this location.
641            (None, Some(rule)) => {
642                made = rule.key_query(header.uri.query().unwrap_or_default());
643                made.as_str()
644            },
645            (None, None) => return true,
646        };
647        // `/a?` is `/a` in the key, and is sent as that.
648        if header.uri.query() == (!query.is_empty()).then_some(query) {
649            return true;
650        }
651        let path_and_query = if query.is_empty() {
652            header.uri.path().to_string()
653        } else {
654            format!("{}?{query}", header.uri.path())
655        };
656        let mut parts = header.uri.clone().into_parts();
657        let Ok(path_and_query) = path_and_query.parse() else {
658            return false;
659        };
660        parts.path_and_query = Some(path_and_query);
661        let Ok(uri) = http::Uri::from_parts(parts) else {
662            return false;
663        };
664        header.set_uri(uri);
665        true
666    }
667}
668
669/// A limit on how fast a response body is sent: so many bytes a second,
670/// once the first `after` of them are out.
671///
672/// It is kept by what has been sent and how long that has taken, not by
673/// a pause per chunk: the time the sending itself takes is part of the
674/// time a body is given, so a client that reads slowly is not slowed
675/// down a second time. Time that was not used is not saved up.
676#[derive(Debug, Clone)]
677pub struct BodyPace {
678    rate: u64,
679    after: u64,
680    sent: u64,
681    /// When the first byte past `after` was about to be sent.
682    limited_since: Option<Instant>,
683}
684
685impl BodyPace {
686    /// `rate` bytes a second, at least one; the first `after` bytes as
687    /// fast as they go.
688    pub fn new(rate: u64, after: u64) -> Self {
689        Self {
690            rate: rate.max(1),
691            after,
692            sent: 0,
693            limited_since: None,
694        }
695    }
696
697    /// How long to wait after a chunk of `len` bytes, for it and what
698    /// went before it to have taken their time at the rate: `None` when
699    /// the body is on time, or not limited yet. For whoever writes the
700    /// chunks itself, and asks between one and the next.
701    pub fn delay(&mut self, len: usize) -> Option<Duration> {
702        self.delay_at(len, Instant::now())
703    }
704
705    /// How long to wait before a chunk of `len` bytes, for what went
706    /// before it to have taken its time: nothing for the first chunk,
707    /// which is how a body that is written in one piece is not held back
708    /// at all. For whoever is asked before a chunk is written, and has
709    /// the wait done for it (the proxy, by pingora).
710    pub fn delay_before(&mut self, len: usize) -> Option<Duration> {
711        self.delay_before_at(len, Instant::now())
712    }
713
714    fn delay_at(&mut self, len: usize, now: Instant) -> Option<Duration> {
715        self.count(len, now);
716        self.owed(now)
717    }
718
719    fn delay_before_at(
720        &mut self,
721        len: usize,
722        now: Instant,
723    ) -> Option<Duration> {
724        let owed = self.owed(now);
725        self.count(len, now);
726        owed
727    }
728
729    fn count(&mut self, len: usize, now: Instant) {
730        self.sent = self.sent.saturating_add(len as u64);
731        if self.sent > self.after {
732            self.limited_since.get_or_insert(now);
733        }
734    }
735
736    /// The time that what has been counted is still to take.
737    fn owed(&mut self, now: Instant) -> Option<Duration> {
738        let limited = self.sent.checked_sub(self.after).filter(|n| *n > 0)?;
739        let since = self.limited_since?;
740        // What the limited part of the body is given, at the rate.
741        let due = Duration::from_secs_f64(limited as f64 / self.rate as f64);
742        let taken = now.saturating_duration_since(since);
743        if taken > due {
744            // Behind, which is the client's doing. The time it did not
745            // use is not kept for it: counted from the start, a body that
746            // stood still for a minute could then go out a minute's worth
747            // at once.
748            self.limited_since = now.checked_sub(due);
749            return None;
750        }
751        Some(due - taken).filter(|delay| !delay.is_zero())
752    }
753}
754
755/// Optional features like tracing, plugins, and response modifications.
756#[derive(Default)]
757pub struct Features {
758    /// A map of custom variables for request processing.
759    pub variables: Option<AHashMap<String, String>>,
760    /// The uri the client asked for, when the location has rewritten it.
761    /// A plugin that sends the client somewhere relative to where it is
762    /// (the `directory` plugin, adding the closing slash) goes by this:
763    /// the rewritten path is the upstream's, not the browser's.
764    pub original_uri: Option<http::Uri>,
765    /// A list of plugin names and their processing times in milliseconds.
766    pub plugin_processing_times: Option<Vec<(Arc<str>, u32)>>,
767    /// Statistics about response compression.
768    pub compression_stat: Option<CompressionStat>,
769    /// A map of plugin names and their response body handlers.
770    pub modify_body_handlers:
771        Option<AHashMap<String, Box<dyn ModifyResponseBody>>>,
772    /// What a plugin settled at one step of a request and goes by again
773    /// at a later one, under a name of its own: see
774    /// [`Ctx::set_plugin_note`].
775    pub plugin_notes: Option<Vec<(String, &'static str)>>,
776    /// What a plugin keeps for a later step of a request that a note
777    /// cannot say, under a name of its own: see [`Ctx::set_plugin_value`].
778    pub plugin_values: Option<Vec<(String, Box<dyn Any + Send + Sync>)>>,
779    /// What plugins do with the body of the request as it passes, see
780    /// [`HandleRequestBody`].
781    pub request_body_handlers: Option<Vec<Box<dyn HandleRequestBody>>>,
782    /// How fast the body of the response is sent, where a plugin limits
783    /// that (`bandwidth_limit`): gone by wherever a body is written, for
784    /// a response from the upstream or the cache as for a file a plugin
785    /// sends itself.
786    pub body_pace: Option<BodyPace>,
787    /// A plugin reads the body of the answer to this request on its way
788    /// to the client and has to have it as the upstream sends it: see
789    /// [`Ctx::keep_response_body_plain`].
790    pub plain_response_body: Option<PlainBody>,
791    /// OpenTelemetry tracer for distributed tracing (available with the "tracing" feature).
792    #[cfg(feature = "tracing")]
793    pub otel_tracer: Option<OtelTracer>,
794    /// OpenTelemetry span for the upstream request (available with the "tracing" feature).
795    #[cfg(feature = "tracing")]
796    pub upstream_span: Option<BoxedSpan>,
797}
798
799/// The responses whose body a plugin reads, see
800/// [`Ctx::keep_response_body_plain`].
801#[derive(Debug, Clone, PartialEq)]
802pub enum PlainBody {
803    /// Whatever the response is.
804    Any,
805    /// Those of these content types: prefixes, in lower case.
806    Types(Vec<String>),
807}
808
809#[derive(Default)]
810/// Statistics about response compression operations.
811pub struct CompressionStat {
812    /// The algorithm used for compression (e.g., "gzip", "br").
813    pub algorithm: String,
814    /// The size of the data before compression in bytes.
815    pub in_bytes: usize,
816    /// The size of the data after compression in bytes.
817    pub out_bytes: usize,
818    /// The time taken to perform the compression operation.
819    pub duration: Duration,
820}
821
822impl CompressionStat {
823    /// Calculates the compression ratio.
824    pub fn ratio(&self) -> f64 {
825        if self.out_bytes == 0 {
826            return 0.0;
827        }
828        (self.in_bytes as f64) / (self.out_bytes as f64)
829    }
830}
831
832/// A wrapper for OpenTelemetry tracing components.
833#[cfg(feature = "tracing")]
834pub struct OtelTracer {
835    /// The tracer instance.
836    pub tracer: BoxedTracer,
837    /// The main span for the incoming HTTP request.
838    pub http_request_span: BoxedSpan,
839}
840
841#[cfg(feature = "tracing")]
842impl OtelTracer {
843    /// Creates a new child span for an upstream request.
844    #[inline]
845    pub fn new_upstream_span(&self, name: &str) -> BoxedSpan {
846        self.tracer
847            .span_builder(name.to_string())
848            .with_kind(SpanKind::Client)
849            .start_with_context(
850                &self.tracer,
851                // Set the parent span context to link this upstream span with the main request span.
852                &Context::current().with_remote_span_context(
853                    self.http_request_span.span_context().clone(),
854                ),
855            )
856    }
857}
858
859/// A plugin paired with the name it was registered under in the location config.
860pub type NamedPlugin = (Arc<str>, Arc<dyn Plugin>);
861
862/// A `Ctx` value the access log can print. The `{:name}` in a log format
863/// parses into one of these once, when the format is read, so writing a
864/// log line dispatches on an enum instead of comparing the name against
865/// every key on every request.
866#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, strum::Display)]
867#[strum(serialize_all = "snake_case")]
868pub enum CtxLogField {
869    ConnectionId,
870    UpstreamReused,
871    UpstreamStatus,
872    UpstreamAddr,
873    UpstreamName,
874    Processing,
875    UpstreamConnected,
876    UpstreamConnectTime,
877    UpstreamConnectTimeHuman,
878    UpstreamProcessingTime,
879    UpstreamProcessingTimeHuman,
880    UpstreamResponseTime,
881    UpstreamResponseTimeHuman,
882    UpstreamFirstBodyTime,
883    UpstreamFirstBodyTimeHuman,
884    UpstreamTcpConnectTime,
885    UpstreamTcpConnectTimeHuman,
886    UpstreamTlsHandshakeTime,
887    UpstreamTlsHandshakeTimeHuman,
888    UpstreamConnectOffloadWaitTime,
889    UpstreamConnectOffloadWaitTimeHuman,
890    UpstreamConnectionTime,
891    UpstreamConnectionTimeHuman,
892    ConnectionTime,
893    ConnectionTimeHuman,
894    Location,
895    ConnectionReused,
896    TlsVersion,
897    TlsCipher,
898    TlsClientSubject,
899    TlsClientFingerprint,
900    TlsClientSerial,
901    TlsClientVerified,
902    Ja4,
903    #[strum(serialize = "ja4_r")]
904    Ja4R,
905    #[strum(serialize = "ja4_o")]
906    Ja4O,
907    #[strum(serialize = "ja4_ro")]
908    Ja4Ro,
909    TlsHandshakeTime,
910    TlsHandshakeTimeHuman,
911    CompressionTime,
912    CompressionTimeHuman,
913    CompressionRatio,
914    CacheLookupTime,
915    CacheLookupTimeHuman,
916    CacheLockTime,
917    CacheLockTimeHuman,
918    ServiceTime,
919    ServiceTimeHuman,
920}
921
922/// Represents the state of a request/response cycle, tracking various metrics and properties
923/// including connection details, caching information, and upstream server interactions.
924#[derive(Default)]
925pub struct Ctx {
926    /// Information about the client connection.
927    pub conn: ConnectionInfo,
928    /// Information about the upstream server.
929    pub upstream: UpstreamInfo,
930    /// Timing metrics for the request lifecycle.
931    pub timing: Timing,
932    /// State related to the current request.
933    pub state: RequestState,
934    /// Cache-related information. Boxed behind an `Option`: most requests
935    /// never touch the cache, and a `None` costs a pointer instead of the
936    /// whole struct on every `Ctx`.
937    pub cache: Option<Box<CacheInfo>>,
938    /// Optional features, boxed for the same reason.
939    pub features: Option<Box<Features>>,
940    /// Plugins for the current location, shared with the location's cache
941    /// of resolved plugins so a request only bumps a reference count.
942    pub plugins: Option<Arc<[NamedPlugin]>>,
943    /// The same list, left here while the request plugins run when one of
944    /// them asks to see the responses of the others
945    /// (`handles_plugin_response`), for a plugin that writes its response
946    /// itself: see [`crate::decorate_plugin_response`].
947    pub response_plugins: Option<Arc<[NamedPlugin]>>,
948}
949
950/// Helper struct to store connection timing and TLS details
951#[derive(Debug, Default)]
952pub struct DigestDetail {
953    /// A guess at reuse: the connection was established more than 100 ms
954    /// before this request. It is only a guess - a slow client or a long TLS
955    /// handshake trips it on a brand-new connection - so the proxy takes the
956    /// exact keepalive signal pingora gives it for HTTP/1 and applies this
957    /// only to HTTP/2, where streams share one connection with no signal.
958    pub connection_reused: bool,
959    /// Age of the connection in milliseconds, wall clock.
960    pub connection_time: u64,
961    /// Timestamp when TCP connection was established
962    pub tcp_established: u64,
963    /// Timestamp when TLS handshake completed
964    pub tls_established: u64,
965    /// TCP connect time in milliseconds, measured by pingora on a monotonic
966    /// clock. Only a connection this side opened has one; an accepted
967    /// connection reports `None`.
968    pub tcp_connect: Option<u64>,
969    /// TLS handshake time in milliseconds, measured by pingora on a monotonic
970    /// clock around the handshake itself; `None` without TLS or when it was
971    /// not measured.
972    pub tls_handshake: Option<u64>,
973    /// Time the connect spent queued for an offload thread, in milliseconds.
974    /// `None` unless the connect was offloaded.
975    pub connect_offload_wait: Option<u64>,
976    /// TLS protocol version if using HTTPS
977    pub tls_version: Option<Cow<'static, str>>,
978    /// TLS cipher suite in use if using HTTPS
979    pub tls_cipher: Option<Cow<'static, str>>,
980    /// The certificate of the peer, where the listener's handshake kept
981    /// one for the connection.
982    pub tls_client_cert: Option<Arc<TlsClientCert>>,
983}
984
985#[inline]
986pub(crate) fn timing_to_ms(timing: Option<&Option<TimingDigest>>) -> u64 {
987    match timing {
988        Some(Some(item)) => item
989            .established_ts
990            .duration_since(SystemTime::UNIX_EPOCH)
991            .unwrap_or_default()
992            .as_millis() as u64,
993        _ => 0,
994    }
995}
996
997/// Extracts timing and TLS information from connection digest.
998/// Used for metrics and logging connection details.
999#[inline]
1000pub fn get_digest_detail(digest: &Digest) -> DigestDetail {
1001    let tcp_established = timing_to_ms(digest.timing_digest.first());
1002    let mut connection_time = 0;
1003    let now = now_ms();
1004    if tcp_established > 0 && tcp_established < now {
1005        connection_time = now - tcp_established;
1006    }
1007    let connection_reused = connection_time > 100;
1008    // The first entry is the transport layer, the last one the outermost
1009    // layer - the TLS session when there is one, otherwise the transport
1010    // layer again, which is why the handshake is only read under TLS.
1011    let tcp_connect = establishment_ms(digest.timing_digest.first());
1012    let connect_offload_wait = offload_wait_ms(digest.timing_digest.first());
1013
1014    let Some(ssl_digest) = &digest.ssl_digest else {
1015        return DigestDetail {
1016            connection_reused,
1017            tcp_established,
1018            connection_time,
1019            tcp_connect,
1020            connect_offload_wait,
1021            ..Default::default()
1022        };
1023    };
1024
1025    DigestDetail {
1026        connection_reused,
1027        tcp_established,
1028        connection_time,
1029        tcp_connect,
1030        connect_offload_wait,
1031        tls_established: timing_to_ms(digest.timing_digest.last()),
1032        tls_handshake: establishment_ms(digest.timing_digest.last()),
1033        // Clone the Cow: Borrowed(&'static str) is allocation-free.
1034        tls_version: Some(ssl_digest.version.clone()),
1035        tls_cipher: Some(ssl_digest.cipher.clone()),
1036        // Made once, when the handshake was done: a request takes a
1037        // reference to it.
1038        tls_client_cert: ssl_digest
1039            .extension
1040            .get::<Arc<TlsClientCert>>()
1041            .cloned(),
1042    }
1043}
1044
1045/// The layer's own establishment time, when pingora measured it.
1046fn establishment_ms(timing: Option<&Option<TimingDigest>>) -> Option<u64> {
1047    timing
1048        .and_then(|item| item.as_ref())
1049        .and_then(|item| item.establishment_duration)
1050        .map(|duration| duration.as_millis() as u64)
1051}
1052
1053/// How long the transport connect waited for an offload thread, when it was
1054/// offloaded at all.
1055fn offload_wait_ms(timing: Option<&Option<TimingDigest>>) -> Option<u64> {
1056    timing
1057        .and_then(|item| item.as_ref())
1058        .and_then(|item| item.offload_wait_duration)
1059        .map(|duration| duration.as_millis() as u64)
1060}
1061
1062impl Ctx {
1063    /// Creates a new Ctx instance with the current timestamp and default values.
1064    ///
1065    /// Returns a new Ctx struct initialized with the current timestamp and all other fields
1066    /// set to their default values.
1067    pub fn new() -> Self {
1068        Self {
1069            ..Default::default()
1070        }
1071    }
1072
1073    /// Adds a variable to the state's variables map with the given key and value.
1074    ///
1075    /// # Arguments
1076    /// * `key` - The variable name.
1077    /// * `value` - The value to store for this variable.
1078    #[inline]
1079    pub fn add_variable(&mut self, key: &str, value: &str) {
1080        // Lazily initialize features and variables map.
1081        let features = self.features.get_or_insert_default();
1082        let variables = features.variables.get_or_insert_with(AHashMap::new);
1083        variables.insert(key.to_string(), value.to_string());
1084    }
1085
1086    /// Extends the variables map with the given key-value pairs.
1087    ///
1088    /// # Arguments
1089    /// * `values` - A HashMap containing the key-value pairs to add.
1090    #[inline]
1091    pub fn extend_variables(&mut self, values: AHashMap<String, String>) {
1092        let features = self.features.get_or_insert_default();
1093        if let Some(variables) = features.variables.as_mut() {
1094            variables.extend(values);
1095        } else {
1096            features.variables = Some(values);
1097        }
1098    }
1099
1100    /// Returns the value of a variable by key.
1101    ///
1102    /// # Arguments
1103    /// * `key` - The key of the variable to retrieve.
1104    ///
1105    /// Returns: Option<&str> representing the value of the variable, or None if the variable does not exist.
1106    #[inline]
1107    pub fn get_variable(&self, key: &str) -> Option<&str> {
1108        self.features
1109            .as_ref()?
1110            .variables
1111            .as_ref()?
1112            .get(key)
1113            .map(|v| v.as_str())
1114    }
1115
1116    /// Keeps `note` for the plugin that calls itself `name`, until the
1117    /// request ends.
1118    ///
1119    /// For a decision that is made once and has to hold: the request a
1120    /// later step sees is not the one the first step saw - a location
1121    /// rewrites the path, another plugin takes a parameter out of the
1122    /// query or changes a header - and a plugin that decided again from
1123    /// what is there then could come to the other answer.
1124    #[inline]
1125    pub fn set_plugin_note(&mut self, name: &str, note: &'static str) {
1126        let notes = self
1127            .features
1128            .get_or_insert_default()
1129            .plugin_notes
1130            .get_or_insert_default();
1131        match notes.iter_mut().find(|(key, _)| key == name) {
1132            Some(found) => found.1 = note,
1133            None => notes.push((name.to_string(), note)),
1134        }
1135    }
1136
1137    /// What [`Ctx::set_plugin_note`] kept under `name`.
1138    #[inline]
1139    pub fn get_plugin_note(&self, name: &str) -> Option<&'static str> {
1140        self.features
1141            .as_ref()?
1142            .plugin_notes
1143            .as_ref()?
1144            .iter()
1145            .find(|(key, _)| key == name)
1146            .map(|(_, note)| *note)
1147    }
1148
1149    /// Keeps `value` for the plugin that calls itself `name`, until the
1150    /// request ends: a [`Ctx::set_plugin_note`] that is not a text, such
1151    /// as what the request step hands to the response step.
1152    pub fn set_plugin_value<T: Any + Send + Sync>(
1153        &mut self,
1154        name: &str,
1155        value: T,
1156    ) {
1157        let values = self
1158            .features
1159            .get_or_insert_default()
1160            .plugin_values
1161            .get_or_insert_default();
1162        match values.iter_mut().find(|(key, _)| key == name) {
1163            Some(found) => found.1 = Box::new(value),
1164            None => values.push((name.to_string(), Box::new(value))),
1165        }
1166    }
1167
1168    /// What [`Ctx::set_plugin_value`] kept under `name`, if it is a `T`.
1169    pub fn get_plugin_value<T: Any + Send + Sync>(
1170        &self,
1171        name: &str,
1172    ) -> Option<&T> {
1173        self.features
1174            .as_ref()?
1175            .plugin_values
1176            .as_ref()?
1177            .iter()
1178            .find(|(key, _)| key == name)
1179            .and_then(|(_, value)| value.downcast_ref::<T>())
1180    }
1181
1182    /// Says that a plugin reads the body of the answer to this request on
1183    /// its way to the client - signs it as a token, rewrites it by rules -
1184    /// and so has to have it as the upstream sends it.
1185    ///
1186    /// What looks at the response before the plugins of the response step
1187    /// do goes by it: compression in its upstream mode, which codes the
1188    /// body as it arrives from the upstream, leaves such a response as it
1189    /// comes. Coded there, the body was opaque bytes to every plugin
1190    /// after it, whatever their order in the list.
1191    ///
1192    /// `types` are the content types the plugin reads (prefixes, in lower
1193    /// case), where it reads only some: the others are none of its
1194    /// business and are coded as before. `None` is every response.
1195    #[inline]
1196    pub fn keep_response_body_plain(&mut self, types: Option<&[String]>) {
1197        let wanted =
1198            &mut self.features.get_or_insert_default().plain_response_body;
1199        *wanted = match (wanted.take(), types) {
1200            (Some(PlainBody::Any), _) | (_, None) => Some(PlainBody::Any),
1201            (Some(PlainBody::Types(mut all)), Some(types)) => {
1202                all.extend_from_slice(types);
1203                Some(PlainBody::Types(all))
1204            },
1205            (None, Some(types)) => Some(PlainBody::Types(types.to_vec())),
1206        };
1207    }
1208
1209    /// Whether a plugin asked for the body of this response as the
1210    /// upstream sends it ([`Ctx::keep_response_body_plain`]). `is_of` says
1211    /// whether the response is of one of the content types it is given.
1212    #[inline]
1213    pub fn response_body_stays_plain(
1214        &self,
1215        is_of: impl FnOnce(&[String]) -> bool,
1216    ) -> bool {
1217        let wanted = self
1218            .features
1219            .as_ref()
1220            .and_then(|features| features.plain_response_body.as_ref());
1221        match wanted {
1222            None => false,
1223            Some(PlainBody::Any) => true,
1224            Some(PlainBody::Types(types)) => is_of(types),
1225        }
1226    }
1227
1228    /// Adds a modify body handler to the context.
1229    ///
1230    /// # Arguments
1231    /// * `name` - The name of the handler.
1232    /// * `handler` - The handler to add.
1233    #[inline]
1234    pub fn add_modify_body_handler(
1235        &mut self,
1236        name: &str,
1237        handler: Box<dyn ModifyResponseBody>,
1238    ) {
1239        let features = self.features.get_or_insert_default();
1240        let handlers = features
1241            .modify_body_handlers
1242            .get_or_insert_with(AHashMap::new);
1243        handlers.insert(name.to_string(), handler);
1244    }
1245
1246    /// Has `handler` see the body of the request as the proxy passes it
1247    /// to the upstream.
1248    #[inline]
1249    pub fn add_request_body_handler(
1250        &mut self,
1251        handler: Box<dyn HandleRequestBody>,
1252    ) {
1253        self.features
1254            .get_or_insert_default()
1255            .request_body_handlers
1256            .get_or_insert_default()
1257            .push(handler);
1258    }
1259
1260    /// Returns the modify body handler by name.
1261    #[inline]
1262    pub fn get_modify_body_handler(
1263        &mut self,
1264        name: &str,
1265    ) -> Option<&mut Box<dyn ModifyResponseBody>> {
1266        self.features
1267            .as_mut()
1268            .and_then(|f| f.modify_body_handlers.as_mut())
1269            .and_then(|h| h.get_mut(name))
1270    }
1271
1272    // A private helper function to filter out time values that are too large (over an hour),
1273    // which might indicate an error or uninitialized state.
1274    #[inline]
1275    fn get_time_field(&self, field: Option<i32>) -> Option<u32> {
1276        if let Some(value) = field
1277            && value >= 0
1278        {
1279            return Some(value as u32);
1280        }
1281        None
1282    }
1283
1284    /// Returns the upstream response time if it's less than one hour, otherwise None.
1285    /// This helps filter out potentially invalid or stale timing data.
1286    ///
1287    /// Returns: Option<u64> representing milliseconds, or None if time exceeds 1 hour.
1288    #[inline]
1289    pub fn get_upstream_response_time(&self) -> Option<u32> {
1290        self.get_time_field(self.timing.upstream_response)
1291    }
1292
1293    /// Returns the upstream connect time if it's less than one hour, otherwise None.
1294    /// This helps filter out potentially invalid or stale timing data.
1295    ///
1296    /// Returns: Option<u64> representing milliseconds, or None if time exceeds 1 hour.
1297    #[inline]
1298    pub fn get_upstream_connect_time(&self) -> Option<u32> {
1299        self.get_time_field(self.timing.upstream_connect)
1300    }
1301
1302    /// Returns the upstream processing time if it's less than one hour, otherwise None.
1303    /// This helps filter out potentially invalid or stale timing data.
1304    ///
1305    /// Returns: Option<u64> representing milliseconds, or None if time exceeds 1 hour.
1306    #[inline]
1307    pub fn get_upstream_processing_time(&self) -> Option<u32> {
1308        self.get_time_field(self.timing.upstream_processing)
1309    }
1310
1311    /// From the upstream connection to the first chunk of the response
1312    /// body, in milliseconds. None for a response without a body, and
1313    /// for one that did not come from an upstream.
1314    #[inline]
1315    pub fn get_upstream_first_body_time(&self) -> Option<u32> {
1316        self.get_time_field(self.timing.upstream_first_body)
1317    }
1318
1319    /// Adds a plugin processing time to the context.
1320    ///
1321    /// # Arguments
1322    /// * `name` - The name of the plugin.
1323    /// * `time` - The time taken by the plugin in milliseconds.
1324    #[inline]
1325    pub fn add_plugin_processing_time(&mut self, name: &Arc<str>, time: u32) {
1326        // Lazily initialize features and the processing times vector.
1327        let features = self.features.get_or_insert_default();
1328        let times = features
1329            .plugin_processing_times
1330            .get_or_insert_with(|| Vec::with_capacity(5));
1331        if let Some(item) = times.iter_mut().find(|item| &item.0 == name) {
1332            item.1 += time;
1333        } else {
1334            times.push((Arc::clone(name), time));
1335        }
1336    }
1337
1338    /// Appends a formatted value to the provided log buffer based on the given key.
1339    /// Handles various metrics including connection info, timing data, and TLS details.
1340    ///
1341    /// Resolves `key` on every call; a caller that knows the key ahead of
1342    /// time (the access log format) should parse it into a [`CtxLogField`]
1343    /// once and use [`Ctx::append_log_field`].
1344    ///
1345    /// # Arguments
1346    /// * `buf` - The BytesMut buffer to append the value to.
1347    /// * `key` - The key identifying which state value to format and append.
1348    #[inline]
1349    pub fn append_log_value(&self, buf: &mut BytesMut, key: &str) {
1350        // Unknown keys append nothing.
1351        if let Ok(field) = key.parse::<CtxLogField>() {
1352            self.append_log_field(buf, field);
1353        }
1354    }
1355
1356    /// Appends the value of `field` to the log buffer.
1357    pub fn append_log_field(&self, buf: &mut BytesMut, field: CtxLogField) {
1358        // A macro to simplify formatting and appending optional time values.
1359        macro_rules! append_time {
1360            // Append raw milliseconds.
1361            ($val:expr) => {
1362                if let Some(ms) = $val {
1363                    buf.extend(itoa::Buffer::new().format(ms).as_bytes());
1364                }
1365            };
1366            // Append human-readable formatted time.
1367            ($val:expr, human) => {
1368                if let Some(ms) = $val {
1369                    format_duration(buf, ms as u64);
1370                }
1371            };
1372        }
1373
1374        match field {
1375            CtxLogField::ConnectionId => {
1376                buf.extend(itoa::Buffer::new().format(self.conn.id).as_bytes());
1377            },
1378            CtxLogField::UpstreamReused => {
1379                if self.upstream.reused {
1380                    buf.extend(b"true");
1381                } else {
1382                    buf.extend(b"false");
1383                }
1384            },
1385            CtxLogField::UpstreamStatus => {
1386                if let Some(status) = &self.upstream.status {
1387                    buf.extend_from_slice(status.as_str().as_bytes());
1388                } else {
1389                    buf.extend_from_slice(b"-");
1390                }
1391            },
1392            CtxLogField::UpstreamAddr => {
1393                buf.extend(self.upstream.address.as_bytes())
1394            },
1395            CtxLogField::UpstreamName => {
1396                buf.extend(self.upstream.name.as_bytes())
1397            },
1398            CtxLogField::Processing => buf.extend(
1399                itoa::Buffer::new()
1400                    .format(self.state.processing_count)
1401                    .as_bytes(),
1402            ),
1403            CtxLogField::UpstreamConnected => {
1404                if let Some(value) = self.upstream.connected_count {
1405                    buf.extend(itoa::Buffer::new().format(value).as_bytes());
1406                }
1407            },
1408
1409            // Timing fields
1410            CtxLogField::UpstreamConnectTime => {
1411                append_time!(self.get_upstream_connect_time())
1412            },
1413            CtxLogField::UpstreamConnectTimeHuman => {
1414                append_time!(self.get_upstream_connect_time(), human)
1415            },
1416
1417            CtxLogField::UpstreamProcessingTime => {
1418                append_time!(self.get_upstream_processing_time())
1419            },
1420            CtxLogField::UpstreamProcessingTimeHuman => {
1421                append_time!(self.get_upstream_processing_time(), human)
1422            },
1423            CtxLogField::UpstreamResponseTime => {
1424                append_time!(self.get_upstream_response_time())
1425            },
1426            CtxLogField::UpstreamResponseTimeHuman => {
1427                append_time!(self.get_upstream_response_time(), human)
1428            },
1429            CtxLogField::UpstreamFirstBodyTime => {
1430                append_time!(self.get_upstream_first_body_time())
1431            },
1432            CtxLogField::UpstreamFirstBodyTimeHuman => {
1433                append_time!(self.get_upstream_first_body_time(), human)
1434            },
1435            CtxLogField::UpstreamTcpConnectTime => {
1436                append_time!(self.timing.upstream_tcp_connect)
1437            },
1438            CtxLogField::UpstreamTcpConnectTimeHuman => {
1439                append_time!(self.timing.upstream_tcp_connect, human)
1440            },
1441            CtxLogField::UpstreamTlsHandshakeTime => {
1442                append_time!(self.timing.upstream_tls_handshake)
1443            },
1444            CtxLogField::UpstreamTlsHandshakeTimeHuman => {
1445                append_time!(self.timing.upstream_tls_handshake, human)
1446            },
1447            CtxLogField::UpstreamConnectOffloadWaitTime => {
1448                append_time!(self.timing.upstream_connect_offload_wait)
1449            },
1450            CtxLogField::UpstreamConnectOffloadWaitTimeHuman => {
1451                append_time!(self.timing.upstream_connect_offload_wait, human)
1452            },
1453            CtxLogField::UpstreamConnectionTime => {
1454                append_time!(self.timing.upstream_connection_duration)
1455            },
1456            CtxLogField::UpstreamConnectionTimeHuman => {
1457                append_time!(self.timing.upstream_connection_duration, human)
1458            },
1459            CtxLogField::ConnectionTime => {
1460                append_time!(Some(self.timing.connection_duration))
1461            },
1462            CtxLogField::ConnectionTimeHuman => {
1463                append_time!(Some(self.timing.connection_duration), human)
1464            },
1465
1466            // Other fields
1467            CtxLogField::Location => {
1468                if !self.upstream.location.is_empty() {
1469                    buf.extend(self.upstream.location.as_bytes())
1470                }
1471            },
1472            CtxLogField::ConnectionReused => {
1473                if self.conn.reused {
1474                    buf.extend(b"true");
1475                } else {
1476                    buf.extend(b"false");
1477                }
1478            },
1479            CtxLogField::TlsVersion => {
1480                if let Some(value) = &self.conn.tls_version {
1481                    buf.extend(value.as_bytes());
1482                }
1483            },
1484            CtxLogField::TlsCipher => {
1485                if let Some(value) = &self.conn.tls_cipher {
1486                    buf.extend(value.as_bytes());
1487                }
1488            },
1489            CtxLogField::TlsClientSubject => {
1490                if let Some(cert) = &self.conn.tls_client_cert {
1491                    buf.extend(cert.subject.as_bytes());
1492                }
1493            },
1494            CtxLogField::TlsClientFingerprint => {
1495                if let Some(cert) = &self.conn.tls_client_cert {
1496                    buf.extend(cert.fingerprint.as_bytes());
1497                }
1498            },
1499            CtxLogField::TlsClientSerial => {
1500                if let Some(cert) = &self.conn.tls_client_cert {
1501                    buf.extend(cert.serial.as_bytes());
1502                }
1503            },
1504            CtxLogField::TlsClientVerified => {
1505                if self.conn.tls_client_cert.is_some() {
1506                    buf.extend(b"true");
1507                } else {
1508                    buf.extend(b"false");
1509                }
1510            },
1511            CtxLogField::Ja4 => {
1512                if let Some(fingerprint) = &self.conn.ja4 {
1513                    buf.extend(fingerprint.ja4().as_bytes());
1514                }
1515            },
1516            CtxLogField::Ja4R => {
1517                if let Some(fingerprint) = &self.conn.ja4 {
1518                    buf.extend(fingerprint.ja4_r().as_bytes());
1519                }
1520            },
1521            CtxLogField::Ja4O => {
1522                if let Some(fingerprint) = &self.conn.ja4 {
1523                    buf.extend(fingerprint.ja4_o().as_bytes());
1524                }
1525            },
1526            CtxLogField::Ja4Ro => {
1527                if let Some(fingerprint) = &self.conn.ja4 {
1528                    buf.extend(fingerprint.ja4_ro().as_bytes());
1529                }
1530            },
1531            CtxLogField::TlsHandshakeTime => {
1532                append_time!(self.timing.tls_handshake)
1533            },
1534            CtxLogField::TlsHandshakeTimeHuman => {
1535                append_time!(self.timing.tls_handshake, human)
1536            },
1537            CtxLogField::CompressionTime => {
1538                if let Some(feature) = &self.features
1539                    && let Some(value) = &feature.compression_stat
1540                {
1541                    append_time!(Some(value.duration.as_millis() as u64))
1542                }
1543            },
1544            CtxLogField::CompressionTimeHuman => {
1545                if let Some(feature) = &self.features
1546                    && let Some(value) = &feature.compression_stat
1547                {
1548                    append_time!(Some(value.duration.as_millis() as u64), human)
1549                }
1550            },
1551            CtxLogField::CompressionRatio => {
1552                if let Some(feature) = &self.features
1553                    && let Some(value) = &feature.compression_stat
1554                {
1555                    // One decimal place without allocating via `format!`.
1556                    let tenths = (value.ratio() * 10.0).round() as u64;
1557                    buf.extend(
1558                        itoa::Buffer::new().format(tenths / 10).as_bytes(),
1559                    );
1560                    buf.extend_from_slice(b".");
1561                    buf.extend(
1562                        itoa::Buffer::new().format(tenths % 10).as_bytes(),
1563                    );
1564                }
1565            },
1566            CtxLogField::CacheLookupTime => {
1567                append_time!(self.timing.cache_lookup)
1568            },
1569            CtxLogField::CacheLookupTimeHuman => {
1570                append_time!(self.timing.cache_lookup, human)
1571            },
1572            CtxLogField::CacheLockTime => {
1573                append_time!(self.timing.cache_lock)
1574            },
1575            CtxLogField::CacheLockTimeHuman => {
1576                append_time!(self.timing.cache_lock, human)
1577            },
1578            CtxLogField::ServiceTime => {
1579                append_time!(Some(self.timing.created_at.elapsed().as_millis()))
1580            },
1581            CtxLogField::ServiceTimeHuman => {
1582                append_time!(
1583                    Some(self.timing.created_at.elapsed().as_millis()),
1584                    human
1585                )
1586            },
1587        }
1588    }
1589
1590    /// Generates a Server-Timing header value based on the context's timing metrics.
1591    ///
1592    /// The Server-Timing header allows servers to communicate performance metrics
1593    /// about the request-response cycle to the client. This implementation includes
1594    /// various timing metrics like connection time, processing time, and cache operations.
1595    ///
1596    /// Returns a String containing the formatted Server-Timing header value.
1597    pub fn generate_server_timing(&self) -> String {
1598        let mut timing_str = String::with_capacity(200);
1599        // Flag to track if this is the first timing entry, to handle commas correctly.
1600        let mut first = true;
1601
1602        // Macro to add a timing entry to the string.
1603        macro_rules! add_timing {
1604            ($name:expr, $dur:expr) => {
1605                if !first {
1606                    timing_str.push_str(", ");
1607                }
1608                // Ignore the write! result as it's unlikely to fail with a String.
1609                let _ = write!(&mut timing_str, "{};dur={}", $name, $dur);
1610                first = false;
1611            };
1612        }
1613
1614        // Aggregate and add upstream timings.
1615        let mut upstream_time = 0;
1616        if let Some(time) = self.get_upstream_connect_time() {
1617            upstream_time += time;
1618            add_timing!("upstream.connect", time);
1619        }
1620        if let Some(time) = self.get_upstream_processing_time() {
1621            upstream_time += time;
1622            add_timing!("upstream.processing", time);
1623        }
1624        if upstream_time > 0 {
1625            add_timing!("upstream", upstream_time);
1626        }
1627
1628        // Aggregate and add cache timings.
1629        let mut cache_time = 0;
1630        if let Some(time) = self.timing.cache_lookup {
1631            cache_time += time;
1632            add_timing!("cache.lookup", time);
1633        }
1634        if let Some(time) = self.timing.cache_lock {
1635            cache_time += time;
1636            add_timing!("cache.lock", time);
1637        }
1638        if cache_time > 0 {
1639            add_timing!("cache", cache_time);
1640        }
1641
1642        // Aggregate and add plugin timings.
1643        if let Some(features) = &self.features
1644            && let Some(times) = &features.plugin_processing_times
1645        {
1646            let mut plugin_time: u32 = 0;
1647            for (name, time) in times {
1648                if *time == 0 {
1649                    continue;
1650                }
1651                plugin_time += time;
1652                // Write directly into the shared buffer — avoid a per-plugin
1653                // temporary `"plugin." + name` String.
1654                if !first {
1655                    timing_str.push_str(", ");
1656                }
1657                let _ = write!(&mut timing_str, "plugin.{name};dur={time}");
1658                first = false;
1659            }
1660            if plugin_time > 0 {
1661                add_timing!("plugin", plugin_time);
1662            }
1663        }
1664
1665        // Add the total service time, which is always present.
1666        let service_time = self.timing.created_at.elapsed().as_millis();
1667        // Add a separator if other timings were already added.
1668        if !first {
1669            timing_str.push_str(", ");
1670        }
1671        // Write the final timing directly.
1672        let _ = write!(&mut timing_str, "total;dur={}", service_time);
1673
1674        timing_str
1675    }
1676
1677    /// Pushes a single cache key component to the context.
1678    #[inline]
1679    pub fn push_cache_key(&mut self, key: String) {
1680        let cache_info = self.cache.get_or_insert_default();
1681        cache_info
1682            .keys
1683            .get_or_insert_with(|| Vec::with_capacity(2))
1684            .push(key);
1685    }
1686
1687    /// Extends the cache key components with a vector of keys.
1688    #[inline]
1689    pub fn extend_cache_keys(&mut self, keys: Vec<String>) {
1690        let cache_info = self.cache.get_or_insert_default();
1691        cache_info
1692            .keys
1693            .get_or_insert_with(|| Vec::with_capacity(keys.len() + 2))
1694            .extend(keys);
1695    }
1696    /// Adds `current` to the cache key as the one of `alternatives` this
1697    /// request stands for. It may be empty: the place is noted all the
1698    /// same, for the request that has to know what else could be there.
1699    #[inline]
1700    pub fn push_cache_key_variant(
1701        &mut self,
1702        current: Vec<String>,
1703        alternatives: Arc<Vec<Vec<String>>>,
1704    ) {
1705        let cache_info = self.cache.get_or_insert_default();
1706        let keys = cache_info
1707            .keys
1708            .get_or_insert_with(|| Vec::with_capacity(current.len() + 2));
1709        cache_info
1710            .key_variants
1711            .get_or_insert_default()
1712            .push(CacheKeyVariant {
1713                at: keys.len(),
1714                len: current.len(),
1715                alternatives,
1716            });
1717        keys.extend(current);
1718    }
1719
1720    /// Every list of key components a request for this url can have: the
1721    /// one of this request, with each [`CacheKeyVariant`] in it replaced by
1722    /// each of its alternatives. The list of this request itself when it
1723    /// has no variants, or when there would be too many lists to go
1724    /// through.
1725    pub fn cache_key_alternatives(&self) -> Vec<Vec<String>> {
1726        /// More than any sensible set of plugins gives: the codings times
1727        /// the selections of four image formats is 64.
1728        const MAX_ALTERNATIVES: usize = 1024;
1729        let Some(cache_info) = &self.cache else {
1730            return vec![vec![]];
1731        };
1732        let keys = cache_info.keys.as_deref().unwrap_or_default();
1733        let variants = cache_info.key_variants.as_deref().unwrap_or_default();
1734        let count = variants.iter().try_fold(1usize, |count, variant| {
1735            count.checked_mul(variant.alternatives.len().max(1))
1736        });
1737        if variants.is_empty() {
1738            return vec![keys.to_vec()];
1739        }
1740        if count.is_none_or(|n| n > MAX_ALTERNATIVES) {
1741            // Said, since whoever asked goes on with less than all of them.
1742            tracing::warn!(
1743                target: "pingap::core",
1744                variants = variants.len(),
1745                "too many cache key alternatives, only the key of this request is used"
1746            );
1747            return vec![keys.to_vec()];
1748        }
1749        let mut lists = vec![Vec::with_capacity(keys.len())];
1750        let mut cursor = 0;
1751        for variant in variants {
1752            // What other plugins put in between is the same in all of them.
1753            let fixed = keys.get(cursor..variant.at).unwrap_or_default();
1754            lists = lists
1755                .into_iter()
1756                .flat_map(|list: Vec<String>| {
1757                    variant.alternatives.iter().map(move |alternative| {
1758                        let mut list = list.clone();
1759                        list.extend_from_slice(fixed);
1760                        list.extend_from_slice(alternative);
1761                        list
1762                    })
1763                })
1764                .collect();
1765            cursor = variant.at + variant.len;
1766        }
1767        let rest = keys.get(cursor..).unwrap_or_default();
1768        for list in lists.iter_mut() {
1769            list.extend_from_slice(rest);
1770        }
1771        lists
1772    }
1773
1774    /// Updates the upstream timing from the digest.
1775    #[inline]
1776    pub fn update_upstream_timing_from_digest(
1777        &mut self,
1778        digest: &Digest,
1779        reused: bool,
1780    ) {
1781        let detail = get_digest_detail(digest);
1782        self.timing.upstream_connection_duration = Some(detail.connection_time);
1783        if reused {
1784            return;
1785        }
1786
1787        // pingora times each layer on a monotonic clock: the TCP connect on
1788        // the transport entry, the handshake on the TLS entry. Prefer those.
1789        if let Some(tcp_connect) = detail.tcp_connect {
1790            self.timing.upstream_tcp_connect = Some(tcp_connect as i32);
1791            self.timing.upstream_tls_handshake =
1792                detail.tls_handshake.map(|value| value as i32);
1793            self.timing.upstream_connect_offload_wait =
1794                detail.connect_offload_wait.map(|value| value as i32);
1795            return;
1796        }
1797
1798        // Fallback for a stream that carries no measurement: split pingap's
1799        // own end-to-end connect timer by the wall-clock gap between the
1800        // layers' timestamps. Coarser, and the TCP share also absorbs
1801        // whatever the connector did around the connect.
1802        let upstream_connect_time =
1803            self.timing.upstream_connect.unwrap_or_default();
1804        let mut upstream_tcp_connect = upstream_connect_time;
1805        if detail.tls_established > detail.tcp_established {
1806            let latency =
1807                (detail.tls_established - detail.tcp_established) as i32;
1808            upstream_tcp_connect -= latency;
1809            self.timing.upstream_tls_handshake = Some(latency);
1810        }
1811        if upstream_tcp_connect > 0 {
1812            self.timing.upstream_tcp_connect = Some(upstream_tcp_connect);
1813        }
1814    }
1815}
1816
1817/// Writes the host part of a cache key.
1818///
1819/// In lower case: a host name is case-insensitive, and one spelling keeps
1820/// `Example.com` from caching beside `example.com`. Anything that is not a
1821/// host name character is written as `%XX`. A `Host` header is whatever the
1822/// client sent, and written as it came `a.com/static` followed by the path
1823/// `/app.js` made the key of `a.com` and `/static/app.js`: one request
1824/// could store its response under the key of another url.
1825fn push_key_host(key: &mut String, host: &str) {
1826    for byte in host.bytes() {
1827        match byte {
1828            b'a'..=b'z'
1829            | b'0'..=b'9'
1830            | b'.'
1831            | b'-'
1832            | b'_'
1833            | b':'
1834            | b'['
1835            | b']' => key.push(byte as char),
1836            b'A'..=b'Z' => key.push(byte.to_ascii_lowercase() as char),
1837            _ => {
1838                let _ = write!(key, "%{byte:02X}");
1839            },
1840        }
1841    }
1842}
1843
1844/// Generates the cache key of a request.
1845///
1846/// The primary is, in this order: the namespace, the custom keys (each
1847/// followed by `:`), the method, `:`, the host in lower case without its
1848/// port, then the path and the query. `user_tag` is the namespace.
1849///
1850/// The host is part of the key whatever the protocol. It used to come from
1851/// the request uri alone, which carries it in HTTP/2 but not in HTTP/1.1:
1852/// two domains behind one cache plugin answered with each other's pages
1853/// over HTTP/1.1, and a `PURGE` sent over one protocol missed what the other
1854/// had stored. Scheme and port are left out on purpose, so a `PURGE` sent
1855/// to an internal plain-http listener with the right `Host` clears what the
1856/// public https listener cached.
1857///
1858/// # Arguments
1859/// * `ctx` - The Ctx context containing cache configuration.
1860/// * `method` - The HTTP method as a string.
1861/// * `header` - The request the key is for: its host, path and query.
1862pub fn get_cache_key(
1863    ctx: &Ctx,
1864    method: &str,
1865    header: &RequestHeader,
1866) -> CacheKey {
1867    let Some(cache_info) = &ctx.cache else {
1868        // Return an empty key if cache is not configured for this context.
1869        return CacheKey::new("", "");
1870    };
1871    let namespace = cache_info.namespace.as_ref().map_or("", |v| v);
1872    // As it was sent, see `get_request_host`.
1873    let host = get_request_host(header).unwrap_or_default();
1874    let path = header.uri.path();
1875    // By the rule of the plugin where it has one: the query that was
1876    // settled when the key of the request was made, or, for a key that is
1877    // made of another request (a purge), the rule applied to that one.
1878    let made;
1879    let query = match (&cache_info.key_query, &cache_info.query_rule) {
1880        (Some(query), _) => (!query.is_empty()).then_some(query.as_str()),
1881        (None, Some(rule)) => {
1882            made = rule.key_query(header.uri.query().unwrap_or_default());
1883            (!made.is_empty()).then_some(made.as_str())
1884        },
1885        (None, None) => header.uri.query(),
1886    };
1887    // pingora's CacheKey used to take the namespace as its own argument and
1888    // hashed `namespace ++ primary` as one unframed byte string. That argument
1889    // is gone, so the namespace is written straight in front of the primary
1890    // here. The storage layer still partitions by namespace, so it also
1891    // travels in `user_tag`, which is carried alongside the key but never
1892    // hashed.
1893    let header_keys = cache_info.header_key_components(header);
1894    let keys_len = cache_info
1895        .keys
1896        .iter()
1897        .flatten()
1898        .chain(&header_keys)
1899        .map(|s| s.len() + 1)
1900        .sum::<usize>();
1901    let mut key_buf = String::with_capacity(
1902        namespace.len()
1903            + keys_len
1904            + method.len()
1905            + 1
1906            + host.len()
1907            + path.len()
1908            + query.map_or(0, |query| query.len() + 1),
1909    );
1910    key_buf.push_str(namespace);
1911    // Custom key components first, each followed by ':'.
1912    for k in cache_info.keys.iter().flatten().chain(&header_keys) {
1913        key_buf.push_str(k);
1914        key_buf.push(':');
1915    }
1916    // Then "METHOD:host/path?query".
1917    key_buf.push_str(method);
1918    key_buf.push(':');
1919    push_key_host(&mut key_buf, host);
1920    key_buf.push_str(path);
1921    if let Some(query) = query {
1922        key_buf.push('?');
1923        key_buf.push_str(query);
1924    }
1925
1926    CacheKey::new(key_buf, namespace)
1927}
1928
1929#[cfg(test)]
1930mod tests {
1931    use super::*;
1932    use bytes::Bytes;
1933    use bytes::BytesMut;
1934    use http::Uri;
1935    use pingora::cache::key::CacheHashKey;
1936    use pingora::protocols::tls::SslDigest;
1937    use pingora::protocols::tls::SslDigestExtension;
1938    use pretty_assertions::assert_eq;
1939    use std::{sync::Arc, time::Duration};
1940
1941    /// A body is held to its rate by what has gone out and how long that
1942    /// took, once the part that is free of it is past.
1943    #[test]
1944    fn test_body_pace() {
1945        let start = Instant::now();
1946        let at = |ms: u64| start + Duration::from_millis(ms);
1947        let ms = |delay: Option<Duration>| delay.map(|d| d.as_millis());
1948
1949        // 1000 bytes a second, from the first byte.
1950        let mut pace = BodyPace::new(1000, 0);
1951        assert_eq!(Some(500), ms(pace.delay_at(500, at(0))));
1952        // On time after the wait: only the new chunk is owed.
1953        assert_eq!(Some(500), ms(pace.delay_at(500, at(500))));
1954        // A client that took its time has used it up already.
1955        assert_eq!(None, ms(pace.delay_at(1000, at(5000))));
1956        // And is not let run ahead by that afterwards: three thousand
1957        // more bytes are three more seconds.
1958        assert_eq!(Some(3000), ms(pace.delay_at(3000, at(5000))));
1959
1960        // The first 2000 bytes as fast as they go.
1961        let mut pace = BodyPace::new(1000, 2000);
1962        assert_eq!(None, ms(pace.delay_at(1500, at(0))));
1963        // 500 of these are free, 500 are not.
1964        assert_eq!(Some(500), ms(pace.delay_at(1000, at(10))));
1965        assert_eq!(Some(1000), ms(pace.delay_at(1000, at(510))));
1966
1967        // No rate at all is the slowest there is, not a division by zero.
1968        let mut pace = BodyPace::new(0, 0);
1969        assert_eq!(Some(2000), ms(pace.delay_at(2, at(0))));
1970        // An empty chunk asks for nothing.
1971        let mut pace = BodyPace::new(1000, 0);
1972        assert_eq!(None, ms(pace.delay_at(0, at(0))));
1973
1974        // Asked before a chunk: the wait is for what went before it.
1975        let mut pace = BodyPace::new(1000, 0);
1976        // nothing went before the first, however large it is
1977        assert_eq!(None, ms(pace.delay_before_at(2000, at(0))));
1978        assert_eq!(Some(2000), ms(pace.delay_before_at(500, at(0))));
1979        // half a second later the 500 are paid for
1980        assert_eq!(Some(500), ms(pace.delay_before_at(500, at(2000))));
1981        assert_eq!(None, ms(pace.delay_before_at(500, at(3000))));
1982        // a client that took its time owes nothing, and is not let run
1983        // ahead afterwards
1984        assert_eq!(None, ms(pace.delay_before_at(1000, at(9000))));
1985        assert_eq!(Some(1000), ms(pace.delay_before_at(1, at(9000))));
1986        // with the first bytes free
1987        let mut pace = BodyPace::new(1000, 2000);
1988        assert_eq!(None, ms(pace.delay_before_at(1500, at(0))));
1989        assert_eq!(None, ms(pace.delay_before_at(1000, at(0))));
1990        assert_eq!(Some(500), ms(pace.delay_before_at(1000, at(0))));
1991    }
1992
1993    /// A `PURGE` names a url and no coding or image format: it has to
1994    /// find the key of every request that named one.
1995    #[test]
1996    fn test_ask_with_key_query() {
1997        let ask = |uri: &str, key_query: Option<&str>| {
1998            let mut header = RequestHeader::build("GET", b"/", None).unwrap();
1999            header.set_uri(uri.parse().unwrap());
2000            let info = CacheInfo {
2001                key_query: key_query.map(str::to_string),
2002                ..Default::default()
2003            };
2004            assert_eq!(true, info.ask_with_key_query(&mut header));
2005            header.uri.to_string()
2006        };
2007        // no rule for the query: the request is sent as it came
2008        assert_eq!("/a?b=2&a=1", ask("/a?b=2&a=1", None));
2009        // the query of the key, whatever else the client sent
2010        assert_eq!(
2011            "/a?a=1&b=2",
2012            ask("/a?b=2&utm_source=x&a=1", Some("a=1&b=2"))
2013        );
2014        // spellings the rule does not know do not reach the upstream
2015        assert_eq!("/list", ask("/list?p%61ge=2", Some("")));
2016        assert_eq!("/list", ask("/list?x=1;page=2", Some("")));
2017        assert_eq!(
2018            "/list?size=10",
2019            ask("/list?Page=2&size=10", Some("size=10"))
2020        );
2021        // nothing to change
2022        assert_eq!("/a?a=1", ask("/a?a=1", Some("a=1")));
2023        assert_eq!("/a", ask("/a", Some("")));
2024        // an empty query is no query, as in the key
2025        assert_eq!("/a", ask("/a?", Some("")));
2026
2027        // The query is settled when the key is made, from the request as
2028        // it is by then; until then the rule is what is applied.
2029        let rule =
2030            Arc::new(CacheQueryRule::Ignore(vec!["utm_source".to_string()]));
2031        let mut info = CacheInfo {
2032            query_rule: Some(rule),
2033            ..Default::default()
2034        };
2035        let mut header = RequestHeader::build("GET", b"/", None).unwrap();
2036        header.set_uri("/a?b=2&utm_source=x&a=1".parse().unwrap());
2037        let mut upstream = header.clone();
2038        assert_eq!(true, info.ask_with_key_query(&mut upstream));
2039        assert_eq!("/a?a=1&b=2", upstream.uri.to_string());
2040        // a plugin after the cache took a parameter out
2041        header.set_uri("/a?utm_source=x&a=1".parse().unwrap());
2042        info.settle_key_query(&header);
2043        assert_eq!(Some("a=1".to_string()), info.key_query);
2044        let mut ctx = Ctx {
2045            cache: Some(Box::new(info)),
2046            ..Default::default()
2047        };
2048        assert_eq!(
2049            Some("GET:/a?a=1"),
2050            get_cache_key(&ctx, "GET", &header).primary_key_str()
2051        );
2052        // and a key that is made of another request goes by the rule
2053        if let Some(info) = ctx.cache.as_mut() {
2054            info.key_query = None;
2055        }
2056        header.set_uri("/a?z=9&utm_source=x&b=1".parse().unwrap());
2057        assert_eq!(
2058            Some("GET:/a?b=1&z=9"),
2059            get_cache_key(&ctx, "GET", &header).primary_key_str()
2060        );
2061        // the rest of the uri is kept (HTTP/2 carries the authority)
2062        assert_eq!(
2063            "https://example.com/a?a=1",
2064            ask("https://example.com/a?z=9&a=1", Some("a=1"))
2065        );
2066    }
2067
2068    #[test]
2069    fn test_cache_key_alternatives() {
2070        let list = |items: &[&str]| {
2071            items
2072                .iter()
2073                .map(|item| item.to_string())
2074                .collect::<Vec<_>>()
2075        };
2076        let mut ctx = Ctx::default();
2077        assert_eq!(vec![list(&[])], ctx.cache_key_alternatives());
2078
2079        ctx.push_cache_key("fixed".to_string());
2080        assert_eq!(vec![list(&["fixed"])], ctx.cache_key_alternatives());
2081
2082        ctx.push_cache_key_variant(
2083            list(&["gzip"]),
2084            Arc::new(vec![list(&[]), list(&["gzip"]), list(&["br"])]),
2085        );
2086        ctx.push_cache_key("lang".to_string());
2087        // Nothing of this one in the key of this request.
2088        ctx.push_cache_key_variant(
2089            list(&[]),
2090            Arc::new(vec![list(&[]), list(&["avif", "webp"])]),
2091        );
2092        assert_eq!(
2093            Some(list(&["fixed", "gzip", "lang"])),
2094            ctx.cache.as_ref().unwrap().keys
2095        );
2096        assert_eq!(
2097            vec![
2098                list(&["fixed", "lang"]),
2099                list(&["fixed", "lang", "avif", "webp"]),
2100                list(&["fixed", "gzip", "lang"]),
2101                list(&["fixed", "gzip", "lang", "avif", "webp"]),
2102                list(&["fixed", "br", "lang"]),
2103                list(&["fixed", "br", "lang", "avif", "webp"]),
2104            ],
2105            ctx.cache_key_alternatives()
2106        );
2107
2108        // Too many to go through: the key of the request itself.
2109        let many: Vec<_> =
2110            (0..40).map(|index| vec![index.to_string()]).collect();
2111        let mut ctx = Ctx::default();
2112        ctx.push_cache_key_variant(list(&["1"]), Arc::new(many.clone()));
2113        ctx.push_cache_key_variant(list(&["2"]), Arc::new(many));
2114        assert_eq!(vec![list(&["1", "2"])], ctx.cache_key_alternatives());
2115    }
2116
2117    #[test]
2118    fn test_ctx_new() {
2119        let ctx = Ctx::new();
2120        // Check that created_at is a recent timestamp.
2121        // It should be within the last 100ms.
2122        let elapsed_ms = ctx.timing.created_at.elapsed().as_millis();
2123        assert!(elapsed_ms < 100, "created_at should be a recent timestamp");
2124        // Check that other fields are correctly defaulted.
2125        assert!(ctx.cache.is_none());
2126        assert!(ctx.features.is_none());
2127        assert_eq!(ctx.conn.id, 0);
2128    }
2129
2130    /// Tests both adding and getting variables.
2131    /// The certificate of a client in the access log, and that there is
2132    /// nothing to print without one.
2133    #[test]
2134    fn test_tls_client_cert_log_fields() {
2135        let mut ctx = Ctx::new();
2136        let print = |ctx: &Ctx, key: &str| {
2137            let mut buf = BytesMut::new();
2138            ctx.append_log_value(&mut buf, key);
2139            String::from_utf8_lossy(&buf).to_string()
2140        };
2141        assert_eq!("", print(&ctx, "tls_client_subject"));
2142        assert_eq!("false", print(&ctx, "tls_client_verified"));
2143        ctx.conn.tls_client_cert = Some(Arc::new(TlsClientCert {
2144            subject: "O=Example, CN=device-42".to_string(),
2145            fingerprint: "b545db7a".to_string(),
2146            serial: "3429".to_string(),
2147        }));
2148        assert_eq!(
2149            "O=Example, CN=device-42",
2150            print(&ctx, "tls_client_subject")
2151        );
2152        assert_eq!("b545db7a", print(&ctx, "tls_client_fingerprint"));
2153        assert_eq!("3429", print(&ctx, "tls_client_serial"));
2154        assert_eq!("true", print(&ctx, "tls_client_verified"));
2155    }
2156
2157    #[test]
2158    fn test_plugin_values() {
2159        let mut ctx = Ctx::new();
2160        assert_eq!(None, ctx.get_plugin_value::<u16>("a"));
2161        ctx.set_plugin_value("a", Arc::new(200u16));
2162        ctx.set_plugin_value("b", "text".to_string());
2163        assert_eq!(
2164            Some(&200),
2165            ctx.get_plugin_value::<Arc<u16>>("a").map(|v| &**v)
2166        );
2167        assert_eq!(
2168            Some("text"),
2169            ctx.get_plugin_value::<String>("b").map(|v| v.as_str())
2170        );
2171        // Of another type: none.
2172        assert_eq!(None, ctx.get_plugin_value::<u16>("a"));
2173        // The last one kept is the one there.
2174        ctx.set_plugin_value("a", 404u16);
2175        assert_eq!(Some(&404), ctx.get_plugin_value::<u16>("a"));
2176    }
2177
2178    #[test]
2179    fn test_plugin_notes() {
2180        let mut ctx = Ctx::new();
2181        assert_eq!(None, ctx.get_plugin_note("a"));
2182        ctx.set_plugin_note("a", "gzip");
2183        ctx.set_plugin_note("b", "");
2184        assert_eq!(Some("gzip"), ctx.get_plugin_note("a"));
2185        assert_eq!(Some(""), ctx.get_plugin_note("b"));
2186        assert_eq!(None, ctx.get_plugin_note("c"));
2187        // The last one said is the one kept.
2188        ctx.set_plugin_note("a", "br");
2189        assert_eq!(Some("br"), ctx.get_plugin_note("a"));
2190        assert_eq!(
2191            2,
2192            ctx.features
2193                .as_ref()
2194                .unwrap()
2195                .plugin_notes
2196                .as_ref()
2197                .unwrap()
2198                .len()
2199        );
2200    }
2201
2202    #[test]
2203    fn test_add_and_get_variable() {
2204        let mut ctx = Ctx::new();
2205        assert!(
2206            ctx.get_variable("key1").is_none(),
2207            "Should be None before adding"
2208        );
2209
2210        ctx.add_variable("key1", "value1");
2211        ctx.add_variable("key2", "value2");
2212
2213        assert_eq!(ctx.get_variable("key1"), Some("value1"));
2214        assert_eq!(ctx.get_variable("key2"), Some("value2"));
2215        assert_eq!(ctx.get_variable("nonexistent"), None);
2216    }
2217
2218    /// Tests the helper functions for getting filtered time values.
2219    #[test]
2220    fn test_get_time_field() {
2221        let mut ctx = Ctx::new();
2222
2223        // Test with a valid time
2224        ctx.timing.upstream_response = Some(100);
2225        assert_eq!(ctx.get_upstream_response_time(), Some(100));
2226
2227        // Test with a time is negative
2228        ctx.timing.upstream_response = Some(-1);
2229        assert_eq!(
2230            ctx.get_upstream_response_time(),
2231            None,
2232            "Time exceeding one hour should be None"
2233        );
2234
2235        // Test with None
2236        ctx.timing.upstream_response = None;
2237        assert_eq!(ctx.get_upstream_response_time(), None);
2238    }
2239
2240    /// Every log field name round-trips through the enum, and the two
2241    /// entry points agree.
2242    #[test]
2243    fn test_ctx_log_field_names() {
2244        for (name, field) in [
2245            ("connection_id", CtxLogField::ConnectionId),
2246            (
2247                "upstream_tcp_connect_time_human",
2248                CtxLogField::UpstreamTcpConnectTimeHuman,
2249            ),
2250            (
2251                "upstream_connect_offload_wait_time",
2252                CtxLogField::UpstreamConnectOffloadWaitTime,
2253            ),
2254            ("tls_version", CtxLogField::TlsVersion),
2255            ("tls_client_subject", CtxLogField::TlsClientSubject),
2256            ("tls_client_fingerprint", CtxLogField::TlsClientFingerprint),
2257            ("tls_client_serial", CtxLogField::TlsClientSerial),
2258            ("tls_client_verified", CtxLogField::TlsClientVerified),
2259            ("ja4", CtxLogField::Ja4),
2260            ("ja4_r", CtxLogField::Ja4R),
2261            ("ja4_o", CtxLogField::Ja4O),
2262            ("ja4_ro", CtxLogField::Ja4Ro),
2263            ("compression_ratio", CtxLogField::CompressionRatio),
2264            ("service_time_human", CtxLogField::ServiceTimeHuman),
2265        ] {
2266            assert_eq!(Ok(field), name.parse::<CtxLogField>(), "{name}");
2267            assert_eq!(name, field.to_string());
2268        }
2269        assert_eq!(true, "unknown_key".parse::<CtxLogField>().is_err());
2270
2271        let mut ctx = Ctx::new();
2272        ctx.conn.id = 7;
2273        let mut by_name = BytesMut::new();
2274        ctx.append_log_value(&mut by_name, "connection_id");
2275        let mut by_field = BytesMut::new();
2276        ctx.append_log_field(&mut by_field, CtxLogField::ConnectionId);
2277        assert_eq!(b"7", by_name.as_ref());
2278        assert_eq!(by_name, by_field);
2279    }
2280
2281    /// The JA4 fields print the connection's fingerprint in each form,
2282    /// and nothing when there is none.
2283    #[test]
2284    fn test_ja4_log_fields() {
2285        let mut ctx = Ctx::new();
2286        let value = |ctx: &Ctx, key: &str| {
2287            let mut buf = BytesMut::new();
2288            ctx.append_log_value(&mut buf, key);
2289            String::from_utf8(buf.to_vec()).unwrap()
2290        };
2291        assert_eq!("", value(&ctx, "ja4"));
2292        let fingerprint = Ja4Fingerprint::from_client_hello(
2293            &crate::ja4::testing::spec_example_body(),
2294        )
2295        .unwrap();
2296        ctx.conn.ja4 = Some(Arc::new(fingerprint.clone()));
2297        assert_eq!("t13d1516h2_8daaf6152771_e5627efa2ab1", value(&ctx, "ja4"));
2298        assert_eq!(fingerprint.ja4_r(), value(&ctx, "ja4_r"));
2299        assert_eq!(fingerprint.ja4_o(), value(&ctx, "ja4_o"));
2300        assert_eq!(fingerprint.ja4_ro(), value(&ctx, "ja4_ro"));
2301    }
2302
2303    /// Tests the `append_log_value` function with a wider range of keys and edge cases.
2304    #[test]
2305    fn test_append_log_value_coverage() {
2306        let mut ctx = Ctx::new();
2307        // Test an unknown key, should do nothing.
2308        let mut buf = BytesMut::new();
2309        ctx.append_log_value(&mut buf, "unknown_key");
2310        assert!(buf.is_empty(), "Unknown key should not append anything");
2311
2312        // Test boolean values
2313        buf = BytesMut::new();
2314        ctx.conn.reused = true;
2315        ctx.append_log_value(&mut buf, "connection_reused");
2316        assert_eq!(&buf[..], b"true");
2317
2318        // Test optional string values
2319        ctx.conn.tls_version = Some("TLSv1.3".into());
2320        buf = BytesMut::new();
2321        ctx.append_log_value(&mut buf, "tls_version");
2322        assert_eq!(&buf[..], b"TLSv1.3");
2323
2324        // Test service_time calculation
2325        std::thread::sleep(Duration::from_millis(11));
2326        buf = BytesMut::new();
2327        ctx.append_log_value(&mut buf, "service_time");
2328        let service_time: u64 =
2329            std::str::from_utf8(&buf[..]).unwrap().parse().unwrap();
2330        assert!(service_time >= 10, "Service time should be at least 10ms");
2331    }
2332
2333    /// Tests the `get_cache_key` function's logic more thoroughly.
2334    #[test]
2335    fn test_get_cache_key() {
2336        let method = "GET";
2337        // HTTP/2: the host is in the uri.
2338        let mut h2 = RequestHeader::build("GET", b"/", None).unwrap();
2339        h2.set_uri(Uri::from_static("https://example.com/path?a=1"));
2340        // HTTP/1.1: the uri is the path, the host is a header.
2341        let h1 = |host: &str, path: &str| {
2342            let mut header =
2343                RequestHeader::build("GET", path.as_bytes(), None).unwrap();
2344            header.insert_header("Host", host).unwrap();
2345            header
2346        };
2347
2348        // Case 1: No cache info in context.
2349        let ctx_no_cache = Ctx::new();
2350        let key1 = get_cache_key(&ctx_no_cache, method, &h2);
2351        assert_eq!(key1.user_tag, "");
2352        assert_eq!(key1.primary_key_str(), Some(""));
2353
2354        // Case 2: Cache info with namespace but no keys.
2355        let mut ctx_with_ns = Ctx::new();
2356        ctx_with_ns.cache = Some(Box::new(CacheInfo {
2357            namespace: Some("my-ns".to_string()),
2358            ..Default::default()
2359        }));
2360        let key2 = get_cache_key(&ctx_with_ns, method, &h2);
2361        assert_eq!(key2.user_tag, "my-ns");
2362        assert_eq!(
2363            key2.primary_key_str(),
2364            Some("my-nsGET:example.com/path?a=1")
2365        );
2366        // Changing what goes into the key makes every entry an older
2367        // pingap wrote to disk unreachable after the upgrade, so the hash
2368        // of a known key is pinned here.
2369        assert_eq!(key2.primary(), "8fe845e5498004cd81203be9074a8e68");
2370
2371        // The same request over HTTP/1.1 is the same entry, whatever the
2372        // spelling of the host and whichever port it came in on.
2373        for host in ["example.com", "Example.COM", "example.com:8443"] {
2374            let key =
2375                get_cache_key(&ctx_with_ns, method, &h1(host, "/path?a=1"));
2376            assert_eq!(key2.primary(), key.primary(), "{host}");
2377        }
2378        // Regression: another host is another entry. Over HTTP/1.1 the host
2379        // used to be missing from the key.
2380        let other =
2381            get_cache_key(&ctx_with_ns, method, &h1("other.com", "/path?a=1"));
2382        assert_eq!(
2383            other.primary_key_str(),
2384            Some("my-nsGET:other.com/path?a=1")
2385        );
2386        assert_ne!(key2.primary(), other.primary());
2387        // Regression: the host is the client's to write, and must not be
2388        // able to stand in for a piece of the path. These two used to have
2389        // one key.
2390        let odd = get_cache_key(
2391            &ctx_with_ns,
2392            method,
2393            &h1("example.com/static", "/app.js"),
2394        );
2395        let plain = get_cache_key(
2396            &ctx_with_ns,
2397            method,
2398            &h1("example.com", "/static/app.js"),
2399        );
2400        assert_eq!(
2401            odd.primary_key_str(),
2402            Some("my-nsGET:example.com%2Fstatic/app.js")
2403        );
2404        assert_ne!(odd.primary(), plain.primary());
2405        // An escape of its own does not get it there either.
2406        let escaped = get_cache_key(
2407            &ctx_with_ns,
2408            method,
2409            &h1("example.com%2Fstatic", "/app.js"),
2410        );
2411        assert_ne!(odd.primary(), escaped.primary());
2412
2413        // No host at all (HTTP/1.0).
2414        let bare = RequestHeader::build("GET", b"/path", None).unwrap();
2415        assert_eq!(
2416            get_cache_key(&ctx_with_ns, method, &bare).primary_key_str(),
2417            Some("my-nsGET:/path")
2418        );
2419
2420        // Case 3: Cache info with namespace and multiple keys.
2421        let mut ctx_with_keys = Ctx::new();
2422        ctx_with_keys.cache = Some(Box::new(CacheInfo {
2423            namespace: Some("my-ns".to_string()),
2424            keys: Some(vec!["user-123".to_string(), "desktop".to_string()]),
2425            ..Default::default()
2426        }));
2427        let key3 = get_cache_key(&ctx_with_keys, method, &h2);
2428        assert_eq!(key3.user_tag, "my-ns");
2429        assert_eq!(
2430            key3.primary_key_str(),
2431            Some("my-nsuser-123:desktop:GET:example.com/path?a=1")
2432        );
2433    }
2434
2435    /// The original `test_generate_server_timing` is good, but this version
2436    /// is slightly more robust to minor timing variations.
2437    #[test]
2438    fn test_generate_server_timing() {
2439        let mut ctx = Ctx::new();
2440        ctx.timing.upstream_connect = Some(1);
2441        ctx.timing.upstream_processing = Some(2);
2442        ctx.timing.cache_lookup = Some(6);
2443        ctx.timing.cache_lock = Some(7);
2444        ctx.add_plugin_processing_time(&Arc::from("plugin1"), 100);
2445
2446        let timing_header = ctx.generate_server_timing();
2447
2448        // Check for the presence of each expected component.
2449        assert!(timing_header.contains("upstream.connect;dur=1"));
2450        assert!(timing_header.contains("upstream.processing;dur=2"));
2451        assert!(timing_header.contains("upstream;dur=3"));
2452        assert!(timing_header.contains("cache.lookup;dur=6"));
2453        assert!(timing_header.contains("cache.lock;dur=7"));
2454        assert!(timing_header.contains("cache;dur=13"));
2455        assert!(timing_header.contains("plugin.plugin1;dur=100"));
2456        assert!(timing_header.contains("plugin;dur=100"));
2457        assert!(timing_header.contains("total;dur="));
2458    }
2459
2460    #[test]
2461    fn test_format_duration() {
2462        let mut buf = BytesMut::new();
2463        format_duration(&mut buf, (3600 + 3500) * 1000);
2464        assert_eq!(b"1.9h", buf.as_ref());
2465
2466        buf = BytesMut::new();
2467        format_duration(&mut buf, (3600 + 1800) * 1000);
2468        assert_eq!(b"1.5h", buf.as_ref());
2469
2470        buf = BytesMut::new();
2471        format_duration(&mut buf, (3600 + 100) * 1000);
2472        assert_eq!(b"1h", buf.as_ref());
2473
2474        buf = BytesMut::new();
2475        format_duration(&mut buf, (60 + 50) * 1000);
2476        assert_eq!(b"1.8m", buf.as_ref());
2477
2478        buf = BytesMut::new();
2479        format_duration(&mut buf, (60 + 2) * 1000);
2480        assert_eq!(b"1m", buf.as_ref());
2481
2482        buf = BytesMut::new();
2483        format_duration(&mut buf, 1000);
2484        assert_eq!(b"1s", buf.as_ref());
2485
2486        buf = BytesMut::new();
2487        format_duration(&mut buf, 512);
2488        assert_eq!(b"512ms", buf.as_ref());
2489
2490        buf = BytesMut::new();
2491        format_duration(&mut buf, 1112);
2492        assert_eq!(b"1.1s", buf.as_ref());
2493    }
2494
2495    #[test]
2496    fn test_add_variable() {
2497        let mut ctx = Ctx::new();
2498        ctx.add_variable("key1", "value1");
2499        ctx.add_variable("key2", "value2");
2500        ctx.extend_variables(AHashMap::from([
2501            ("key3".to_string(), "value3".to_string()),
2502            ("key4".to_string(), "value4".to_string()),
2503        ]));
2504        let variables =
2505            ctx.features.as_ref().unwrap().variables.as_ref().unwrap();
2506        // NOTE: The current implementation in the main code doesn't add the '$' prefix automatically.
2507        // The test should reflect the actual implementation.
2508        assert_eq!(variables.get("key1"), Some(&"value1".to_string()));
2509        assert_eq!(variables.get("key2"), Some(&"value2".to_string()));
2510        assert_eq!(variables.get("key3"), Some(&"value3".to_string()));
2511        assert_eq!(variables.get("key4"), Some(&"value4".to_string()));
2512    }
2513
2514    #[test]
2515    fn test_cache_key() {
2516        let mut ctx = Ctx::new();
2517        ctx.push_cache_key("key1".to_string());
2518        ctx.extend_cache_keys(vec!["key2".to_string(), "key3".to_string()]);
2519        assert_eq!(
2520            vec!["key1".to_string(), "key2".to_string(), "key3".to_string()],
2521            ctx.cache.unwrap().keys.unwrap()
2522        );
2523
2524        let mut ctx = Ctx::new();
2525        ctx.cache.get_or_insert_default();
2526        let mut header = RequestHeader::build("GET", b"/", None).unwrap();
2527        header.set_uri(Uri::from_static("https://example.com/path"));
2528        let key = get_cache_key(&ctx, "GET", &header);
2529        assert_eq!(key.user_tag, "");
2530        assert_eq!(key.primary_key_str(), Some("GET:example.com/path"));
2531    }
2532
2533    #[test]
2534    fn test_state() {
2535        let mut ctx = Ctx::new();
2536
2537        let mut buf = BytesMut::new();
2538        ctx.conn.id = 10;
2539        ctx.append_log_value(&mut buf, "connection_id");
2540        assert_eq!(b"10", buf.as_ref());
2541
2542        buf = BytesMut::new();
2543        ctx.append_log_value(&mut buf, "upstream_reused");
2544        assert_eq!(b"false", buf.as_ref());
2545
2546        buf = BytesMut::new();
2547        ctx.upstream.reused = true;
2548        ctx.append_log_value(&mut buf, "upstream_reused");
2549        assert_eq!(b"true", buf.as_ref());
2550
2551        buf = BytesMut::new();
2552        ctx.upstream.address = "192.168.1.1:80".to_string();
2553        ctx.append_log_value(&mut buf, "upstream_addr");
2554        assert_eq!(b"192.168.1.1:80", buf.as_ref());
2555
2556        buf = BytesMut::new();
2557        ctx.upstream.name = "standby".into();
2558        ctx.append_log_value(&mut buf, "upstream_name");
2559        assert_eq!(b"standby", buf.as_ref());
2560
2561        buf = BytesMut::new();
2562        ctx.upstream.status = Some(StatusCode::CREATED);
2563        ctx.append_log_value(&mut buf, "upstream_status");
2564        assert_eq!(b"201", buf.as_ref());
2565
2566        buf = BytesMut::new();
2567        ctx.state.processing_count = 10;
2568        ctx.append_log_value(&mut buf, "processing");
2569        assert_eq!(b"10", buf.as_ref());
2570
2571        buf = BytesMut::new();
2572        ctx.timing.upstream_connect = Some(1);
2573        ctx.append_log_value(&mut buf, "upstream_connect_time");
2574        assert_eq!(b"1", buf.as_ref());
2575
2576        buf = BytesMut::new();
2577        ctx.append_log_value(&mut buf, "upstream_connect_time_human");
2578        assert_eq!(b"1ms", buf.as_ref());
2579
2580        buf = BytesMut::new();
2581        ctx.upstream.connected_count = Some(30);
2582        ctx.append_log_value(&mut buf, "upstream_connected");
2583        assert_eq!(b"30", buf.as_ref());
2584
2585        buf = BytesMut::new();
2586        ctx.timing.upstream_processing = Some(2);
2587        ctx.append_log_value(&mut buf, "upstream_processing_time");
2588        assert_eq!(b"2", buf.as_ref());
2589
2590        buf = BytesMut::new();
2591        ctx.append_log_value(&mut buf, "upstream_processing_time_human");
2592        assert_eq!(b"2ms", buf.as_ref());
2593
2594        buf = BytesMut::new();
2595        ctx.timing.upstream_response = Some(3);
2596        ctx.append_log_value(&mut buf, "upstream_response_time");
2597        assert_eq!(b"3", buf.as_ref());
2598
2599        buf = BytesMut::new();
2600        ctx.append_log_value(&mut buf, "upstream_response_time_human");
2601        assert_eq!(b"3ms", buf.as_ref());
2602
2603        // never measured, or only begun (the start marker): no value
2604        buf = BytesMut::new();
2605        ctx.append_log_value(&mut buf, "upstream_first_body_time");
2606        assert_eq!(b"", buf.as_ref());
2607        buf = BytesMut::new();
2608        ctx.timing.upstream_first_body = Some(-5);
2609        ctx.append_log_value(&mut buf, "upstream_first_body_time");
2610        assert_eq!(b"", buf.as_ref());
2611
2612        buf = BytesMut::new();
2613        ctx.timing.upstream_first_body = Some(1250);
2614        ctx.append_log_value(&mut buf, "upstream_first_body_time");
2615        assert_eq!(b"1250", buf.as_ref());
2616
2617        buf = BytesMut::new();
2618        ctx.append_log_value(&mut buf, "upstream_first_body_time_human");
2619        assert_eq!(b"1.2s", buf.as_ref());
2620
2621        buf = BytesMut::new();
2622        ctx.timing.upstream_tcp_connect = Some(100);
2623        ctx.append_log_value(&mut buf, "upstream_tcp_connect_time");
2624        assert_eq!(b"100", buf.as_ref());
2625
2626        buf = BytesMut::new();
2627        ctx.append_log_value(&mut buf, "upstream_tcp_connect_time_human");
2628        assert_eq!(b"100ms", buf.as_ref());
2629
2630        buf = BytesMut::new();
2631        ctx.timing.upstream_tls_handshake = Some(110);
2632        ctx.append_log_value(&mut buf, "upstream_tls_handshake_time");
2633        assert_eq!(b"110", buf.as_ref());
2634
2635        buf = BytesMut::new();
2636        ctx.timing.upstream_connect_offload_wait = Some(3);
2637        ctx.append_log_value(&mut buf, "upstream_connect_offload_wait_time");
2638        assert_eq!(b"3", buf.as_ref());
2639        buf = BytesMut::new();
2640        ctx.append_log_value(
2641            &mut buf,
2642            "upstream_connect_offload_wait_time_human",
2643        );
2644        assert_eq!(b"3ms", buf.as_ref());
2645
2646        buf = BytesMut::new();
2647        ctx.append_log_value(&mut buf, "upstream_tls_handshake_time_human");
2648        assert_eq!(b"110ms", buf.as_ref());
2649
2650        buf = BytesMut::new();
2651        ctx.timing.upstream_connection_duration = Some(120);
2652        ctx.append_log_value(&mut buf, "upstream_connection_time");
2653        assert_eq!(b"120", buf.as_ref());
2654
2655        buf = BytesMut::new();
2656        ctx.append_log_value(&mut buf, "upstream_connection_time_human");
2657        assert_eq!(b"120ms", buf.as_ref());
2658
2659        buf = BytesMut::new();
2660        ctx.upstream.location = "pingap".to_string().into();
2661        ctx.append_log_value(&mut buf, "location");
2662        assert_eq!(b"pingap", buf.as_ref());
2663
2664        buf = BytesMut::new();
2665        ctx.timing.connection_duration = 4;
2666        ctx.append_log_value(&mut buf, "connection_time");
2667        assert_eq!(b"4", buf.as_ref());
2668
2669        buf = BytesMut::new();
2670        ctx.append_log_value(&mut buf, "connection_time_human");
2671        assert_eq!(b"4ms", buf.as_ref());
2672
2673        buf = BytesMut::new();
2674        ctx.conn.reused = false;
2675        ctx.append_log_value(&mut buf, "connection_reused");
2676        assert_eq!(b"false", buf.as_ref());
2677
2678        buf = BytesMut::new();
2679        ctx.conn.reused = true;
2680        ctx.append_log_value(&mut buf, "connection_reused");
2681        assert_eq!(b"true", buf.as_ref());
2682
2683        buf = BytesMut::new();
2684        ctx.conn.tls_version = Some("TLSv1.3".into());
2685        ctx.append_log_value(&mut buf, "tls_version");
2686        assert_eq!(b"TLSv1.3", buf.as_ref());
2687
2688        buf = BytesMut::new();
2689        ctx.conn.tls_cipher =
2690            Some("ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".into());
2691        ctx.append_log_value(&mut buf, "tls_cipher");
2692        assert_eq!(b"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", buf.as_ref());
2693
2694        buf = BytesMut::new();
2695        ctx.timing.tls_handshake = Some(101);
2696        ctx.append_log_value(&mut buf, "tls_handshake_time");
2697        assert_eq!(b"101", buf.as_ref());
2698
2699        buf = BytesMut::new();
2700        ctx.append_log_value(&mut buf, "tls_handshake_time_human");
2701        assert_eq!(b"101ms", buf.as_ref());
2702
2703        {
2704            let features = ctx.features.get_or_insert_default();
2705            features.compression_stat = Some(CompressionStat {
2706                in_bytes: 1024,
2707                out_bytes: 500,
2708                duration: Duration::from_millis(5),
2709                ..Default::default()
2710            })
2711        }
2712
2713        buf = BytesMut::new();
2714        ctx.append_log_value(&mut buf, "compression_time");
2715        assert_eq!(b"5", buf.as_ref());
2716
2717        buf = BytesMut::new();
2718        ctx.append_log_value(&mut buf, "compression_time_human");
2719        assert_eq!(b"5ms", buf.as_ref());
2720
2721        buf = BytesMut::new();
2722        ctx.append_log_value(&mut buf, "compression_ratio");
2723        assert_eq!(b"2.0", buf.as_ref());
2724
2725        buf = BytesMut::new();
2726        ctx.timing.cache_lookup = Some(6);
2727        ctx.append_log_value(&mut buf, "cache_lookup_time");
2728        assert_eq!(b"6", buf.as_ref());
2729
2730        buf = BytesMut::new();
2731        ctx.append_log_value(&mut buf, "cache_lookup_time_human");
2732        assert_eq!(b"6ms", buf.as_ref());
2733
2734        buf = BytesMut::new();
2735        ctx.timing.cache_lock = Some(7);
2736        ctx.append_log_value(&mut buf, "cache_lock_time");
2737        assert_eq!(b"7", buf.as_ref());
2738
2739        buf = BytesMut::new();
2740        ctx.append_log_value(&mut buf, "cache_lock_time_human");
2741        assert_eq!(b"7ms", buf.as_ref());
2742    }
2743
2744    #[test]
2745    fn test_add_plugin_processing_time() {
2746        let mut ctx = Ctx::new();
2747        ctx.add_plugin_processing_time(&Arc::from("plugin1"), 100);
2748        ctx.add_plugin_processing_time(&Arc::from("plugin2"), 200);
2749        assert_eq!(
2750            ctx.features.unwrap().plugin_processing_times,
2751            Some(vec![
2752                (Arc::from("plugin1"), 100),
2753                (Arc::from("plugin2"), 200)
2754            ])
2755        );
2756    }
2757
2758    #[test]
2759    fn test_get_digest_detail() {
2760        let mut digest = Digest::default();
2761        let detail = get_digest_detail(&digest);
2762        assert_eq!(detail.connection_reused, false);
2763        assert_eq!(detail.connection_time, 0);
2764        assert_eq!(detail.tcp_established, 0);
2765        assert_eq!(detail.tls_established, 0);
2766        assert_eq!(detail.tls_version, None);
2767        assert_eq!(detail.tls_cipher, None);
2768
2769        digest.timing_digest.push(Some(TimingDigest {
2770            established_ts: SystemTime::UNIX_EPOCH
2771                .checked_add(Duration::from_secs(5))
2772                .unwrap(),
2773            ..Default::default()
2774        }));
2775        digest.timing_digest.push(Some(TimingDigest {
2776            established_ts: SystemTime::UNIX_EPOCH
2777                .checked_add(Duration::from_secs(3))
2778                .unwrap(),
2779            ..Default::default()
2780        }));
2781        digest.ssl_digest = Some(Arc::new(SslDigest {
2782            version: "1.3".into(),
2783            cipher: "123".into(),
2784            organization: Some("cloudflare".to_string()),
2785            serial_number: Some(
2786                "0x00000000000000000000000000000abc".to_string(),
2787            ),
2788            cert_digest: vec![],
2789            extension: SslDigestExtension::default(),
2790        }));
2791        let detail = get_digest_detail(&digest);
2792        assert_eq!(detail.connection_reused, true);
2793        assert_eq!(detail.tcp_established, 5000);
2794        assert_eq!(detail.tls_established, 3000);
2795        assert_eq!(detail.tls_version.as_deref(), Some("1.3"));
2796        assert_eq!(detail.tls_cipher.as_deref(), Some("123"));
2797        // Nothing measured on these entries.
2798        assert_eq!(detail.tcp_connect, None);
2799        assert_eq!(detail.tls_handshake, None);
2800
2801        // pingora's per-layer measurements come through as such.
2802        digest.timing_digest = vec![
2803            Some(TimingDigest {
2804                establishment_duration: Some(Duration::from_millis(12)),
2805                ..Default::default()
2806            }),
2807            Some(TimingDigest {
2808                establishment_duration: Some(Duration::from_millis(34)),
2809                ..Default::default()
2810            }),
2811        ];
2812        let detail = get_digest_detail(&digest);
2813        assert_eq!(detail.tcp_connect, Some(12));
2814        assert_eq!(detail.tls_handshake, Some(34));
2815        // Not offloaded: no queueing time to report.
2816        assert_eq!(detail.connect_offload_wait, None);
2817
2818        // An offloaded connect also carries the time it waited for a thread.
2819        digest.timing_digest[0] = Some(TimingDigest {
2820            establishment_duration: Some(Duration::from_millis(12)),
2821            offload_wait_duration: Some(Duration::from_millis(2)),
2822            ..Default::default()
2823        });
2824        let detail = get_digest_detail(&digest);
2825        assert_eq!(detail.connect_offload_wait, Some(2));
2826
2827        // Without TLS the last entry is the transport again: no handshake.
2828        digest.ssl_digest = None;
2829        digest.timing_digest.truncate(1);
2830        let detail = get_digest_detail(&digest);
2831        assert_eq!(detail.tcp_connect, Some(12));
2832        assert_eq!(detail.tls_handshake, None);
2833    }
2834
2835    #[test]
2836    fn test_update_upstream_timing_from_digest() {
2837        let measured = |tcp: u64, tls: u64| Digest {
2838            timing_digest: vec![
2839                Some(TimingDigest {
2840                    establishment_duration: Some(Duration::from_millis(tcp)),
2841                    ..Default::default()
2842                }),
2843                Some(TimingDigest {
2844                    establishment_duration: Some(Duration::from_millis(tls)),
2845                    ..Default::default()
2846                }),
2847            ],
2848            ssl_digest: Some(Arc::new(SslDigest {
2849                version: "1.3".into(),
2850                cipher: "123".into(),
2851                organization: None,
2852                serial_number: None,
2853                cert_digest: vec![],
2854                extension: SslDigestExtension::default(),
2855            })),
2856            ..Default::default()
2857        };
2858
2859        // Measured layers are taken as they are, independent of pingap's
2860        // own end-to-end timer.
2861        let mut ctx = Ctx::new();
2862        ctx.timing.upstream_connect = Some(100);
2863        ctx.update_upstream_timing_from_digest(&measured(12, 34), false);
2864        assert_eq!(Some(12), ctx.timing.upstream_tcp_connect);
2865        assert_eq!(Some(34), ctx.timing.upstream_tls_handshake);
2866        assert_eq!(None, ctx.timing.upstream_connect_offload_wait);
2867
2868        // The offload queueing time rides along when the connect was
2869        // offloaded.
2870        let mut ctx = Ctx::new();
2871        let mut offloaded = measured(12, 34);
2872        offloaded.timing_digest[0] = Some(TimingDigest {
2873            establishment_duration: Some(Duration::from_millis(12)),
2874            offload_wait_duration: Some(Duration::from_millis(2)),
2875            ..Default::default()
2876        });
2877        ctx.update_upstream_timing_from_digest(&offloaded, false);
2878        assert_eq!(Some(2), ctx.timing.upstream_connect_offload_wait);
2879
2880        // A reused connection carries no connect cost for this request.
2881        let mut ctx = Ctx::new();
2882        ctx.timing.upstream_connect = Some(100);
2883        ctx.update_upstream_timing_from_digest(&measured(12, 34), true);
2884        assert_eq!(None, ctx.timing.upstream_tcp_connect);
2885        assert_eq!(None, ctx.timing.upstream_tls_handshake);
2886
2887        // No measurement: fall back to splitting the end-to-end timer by
2888        // the layers' wall-clock timestamps.
2889        let mut ctx = Ctx::new();
2890        ctx.timing.upstream_connect = Some(100);
2891        let mut unmeasured = measured(0, 0);
2892        unmeasured.timing_digest = vec![
2893            Some(TimingDigest {
2894                established_ts: SystemTime::UNIX_EPOCH
2895                    .checked_add(Duration::from_millis(1_000))
2896                    .unwrap(),
2897                ..Default::default()
2898            }),
2899            Some(TimingDigest {
2900                established_ts: SystemTime::UNIX_EPOCH
2901                    .checked_add(Duration::from_millis(1_030))
2902                    .unwrap(),
2903                ..Default::default()
2904            }),
2905        ];
2906        ctx.update_upstream_timing_from_digest(&unmeasured, false);
2907        assert_eq!(Some(70), ctx.timing.upstream_tcp_connect);
2908        assert_eq!(Some(30), ctx.timing.upstream_tls_handshake);
2909    }
2910
2911    #[test]
2912    fn test_modify_body_handler() {
2913        let mut ctx = Ctx::default();
2914
2915        struct TestHandler {}
2916        impl ModifyResponseBody for TestHandler {
2917            fn handle(
2918                &mut self,
2919                _session: &Session,
2920                body: &mut Option<bytes::Bytes>,
2921                _end_of_stream: bool,
2922            ) -> pingora::Result<()> {
2923                *body = Some(Bytes::from("test"));
2924                Ok(())
2925            }
2926        }
2927
2928        ctx.add_modify_body_handler("test", Box::new(TestHandler {}));
2929        assert_eq!(true, ctx.get_modify_body_handler("test").is_some());
2930    }
2931}