1use 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
40const SECOND: u64 = 1_000;
42const MINUTE: u64 = 60 * SECOND;
43const HOUR: u64 = 60 * MINUTE;
44
45#[inline]
46pub fn format_duration(buf: &mut BytesMut, ms: u64) {
49 if ms < SECOND {
50 buf.extend_from_slice(itoa::Buffer::new().format(ms).as_bytes());
52 buf.extend_from_slice(b"ms");
53 } else if ms < MINUTE {
54 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 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 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
102pub trait ModifyResponseBody: Sync + Send {
104 fn handle(
106 &mut self,
107 session: &Session,
108 body: &mut Option<bytes::Bytes>,
109 end_of_stream: bool,
110 ) -> pingora::Result<()>;
111 fn name(&self) -> &str {
113 "unknown"
114 }
115}
116
117pub trait HandleRequestBody: Sync + Send {
127 fn handle(
140 &mut self,
141 body: Option<&bytes::Bytes>,
142 end_of_stream: bool,
143 ) -> pingora::Result<()>;
144 fn restart(&mut self);
147}
148
149#[derive(Default)]
151pub struct ConnectionInfo {
152 pub id: usize,
154 pub client_ip: Option<String>,
156 pub remote_addr: Option<String>,
158 pub remote_port: Option<u16>,
160 pub server_addr: Option<String>,
162 pub server_port: Option<u16>,
164 pub tls_version: Option<Cow<'static, str>>,
169 pub tls_cipher: Option<Cow<'static, str>>,
171 pub ja4: Option<Arc<Ja4Fingerprint>>,
174 pub tls_client_cert: Option<Arc<TlsClientCert>>,
179 pub reused: bool,
181}
182
183#[derive(Debug, Clone, Default, PartialEq, Eq)]
186pub struct TlsClientCert {
187 pub subject: String,
190 pub fingerprint: String,
192 pub serial: String,
194}
195
196pub struct Timing {
199 pub created_at: Instant,
201 pub connection_duration: u64,
204 pub tls_handshake: Option<i32>,
206 pub upstream_connect: Option<i32>,
208 pub upstream_tcp_connect: Option<i32>,
210 pub upstream_tls_handshake: Option<i32>,
212 pub upstream_connect_offload_wait: Option<i32>,
217 pub upstream_processing: Option<i32>,
219 pub upstream_response: Option<i32>,
221 pub upstream_first_body: Option<i32>,
226 pub upstream_connection_duration: Option<u64>,
228 pub cache_lookup: Option<i32>,
230 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
254pub trait UpstreamInstance: Send + Sync {
256 fn on_transport_failure(&self, address: &str);
257 fn on_response(&self, address: &str, status: StatusCode);
258 fn completed(&self) -> i32;
263}
264
265pub trait LocationInstance: Send + Sync {
267 fn name(&self) -> &str;
269 fn upstream(&self) -> &str;
271 fn fallback_upstream(&self) -> &str {
275 ""
276 }
277 fn has_rewrite(&self) -> bool {
281 false
282 }
283 fn rewrite(
288 &self,
289 header: &mut RequestHeader,
290 variables: &mut Option<AHashMap<String, String>>,
291 ) -> pingora::Result<bool>;
292 fn headers(&self) -> Option<&Vec<(HeaderName, HeaderValue, bool)>>;
294 fn client_body_size_limit(&self) -> usize;
296 fn on_request(&self) -> pingora::Result<(u64, i32)>;
302 fn on_response(&self);
304 fn apply_timeouts(
308 &self,
309 _options: &mut pingora::upstreams::peer::PeerOptions,
310 ) {
311 }
312}
313
314#[derive(Default)]
316pub struct UpstreamInfo {
317 pub upstream_instance: Option<Arc<dyn UpstreamInstance>>,
319 pub location_instance: Option<Arc<dyn LocationInstance>>,
321 pub location: Arc<str>,
323 pub name: Arc<str>,
325 pub address: String,
327 pub reused: bool,
329 pub processing_count: Option<i32>,
331 pub connected_count: Option<i32>,
333 pub status: Option<StatusCode>,
335 pub retries: u8,
337 pub fallback: bool,
340 pub fallback_from: Option<Arc<str>>,
343 pub max_retries: Option<u8>,
345 pub max_retry_window: Option<Duration>,
353 pub failed_addresses: Vec<String>,
357 pub backend_inflight: Option<InflightGuard>,
361 pub sticky_cookie: Option<String>,
366}
367
368pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
389pub struct RateLimitQuota {
390 pub limit: u64,
392 pub remaining: u64,
394 pub reset: Option<u64>,
398}
399
400#[derive(Default)]
402pub struct RequestState {
403 pub request_id: Option<String>,
405 pub status: Option<StatusCode>,
407 pub payload_size: usize,
409 pub guards: Vec<Guard>,
413 pub processing_count: i32,
415 pub accepted_count: u64,
417 pub location_processing_count: i32,
419 pub location_accepted_count: u64,
421 pub admin: bool,
424 pub proxying: bool,
429 pub stale_for_status: bool,
434 pub body_end_said: bool,
437 pub rate_limit: Option<RateLimitQuota>,
440}
441
442#[derive(Clone, Debug)]
450pub struct CacheKeyVariant {
451 pub at: usize,
453 pub len: usize,
455 pub alternatives: Arc<Vec<Vec<String>>>,
458}
459
460#[derive(Debug, Clone, PartialEq)]
462pub enum CacheQueryRule {
463 Ignore(Vec<String>),
465 Allow(Vec<String>),
467}
468
469impl CacheQueryRule {
470 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#[derive(Default)]
500pub struct CacheInfo {
501 pub namespace: Option<String>,
503 pub keys: Option<Vec<String>>,
505 pub key_variants: Option<Vec<CacheKeyVariant>>,
508 pub check_cache_control: bool,
510 pub max_ttl: Option<Duration>,
512 pub vary_headers: Option<Arc<Vec<String>>>,
515 pub default_ttl: Option<Duration>,
518 pub status_ttl: Option<Arc<Vec<(u16, Duration)>>>,
522 pub query_rule: Option<Arc<CacheQueryRule>>,
525 pub key_query: Option<String>,
531 pub key_headers: Option<Arc<Vec<String>>>,
535 pub revalidate: bool,
539 pub reading_count: Option<u32>,
541 pub writing_count: Option<u32>,
543}
544
545pub 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
557fn 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 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 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 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 (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 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#[derive(Debug, Clone)]
677pub struct BodyPace {
678 rate: u64,
679 after: u64,
680 sent: u64,
681 limited_since: Option<Instant>,
683}
684
685impl BodyPace {
686 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 pub fn delay(&mut self, len: usize) -> Option<Duration> {
702 self.delay_at(len, Instant::now())
703 }
704
705 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 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 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 self.limited_since = now.checked_sub(due);
749 return None;
750 }
751 Some(due - taken).filter(|delay| !delay.is_zero())
752 }
753}
754
755#[derive(Default)]
757pub struct Features {
758 pub variables: Option<AHashMap<String, String>>,
760 pub original_uri: Option<http::Uri>,
765 pub plugin_processing_times: Option<Vec<(Arc<str>, u32)>>,
767 pub compression_stat: Option<CompressionStat>,
769 pub modify_body_handlers:
771 Option<AHashMap<String, Box<dyn ModifyResponseBody>>>,
772 pub plugin_notes: Option<Vec<(String, &'static str)>>,
776 pub plugin_values: Option<Vec<(String, Box<dyn Any + Send + Sync>)>>,
779 pub request_body_handlers: Option<Vec<Box<dyn HandleRequestBody>>>,
782 pub body_pace: Option<BodyPace>,
787 pub plain_response_body: Option<PlainBody>,
791 #[cfg(feature = "tracing")]
793 pub otel_tracer: Option<OtelTracer>,
794 #[cfg(feature = "tracing")]
796 pub upstream_span: Option<BoxedSpan>,
797}
798
799#[derive(Debug, Clone, PartialEq)]
802pub enum PlainBody {
803 Any,
805 Types(Vec<String>),
807}
808
809#[derive(Default)]
810pub struct CompressionStat {
812 pub algorithm: String,
814 pub in_bytes: usize,
816 pub out_bytes: usize,
818 pub duration: Duration,
820}
821
822impl CompressionStat {
823 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#[cfg(feature = "tracing")]
834pub struct OtelTracer {
835 pub tracer: BoxedTracer,
837 pub http_request_span: BoxedSpan,
839}
840
841#[cfg(feature = "tracing")]
842impl OtelTracer {
843 #[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 &Context::current().with_remote_span_context(
853 self.http_request_span.span_context().clone(),
854 ),
855 )
856 }
857}
858
859pub type NamedPlugin = (Arc<str>, Arc<dyn Plugin>);
861
862#[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#[derive(Default)]
925pub struct Ctx {
926 pub conn: ConnectionInfo,
928 pub upstream: UpstreamInfo,
930 pub timing: Timing,
932 pub state: RequestState,
934 pub cache: Option<Box<CacheInfo>>,
938 pub features: Option<Box<Features>>,
940 pub plugins: Option<Arc<[NamedPlugin]>>,
943 pub response_plugins: Option<Arc<[NamedPlugin]>>,
948}
949
950#[derive(Debug, Default)]
952pub struct DigestDetail {
953 pub connection_reused: bool,
959 pub connection_time: u64,
961 pub tcp_established: u64,
963 pub tls_established: u64,
965 pub tcp_connect: Option<u64>,
969 pub tls_handshake: Option<u64>,
973 pub connect_offload_wait: Option<u64>,
976 pub tls_version: Option<Cow<'static, str>>,
978 pub tls_cipher: Option<Cow<'static, str>>,
980 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#[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 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 tls_version: Some(ssl_digest.version.clone()),
1035 tls_cipher: Some(ssl_digest.cipher.clone()),
1036 tls_client_cert: ssl_digest
1039 .extension
1040 .get::<Arc<TlsClientCert>>()
1041 .cloned(),
1042 }
1043}
1044
1045fn 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
1053fn 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 pub fn new() -> Self {
1068 Self {
1069 ..Default::default()
1070 }
1071 }
1072
1073 #[inline]
1079 pub fn add_variable(&mut self, key: &str, value: &str) {
1080 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 #[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 #[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 #[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 #[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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[inline]
1289 pub fn get_upstream_response_time(&self) -> Option<u32> {
1290 self.get_time_field(self.timing.upstream_response)
1291 }
1292
1293 #[inline]
1298 pub fn get_upstream_connect_time(&self) -> Option<u32> {
1299 self.get_time_field(self.timing.upstream_connect)
1300 }
1301
1302 #[inline]
1307 pub fn get_upstream_processing_time(&self) -> Option<u32> {
1308 self.get_time_field(self.timing.upstream_processing)
1309 }
1310
1311 #[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 #[inline]
1325 pub fn add_plugin_processing_time(&mut self, name: &Arc<str>, time: u32) {
1326 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 #[inline]
1349 pub fn append_log_value(&self, buf: &mut BytesMut, key: &str) {
1350 if let Ok(field) = key.parse::<CtxLogField>() {
1352 self.append_log_field(buf, field);
1353 }
1354 }
1355
1356 pub fn append_log_field(&self, buf: &mut BytesMut, field: CtxLogField) {
1358 macro_rules! append_time {
1360 ($val:expr) => {
1362 if let Some(ms) = $val {
1363 buf.extend(itoa::Buffer::new().format(ms).as_bytes());
1364 }
1365 };
1366 ($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 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 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 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 pub fn generate_server_timing(&self) -> String {
1598 let mut timing_str = String::with_capacity(200);
1599 let mut first = true;
1601
1602 macro_rules! add_timing {
1604 ($name:expr, $dur:expr) => {
1605 if !first {
1606 timing_str.push_str(", ");
1607 }
1608 let _ = write!(&mut timing_str, "{};dur={}", $name, $dur);
1610 first = false;
1611 };
1612 }
1613
1614 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 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 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 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 let service_time = self.timing.created_at.elapsed().as_millis();
1667 if !first {
1669 timing_str.push_str(", ");
1670 }
1671 let _ = write!(&mut timing_str, "total;dur={}", service_time);
1673
1674 timing_str
1675 }
1676
1677 #[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 #[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 #[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 pub fn cache_key_alternatives(&self) -> Vec<Vec<String>> {
1726 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 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 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 #[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 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 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
1817fn 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
1844pub 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 CacheKey::new("", "");
1870 };
1871 let namespace = cache_info.namespace.as_ref().map_or("", |v| v);
1872 let host = get_request_host(header).unwrap_or_default();
1874 let path = header.uri.path();
1875 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 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 for k in cache_info.keys.iter().flatten().chain(&header_keys) {
1913 key_buf.push_str(k);
1914 key_buf.push(':');
1915 }
1916 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 #[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 let mut pace = BodyPace::new(1000, 0);
1951 assert_eq!(Some(500), ms(pace.delay_at(500, at(0))));
1952 assert_eq!(Some(500), ms(pace.delay_at(500, at(500))));
1954 assert_eq!(None, ms(pace.delay_at(1000, at(5000))));
1956 assert_eq!(Some(3000), ms(pace.delay_at(3000, at(5000))));
1959
1960 let mut pace = BodyPace::new(1000, 2000);
1962 assert_eq!(None, ms(pace.delay_at(1500, at(0))));
1963 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 let mut pace = BodyPace::new(0, 0);
1969 assert_eq!(Some(2000), ms(pace.delay_at(2, at(0))));
1970 let mut pace = BodyPace::new(1000, 0);
1972 assert_eq!(None, ms(pace.delay_at(0, at(0))));
1973
1974 let mut pace = BodyPace::new(1000, 0);
1976 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 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 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 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 #[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 assert_eq!("/a?b=2&a=1", ask("/a?b=2&a=1", None));
2009 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 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 assert_eq!("/a?a=1", ask("/a?a=1", Some("a=1")));
2023 assert_eq!("/a", ask("/a", Some("")));
2024 assert_eq!("/a", ask("/a?", Some("")));
2026
2027 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 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 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 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 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 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 let elapsed_ms = ctx.timing.created_at.elapsed().as_millis();
2123 assert!(elapsed_ms < 100, "created_at should be a recent timestamp");
2124 assert!(ctx.cache.is_none());
2126 assert!(ctx.features.is_none());
2127 assert_eq!(ctx.conn.id, 0);
2128 }
2129
2130 #[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 assert_eq!(None, ctx.get_plugin_value::<u16>("a"));
2173 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 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 #[test]
2220 fn test_get_time_field() {
2221 let mut ctx = Ctx::new();
2222
2223 ctx.timing.upstream_response = Some(100);
2225 assert_eq!(ctx.get_upstream_response_time(), Some(100));
2226
2227 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 ctx.timing.upstream_response = None;
2237 assert_eq!(ctx.get_upstream_response_time(), None);
2238 }
2239
2240 #[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 #[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 #[test]
2305 fn test_append_log_value_coverage() {
2306 let mut ctx = Ctx::new();
2307 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 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 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 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 #[test]
2335 fn test_get_cache_key() {
2336 let method = "GET";
2337 let mut h2 = RequestHeader::build("GET", b"/", None).unwrap();
2339 h2.set_uri(Uri::from_static("https://example.com/path?a=1"));
2340 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 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 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 assert_eq!(key2.primary(), "8fe845e5498004cd81203be9074a8e68");
2370
2371 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 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 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 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 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 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 #[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 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 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 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 assert_eq!(detail.tcp_connect, None);
2799 assert_eq!(detail.tls_handshake, None);
2800
2801 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 assert_eq!(detail.connect_offload_wait, None);
2817
2818 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 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 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 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 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 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}