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::borrow::Cow;
33use std::fmt::Write;
34use std::sync::Arc;
35use std::sync::atomic::{AtomicU32, Ordering};
36use std::time::{Duration, Instant, SystemTime};
37use strum::EnumString;
38
39const SECOND: u64 = 1_000;
41const MINUTE: u64 = 60 * SECOND;
42const HOUR: u64 = 60 * MINUTE;
43
44#[inline]
45pub fn format_duration(buf: &mut BytesMut, ms: u64) {
48 if ms < SECOND {
49 buf.extend_from_slice(itoa::Buffer::new().format(ms).as_bytes());
51 buf.extend_from_slice(b"ms");
52 } else if ms < MINUTE {
53 buf.extend_from_slice(
55 itoa::Buffer::new().format(ms / SECOND).as_bytes(),
56 );
57 let value = (ms % SECOND) / 100;
58 if value != 0 {
59 buf.extend_from_slice(b".");
60 buf.extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
61 }
62 buf.extend_from_slice(b"s");
63 } else if ms < HOUR {
64 buf.extend_from_slice(
66 itoa::Buffer::new().format(ms / MINUTE).as_bytes(),
67 );
68 let value = ms % MINUTE * 10 / MINUTE;
69 if value != 0 {
70 buf.extend_from_slice(b".");
71 buf.extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
72 }
73 buf.extend_from_slice(b"m");
74 } else {
75 buf.extend_from_slice(itoa::Buffer::new().format(ms / HOUR).as_bytes());
77 let value = ms % HOUR * 10 / HOUR;
78 if value != 0 {
79 buf.extend_from_slice(b".");
80 buf.extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
81 }
82 buf.extend_from_slice(b"h");
83 }
84}
85
86#[derive(PartialEq)]
87pub enum ModifiedMode {
88 Upstream,
89 Response,
90}
91
92impl From<&str> for ModifiedMode {
93 fn from(value: &str) -> Self {
94 match value {
95 "upstream" => ModifiedMode::Upstream,
96 _ => ModifiedMode::Response,
97 }
98 }
99}
100
101pub trait ModifyResponseBody: Sync + Send {
103 fn handle(
105 &mut self,
106 session: &Session,
107 body: &mut Option<bytes::Bytes>,
108 end_of_stream: bool,
109 ) -> pingora::Result<()>;
110 fn name(&self) -> &str {
112 "unknown"
113 }
114}
115
116pub trait HandleRequestBody: Sync + Send {
126 fn handle(
139 &mut self,
140 body: Option<&bytes::Bytes>,
141 end_of_stream: bool,
142 ) -> pingora::Result<()>;
143 fn restart(&mut self);
146}
147
148#[derive(Default)]
150pub struct ConnectionInfo {
151 pub id: usize,
153 pub client_ip: Option<String>,
155 pub remote_addr: Option<String>,
157 pub remote_port: Option<u16>,
159 pub server_addr: Option<String>,
161 pub server_port: Option<u16>,
163 pub tls_version: Option<Cow<'static, str>>,
168 pub tls_cipher: Option<Cow<'static, str>>,
170 pub ja4: Option<Arc<Ja4Fingerprint>>,
173 pub tls_client_cert: Option<Arc<TlsClientCert>>,
178 pub reused: bool,
180}
181
182#[derive(Debug, Clone, Default, PartialEq, Eq)]
185pub struct TlsClientCert {
186 pub subject: String,
189 pub fingerprint: String,
191 pub serial: String,
193}
194
195pub struct Timing {
198 pub created_at: Instant,
200 pub connection_duration: u64,
203 pub tls_handshake: Option<i32>,
205 pub upstream_connect: Option<i32>,
207 pub upstream_tcp_connect: Option<i32>,
209 pub upstream_tls_handshake: Option<i32>,
211 pub upstream_connect_offload_wait: Option<i32>,
216 pub upstream_processing: Option<i32>,
218 pub upstream_response: Option<i32>,
220 pub upstream_connection_duration: Option<u64>,
222 pub cache_lookup: Option<i32>,
224 pub cache_lock: Option<i32>,
226}
227
228impl Default for Timing {
229 fn default() -> Self {
230 Self {
231 created_at: Instant::now(),
232 connection_duration: 0,
233 tls_handshake: None,
234 upstream_connect: None,
235 upstream_tcp_connect: None,
236 upstream_tls_handshake: None,
237 upstream_connect_offload_wait: None,
238 upstream_processing: None,
239 upstream_response: None,
240 upstream_connection_duration: None,
241 cache_lookup: None,
242 cache_lock: None,
243 }
244 }
245}
246
247pub trait UpstreamInstance: Send + Sync {
249 fn on_transport_failure(&self, address: &str);
250 fn on_response(&self, address: &str, status: StatusCode);
251 fn completed(&self) -> i32;
256}
257
258pub trait LocationInstance: Send + Sync {
260 fn name(&self) -> &str;
262 fn upstream(&self) -> &str;
264 fn has_rewrite(&self) -> bool {
268 false
269 }
270 fn rewrite(
275 &self,
276 header: &mut RequestHeader,
277 variables: &mut Option<AHashMap<String, String>>,
278 ) -> pingora::Result<bool>;
279 fn headers(&self) -> Option<&Vec<(HeaderName, HeaderValue, bool)>>;
281 fn client_body_size_limit(&self) -> usize;
283 fn on_request(&self) -> pingora::Result<(u64, i32)>;
289 fn on_response(&self);
291 fn apply_timeouts(
295 &self,
296 _options: &mut pingora::upstreams::peer::PeerOptions,
297 ) {
298 }
299}
300
301#[derive(Default)]
303pub struct UpstreamInfo {
304 pub upstream_instance: Option<Arc<dyn UpstreamInstance>>,
306 pub location_instance: Option<Arc<dyn LocationInstance>>,
308 pub location: Arc<str>,
310 pub name: Arc<str>,
312 pub address: String,
314 pub reused: bool,
316 pub processing_count: Option<i32>,
318 pub connected_count: Option<i32>,
320 pub status: Option<StatusCode>,
322 pub retries: u8,
324 pub max_retries: Option<u8>,
326 pub max_retry_window: Option<Duration>,
334 pub failed_addresses: Vec<String>,
338 pub backend_inflight: Option<InflightGuard>,
342 pub sticky_cookie: Option<String>,
347}
348
349pub struct InflightGuard(Arc<AtomicU32>);
353
354impl InflightGuard {
355 pub fn new(count: Arc<AtomicU32>) -> Self {
356 count.fetch_add(1, Ordering::Relaxed);
357 Self(count)
358 }
359}
360
361impl Drop for InflightGuard {
362 fn drop(&mut self) {
363 self.0.fetch_sub(1, Ordering::Relaxed);
364 }
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
370pub struct RateLimitQuota {
371 pub limit: u64,
373 pub remaining: u64,
375 pub reset: Option<u64>,
379}
380
381#[derive(Default)]
383pub struct RequestState {
384 pub request_id: Option<String>,
386 pub status: Option<StatusCode>,
388 pub payload_size: usize,
390 pub guards: Vec<Guard>,
394 pub processing_count: i32,
396 pub accepted_count: u64,
398 pub location_processing_count: i32,
400 pub location_accepted_count: u64,
402 pub proxying: bool,
407 pub stale_for_status: bool,
412 pub body_end_said: bool,
415 pub rate_limit: Option<RateLimitQuota>,
418}
419
420#[derive(Clone, Debug)]
428pub struct CacheKeyVariant {
429 pub at: usize,
431 pub len: usize,
433 pub alternatives: Arc<Vec<Vec<String>>>,
436}
437
438#[derive(Debug, Clone, PartialEq)]
440pub enum CacheQueryRule {
441 Ignore(Vec<String>),
443 Allow(Vec<String>),
445}
446
447impl CacheQueryRule {
448 pub fn key_query(&self, query: &str) -> String {
457 fn name(pair: &str) -> &str {
458 pair.split('=').next().unwrap_or(pair)
459 }
460 let mut pairs: Vec<&str> = query
461 .split('&')
462 .filter(|pair| !pair.is_empty())
463 .filter(|pair| {
464 let name = name(pair);
465 match self {
466 Self::Ignore(names) => !names.iter().any(|n| n == name),
467 Self::Allow(names) => names.iter().any(|n| n == name),
468 }
469 })
470 .collect();
471 pairs.sort_by(|a, b| name(a).cmp(name(b)));
472 pairs.join("&")
473 }
474}
475
476#[derive(Default)]
478pub struct CacheInfo {
479 pub namespace: Option<String>,
481 pub keys: Option<Vec<String>>,
483 pub key_variants: Option<Vec<CacheKeyVariant>>,
486 pub check_cache_control: bool,
488 pub max_ttl: Option<Duration>,
490 pub vary_headers: Option<Arc<Vec<String>>>,
493 pub default_ttl: Option<Duration>,
496 pub status_ttl: Option<Arc<Vec<(u16, Duration)>>>,
500 pub query_rule: Option<Arc<CacheQueryRule>>,
503 pub key_query: Option<String>,
509 pub key_headers: Option<Arc<Vec<String>>>,
513 pub revalidate: bool,
517 pub reading_count: Option<u32>,
519 pub writing_count: Option<u32>,
521}
522
523pub fn cache_key_slot(value: &str) -> String {
529 if !value.contains([':', '%']) {
530 return value.to_string();
531 }
532 value.replace('%', "%25").replace(':', "%3A")
533}
534
535fn header_key_slot(header: &RequestHeader, name: &str) -> String {
540 let mut values = header.headers.get_all(name).iter();
541 let Some(first) = values.next() else {
542 return String::new();
543 };
544 let mut value = String::from_utf8_lossy(first.as_bytes()).into_owned();
545 for more in values {
546 value.push_str(", ");
547 value.push_str(&String::from_utf8_lossy(more.as_bytes()));
548 }
549 cache_key_slot(&value)
550}
551
552impl CacheInfo {
553 fn header_key_components(&self, header: &RequestHeader) -> Vec<String> {
570 let Some(names) = &self.key_headers else {
571 return vec![];
572 };
573 let mut slots: Vec<String> = names
574 .iter()
575 .map(|name| header_key_slot(header, name))
576 .collect();
577 if slots.iter().all(|slot| slot.is_empty()) {
578 slots.clear();
579 }
580 slots
581 }
582
583 pub fn settle_key_query(&mut self, header: &RequestHeader) {
591 if let Some(rule) = &self.query_rule {
592 self.key_query =
593 Some(rule.key_query(header.uri.query().unwrap_or_default()));
594 }
595 }
596
597 pub fn ask_with_key_query(&self, header: &mut RequestHeader) -> bool {
613 let made;
614 let query = match (&self.key_query, &self.query_rule) {
615 (Some(query), _) => query.as_str(),
616 (None, Some(rule)) => {
620 made = rule.key_query(header.uri.query().unwrap_or_default());
621 made.as_str()
622 },
623 (None, None) => return true,
624 };
625 if header.uri.query() == (!query.is_empty()).then_some(query) {
627 return true;
628 }
629 let path_and_query = if query.is_empty() {
630 header.uri.path().to_string()
631 } else {
632 format!("{}?{query}", header.uri.path())
633 };
634 let mut parts = header.uri.clone().into_parts();
635 let Ok(path_and_query) = path_and_query.parse() else {
636 return false;
637 };
638 parts.path_and_query = Some(path_and_query);
639 let Ok(uri) = http::Uri::from_parts(parts) else {
640 return false;
641 };
642 header.set_uri(uri);
643 true
644 }
645}
646
647#[derive(Debug, Clone)]
655pub struct BodyPace {
656 rate: u64,
657 after: u64,
658 sent: u64,
659 limited_since: Option<Instant>,
661}
662
663impl BodyPace {
664 pub fn new(rate: u64, after: u64) -> Self {
667 Self {
668 rate: rate.max(1),
669 after,
670 sent: 0,
671 limited_since: None,
672 }
673 }
674
675 pub fn delay(&mut self, len: usize) -> Option<Duration> {
680 self.delay_at(len, Instant::now())
681 }
682
683 pub fn delay_before(&mut self, len: usize) -> Option<Duration> {
689 self.delay_before_at(len, Instant::now())
690 }
691
692 fn delay_at(&mut self, len: usize, now: Instant) -> Option<Duration> {
693 self.count(len, now);
694 self.owed(now)
695 }
696
697 fn delay_before_at(
698 &mut self,
699 len: usize,
700 now: Instant,
701 ) -> Option<Duration> {
702 let owed = self.owed(now);
703 self.count(len, now);
704 owed
705 }
706
707 fn count(&mut self, len: usize, now: Instant) {
708 self.sent = self.sent.saturating_add(len as u64);
709 if self.sent > self.after {
710 self.limited_since.get_or_insert(now);
711 }
712 }
713
714 fn owed(&mut self, now: Instant) -> Option<Duration> {
716 let limited = self.sent.checked_sub(self.after).filter(|n| *n > 0)?;
717 let since = self.limited_since?;
718 let due = Duration::from_secs_f64(limited as f64 / self.rate as f64);
720 let taken = now.saturating_duration_since(since);
721 if taken > due {
722 self.limited_since = now.checked_sub(due);
727 return None;
728 }
729 Some(due - taken).filter(|delay| !delay.is_zero())
730 }
731}
732
733#[derive(Default)]
735pub struct Features {
736 pub variables: Option<AHashMap<String, String>>,
738 pub original_uri: Option<http::Uri>,
743 pub plugin_processing_times: Option<Vec<(Arc<str>, u32)>>,
745 pub compression_stat: Option<CompressionStat>,
747 pub modify_body_handlers:
749 Option<AHashMap<String, Box<dyn ModifyResponseBody>>>,
750 pub plugin_notes: Option<Vec<(String, &'static str)>>,
754 pub request_body_handlers: Option<Vec<Box<dyn HandleRequestBody>>>,
757 pub body_pace: Option<BodyPace>,
762 pub plain_response_body: Option<PlainBody>,
766 #[cfg(feature = "tracing")]
768 pub otel_tracer: Option<OtelTracer>,
769 #[cfg(feature = "tracing")]
771 pub upstream_span: Option<BoxedSpan>,
772}
773
774#[derive(Debug, Clone, PartialEq)]
777pub enum PlainBody {
778 Any,
780 Types(Vec<String>),
782}
783
784#[derive(Default)]
785pub struct CompressionStat {
787 pub algorithm: String,
789 pub in_bytes: usize,
791 pub out_bytes: usize,
793 pub duration: Duration,
795}
796
797impl CompressionStat {
798 pub fn ratio(&self) -> f64 {
800 if self.out_bytes == 0 {
801 return 0.0;
802 }
803 (self.in_bytes as f64) / (self.out_bytes as f64)
804 }
805}
806
807#[cfg(feature = "tracing")]
809pub struct OtelTracer {
810 pub tracer: BoxedTracer,
812 pub http_request_span: BoxedSpan,
814}
815
816#[cfg(feature = "tracing")]
817impl OtelTracer {
818 #[inline]
820 pub fn new_upstream_span(&self, name: &str) -> BoxedSpan {
821 self.tracer
822 .span_builder(name.to_string())
823 .with_kind(SpanKind::Client)
824 .start_with_context(
825 &self.tracer,
826 &Context::current().with_remote_span_context(
828 self.http_request_span.span_context().clone(),
829 ),
830 )
831 }
832}
833
834pub type NamedPlugin = (Arc<str>, Arc<dyn Plugin>);
836
837#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, strum::Display)]
842#[strum(serialize_all = "snake_case")]
843pub enum CtxLogField {
844 ConnectionId,
845 UpstreamReused,
846 UpstreamStatus,
847 UpstreamAddr,
848 Processing,
849 UpstreamConnected,
850 UpstreamConnectTime,
851 UpstreamConnectTimeHuman,
852 UpstreamProcessingTime,
853 UpstreamProcessingTimeHuman,
854 UpstreamResponseTime,
855 UpstreamResponseTimeHuman,
856 UpstreamTcpConnectTime,
857 UpstreamTcpConnectTimeHuman,
858 UpstreamTlsHandshakeTime,
859 UpstreamTlsHandshakeTimeHuman,
860 UpstreamConnectOffloadWaitTime,
861 UpstreamConnectOffloadWaitTimeHuman,
862 UpstreamConnectionTime,
863 UpstreamConnectionTimeHuman,
864 ConnectionTime,
865 ConnectionTimeHuman,
866 Location,
867 ConnectionReused,
868 TlsVersion,
869 TlsCipher,
870 TlsClientSubject,
871 TlsClientFingerprint,
872 TlsClientSerial,
873 TlsClientVerified,
874 Ja4,
875 #[strum(serialize = "ja4_r")]
876 Ja4R,
877 #[strum(serialize = "ja4_o")]
878 Ja4O,
879 #[strum(serialize = "ja4_ro")]
880 Ja4Ro,
881 TlsHandshakeTime,
882 TlsHandshakeTimeHuman,
883 CompressionTime,
884 CompressionTimeHuman,
885 CompressionRatio,
886 CacheLookupTime,
887 CacheLookupTimeHuman,
888 CacheLockTime,
889 CacheLockTimeHuman,
890 ServiceTime,
891 ServiceTimeHuman,
892}
893
894#[derive(Default)]
897pub struct Ctx {
898 pub conn: ConnectionInfo,
900 pub upstream: UpstreamInfo,
902 pub timing: Timing,
904 pub state: RequestState,
906 pub cache: Option<Box<CacheInfo>>,
910 pub features: Option<Box<Features>>,
912 pub plugins: Option<Arc<[NamedPlugin]>>,
915 pub response_plugins: Option<Arc<[NamedPlugin]>>,
920}
921
922#[derive(Debug, Default)]
924pub struct DigestDetail {
925 pub connection_reused: bool,
931 pub connection_time: u64,
933 pub tcp_established: u64,
935 pub tls_established: u64,
937 pub tcp_connect: Option<u64>,
941 pub tls_handshake: Option<u64>,
945 pub connect_offload_wait: Option<u64>,
948 pub tls_version: Option<Cow<'static, str>>,
950 pub tls_cipher: Option<Cow<'static, str>>,
952 pub tls_client_cert: Option<Arc<TlsClientCert>>,
955}
956
957#[inline]
958pub(crate) fn timing_to_ms(timing: Option<&Option<TimingDigest>>) -> u64 {
959 match timing {
960 Some(Some(item)) => item
961 .established_ts
962 .duration_since(SystemTime::UNIX_EPOCH)
963 .unwrap_or_default()
964 .as_millis() as u64,
965 _ => 0,
966 }
967}
968
969#[inline]
972pub fn get_digest_detail(digest: &Digest) -> DigestDetail {
973 let tcp_established = timing_to_ms(digest.timing_digest.first());
974 let mut connection_time = 0;
975 let now = now_ms();
976 if tcp_established > 0 && tcp_established < now {
977 connection_time = now - tcp_established;
978 }
979 let connection_reused = connection_time > 100;
980 let tcp_connect = establishment_ms(digest.timing_digest.first());
984 let connect_offload_wait = offload_wait_ms(digest.timing_digest.first());
985
986 let Some(ssl_digest) = &digest.ssl_digest else {
987 return DigestDetail {
988 connection_reused,
989 tcp_established,
990 connection_time,
991 tcp_connect,
992 connect_offload_wait,
993 ..Default::default()
994 };
995 };
996
997 DigestDetail {
998 connection_reused,
999 tcp_established,
1000 connection_time,
1001 tcp_connect,
1002 connect_offload_wait,
1003 tls_established: timing_to_ms(digest.timing_digest.last()),
1004 tls_handshake: establishment_ms(digest.timing_digest.last()),
1005 tls_version: Some(ssl_digest.version.clone()),
1007 tls_cipher: Some(ssl_digest.cipher.clone()),
1008 tls_client_cert: ssl_digest
1011 .extension
1012 .get::<Arc<TlsClientCert>>()
1013 .cloned(),
1014 }
1015}
1016
1017fn establishment_ms(timing: Option<&Option<TimingDigest>>) -> Option<u64> {
1019 timing
1020 .and_then(|item| item.as_ref())
1021 .and_then(|item| item.establishment_duration)
1022 .map(|duration| duration.as_millis() as u64)
1023}
1024
1025fn offload_wait_ms(timing: Option<&Option<TimingDigest>>) -> Option<u64> {
1028 timing
1029 .and_then(|item| item.as_ref())
1030 .and_then(|item| item.offload_wait_duration)
1031 .map(|duration| duration.as_millis() as u64)
1032}
1033
1034impl Ctx {
1035 pub fn new() -> Self {
1040 Self {
1041 ..Default::default()
1042 }
1043 }
1044
1045 #[inline]
1051 pub fn add_variable(&mut self, key: &str, value: &str) {
1052 let features = self.features.get_or_insert_default();
1054 let variables = features.variables.get_or_insert_with(AHashMap::new);
1055 variables.insert(key.to_string(), value.to_string());
1056 }
1057
1058 #[inline]
1063 pub fn extend_variables(&mut self, values: AHashMap<String, String>) {
1064 let features = self.features.get_or_insert_default();
1065 if let Some(variables) = features.variables.as_mut() {
1066 variables.extend(values);
1067 } else {
1068 features.variables = Some(values);
1069 }
1070 }
1071
1072 #[inline]
1079 pub fn get_variable(&self, key: &str) -> Option<&str> {
1080 self.features
1081 .as_ref()?
1082 .variables
1083 .as_ref()?
1084 .get(key)
1085 .map(|v| v.as_str())
1086 }
1087
1088 #[inline]
1097 pub fn set_plugin_note(&mut self, name: &str, note: &'static str) {
1098 let notes = self
1099 .features
1100 .get_or_insert_default()
1101 .plugin_notes
1102 .get_or_insert_default();
1103 match notes.iter_mut().find(|(key, _)| key == name) {
1104 Some(found) => found.1 = note,
1105 None => notes.push((name.to_string(), note)),
1106 }
1107 }
1108
1109 #[inline]
1111 pub fn get_plugin_note(&self, name: &str) -> Option<&'static str> {
1112 self.features
1113 .as_ref()?
1114 .plugin_notes
1115 .as_ref()?
1116 .iter()
1117 .find(|(key, _)| key == name)
1118 .map(|(_, note)| *note)
1119 }
1120
1121 #[inline]
1135 pub fn keep_response_body_plain(&mut self, types: Option<&[String]>) {
1136 let wanted =
1137 &mut self.features.get_or_insert_default().plain_response_body;
1138 *wanted = match (wanted.take(), types) {
1139 (Some(PlainBody::Any), _) | (_, None) => Some(PlainBody::Any),
1140 (Some(PlainBody::Types(mut all)), Some(types)) => {
1141 all.extend_from_slice(types);
1142 Some(PlainBody::Types(all))
1143 },
1144 (None, Some(types)) => Some(PlainBody::Types(types.to_vec())),
1145 };
1146 }
1147
1148 #[inline]
1152 pub fn response_body_stays_plain(
1153 &self,
1154 is_of: impl FnOnce(&[String]) -> bool,
1155 ) -> bool {
1156 let wanted = self
1157 .features
1158 .as_ref()
1159 .and_then(|features| features.plain_response_body.as_ref());
1160 match wanted {
1161 None => false,
1162 Some(PlainBody::Any) => true,
1163 Some(PlainBody::Types(types)) => is_of(types),
1164 }
1165 }
1166
1167 #[inline]
1173 pub fn add_modify_body_handler(
1174 &mut self,
1175 name: &str,
1176 handler: Box<dyn ModifyResponseBody>,
1177 ) {
1178 let features = self.features.get_or_insert_default();
1179 let handlers = features
1180 .modify_body_handlers
1181 .get_or_insert_with(AHashMap::new);
1182 handlers.insert(name.to_string(), handler);
1183 }
1184
1185 #[inline]
1188 pub fn add_request_body_handler(
1189 &mut self,
1190 handler: Box<dyn HandleRequestBody>,
1191 ) {
1192 self.features
1193 .get_or_insert_default()
1194 .request_body_handlers
1195 .get_or_insert_default()
1196 .push(handler);
1197 }
1198
1199 #[inline]
1201 pub fn get_modify_body_handler(
1202 &mut self,
1203 name: &str,
1204 ) -> Option<&mut Box<dyn ModifyResponseBody>> {
1205 self.features
1206 .as_mut()
1207 .and_then(|f| f.modify_body_handlers.as_mut())
1208 .and_then(|h| h.get_mut(name))
1209 }
1210
1211 #[inline]
1214 fn get_time_field(&self, field: Option<i32>) -> Option<u32> {
1215 if let Some(value) = field
1216 && value >= 0
1217 {
1218 return Some(value as u32);
1219 }
1220 None
1221 }
1222
1223 #[inline]
1228 pub fn get_upstream_response_time(&self) -> Option<u32> {
1229 self.get_time_field(self.timing.upstream_response)
1230 }
1231
1232 #[inline]
1237 pub fn get_upstream_connect_time(&self) -> Option<u32> {
1238 self.get_time_field(self.timing.upstream_connect)
1239 }
1240
1241 #[inline]
1246 pub fn get_upstream_processing_time(&self) -> Option<u32> {
1247 self.get_time_field(self.timing.upstream_processing)
1248 }
1249
1250 #[inline]
1256 pub fn add_plugin_processing_time(&mut self, name: &Arc<str>, time: u32) {
1257 let features = self.features.get_or_insert_default();
1259 let times = features
1260 .plugin_processing_times
1261 .get_or_insert_with(|| Vec::with_capacity(5));
1262 if let Some(item) = times.iter_mut().find(|item| &item.0 == name) {
1263 item.1 += time;
1264 } else {
1265 times.push((Arc::clone(name), time));
1266 }
1267 }
1268
1269 #[inline]
1280 pub fn append_log_value(&self, buf: &mut BytesMut, key: &str) {
1281 if let Ok(field) = key.parse::<CtxLogField>() {
1283 self.append_log_field(buf, field);
1284 }
1285 }
1286
1287 pub fn append_log_field(&self, buf: &mut BytesMut, field: CtxLogField) {
1289 macro_rules! append_time {
1291 ($val:expr) => {
1293 if let Some(ms) = $val {
1294 buf.extend(itoa::Buffer::new().format(ms).as_bytes());
1295 }
1296 };
1297 ($val:expr, human) => {
1299 if let Some(ms) = $val {
1300 format_duration(buf, ms as u64);
1301 }
1302 };
1303 }
1304
1305 match field {
1306 CtxLogField::ConnectionId => {
1307 buf.extend(itoa::Buffer::new().format(self.conn.id).as_bytes());
1308 },
1309 CtxLogField::UpstreamReused => {
1310 if self.upstream.reused {
1311 buf.extend(b"true");
1312 } else {
1313 buf.extend(b"false");
1314 }
1315 },
1316 CtxLogField::UpstreamStatus => {
1317 if let Some(status) = &self.upstream.status {
1318 buf.extend_from_slice(status.as_str().as_bytes());
1319 } else {
1320 buf.extend_from_slice(b"-");
1321 }
1322 },
1323 CtxLogField::UpstreamAddr => {
1324 buf.extend(self.upstream.address.as_bytes())
1325 },
1326 CtxLogField::Processing => buf.extend(
1327 itoa::Buffer::new()
1328 .format(self.state.processing_count)
1329 .as_bytes(),
1330 ),
1331 CtxLogField::UpstreamConnected => {
1332 if let Some(value) = self.upstream.connected_count {
1333 buf.extend(itoa::Buffer::new().format(value).as_bytes());
1334 }
1335 },
1336
1337 CtxLogField::UpstreamConnectTime => {
1339 append_time!(self.get_upstream_connect_time())
1340 },
1341 CtxLogField::UpstreamConnectTimeHuman => {
1342 append_time!(self.get_upstream_connect_time(), human)
1343 },
1344
1345 CtxLogField::UpstreamProcessingTime => {
1346 append_time!(self.get_upstream_processing_time())
1347 },
1348 CtxLogField::UpstreamProcessingTimeHuman => {
1349 append_time!(self.get_upstream_processing_time(), human)
1350 },
1351 CtxLogField::UpstreamResponseTime => {
1352 append_time!(self.get_upstream_response_time())
1353 },
1354 CtxLogField::UpstreamResponseTimeHuman => {
1355 append_time!(self.get_upstream_response_time(), human)
1356 },
1357 CtxLogField::UpstreamTcpConnectTime => {
1358 append_time!(self.timing.upstream_tcp_connect)
1359 },
1360 CtxLogField::UpstreamTcpConnectTimeHuman => {
1361 append_time!(self.timing.upstream_tcp_connect, human)
1362 },
1363 CtxLogField::UpstreamTlsHandshakeTime => {
1364 append_time!(self.timing.upstream_tls_handshake)
1365 },
1366 CtxLogField::UpstreamTlsHandshakeTimeHuman => {
1367 append_time!(self.timing.upstream_tls_handshake, human)
1368 },
1369 CtxLogField::UpstreamConnectOffloadWaitTime => {
1370 append_time!(self.timing.upstream_connect_offload_wait)
1371 },
1372 CtxLogField::UpstreamConnectOffloadWaitTimeHuman => {
1373 append_time!(self.timing.upstream_connect_offload_wait, human)
1374 },
1375 CtxLogField::UpstreamConnectionTime => {
1376 append_time!(self.timing.upstream_connection_duration)
1377 },
1378 CtxLogField::UpstreamConnectionTimeHuman => {
1379 append_time!(self.timing.upstream_connection_duration, human)
1380 },
1381 CtxLogField::ConnectionTime => {
1382 append_time!(Some(self.timing.connection_duration))
1383 },
1384 CtxLogField::ConnectionTimeHuman => {
1385 append_time!(Some(self.timing.connection_duration), human)
1386 },
1387
1388 CtxLogField::Location => {
1390 if !self.upstream.location.is_empty() {
1391 buf.extend(self.upstream.location.as_bytes())
1392 }
1393 },
1394 CtxLogField::ConnectionReused => {
1395 if self.conn.reused {
1396 buf.extend(b"true");
1397 } else {
1398 buf.extend(b"false");
1399 }
1400 },
1401 CtxLogField::TlsVersion => {
1402 if let Some(value) = &self.conn.tls_version {
1403 buf.extend(value.as_bytes());
1404 }
1405 },
1406 CtxLogField::TlsCipher => {
1407 if let Some(value) = &self.conn.tls_cipher {
1408 buf.extend(value.as_bytes());
1409 }
1410 },
1411 CtxLogField::TlsClientSubject => {
1412 if let Some(cert) = &self.conn.tls_client_cert {
1413 buf.extend(cert.subject.as_bytes());
1414 }
1415 },
1416 CtxLogField::TlsClientFingerprint => {
1417 if let Some(cert) = &self.conn.tls_client_cert {
1418 buf.extend(cert.fingerprint.as_bytes());
1419 }
1420 },
1421 CtxLogField::TlsClientSerial => {
1422 if let Some(cert) = &self.conn.tls_client_cert {
1423 buf.extend(cert.serial.as_bytes());
1424 }
1425 },
1426 CtxLogField::TlsClientVerified => {
1427 if self.conn.tls_client_cert.is_some() {
1428 buf.extend(b"true");
1429 } else {
1430 buf.extend(b"false");
1431 }
1432 },
1433 CtxLogField::Ja4 => {
1434 if let Some(fingerprint) = &self.conn.ja4 {
1435 buf.extend(fingerprint.ja4().as_bytes());
1436 }
1437 },
1438 CtxLogField::Ja4R => {
1439 if let Some(fingerprint) = &self.conn.ja4 {
1440 buf.extend(fingerprint.ja4_r().as_bytes());
1441 }
1442 },
1443 CtxLogField::Ja4O => {
1444 if let Some(fingerprint) = &self.conn.ja4 {
1445 buf.extend(fingerprint.ja4_o().as_bytes());
1446 }
1447 },
1448 CtxLogField::Ja4Ro => {
1449 if let Some(fingerprint) = &self.conn.ja4 {
1450 buf.extend(fingerprint.ja4_ro().as_bytes());
1451 }
1452 },
1453 CtxLogField::TlsHandshakeTime => {
1454 append_time!(self.timing.tls_handshake)
1455 },
1456 CtxLogField::TlsHandshakeTimeHuman => {
1457 append_time!(self.timing.tls_handshake, human)
1458 },
1459 CtxLogField::CompressionTime => {
1460 if let Some(feature) = &self.features
1461 && let Some(value) = &feature.compression_stat
1462 {
1463 append_time!(Some(value.duration.as_millis() as u64))
1464 }
1465 },
1466 CtxLogField::CompressionTimeHuman => {
1467 if let Some(feature) = &self.features
1468 && let Some(value) = &feature.compression_stat
1469 {
1470 append_time!(Some(value.duration.as_millis() as u64), human)
1471 }
1472 },
1473 CtxLogField::CompressionRatio => {
1474 if let Some(feature) = &self.features
1475 && let Some(value) = &feature.compression_stat
1476 {
1477 let tenths = (value.ratio() * 10.0).round() as u64;
1479 buf.extend(
1480 itoa::Buffer::new().format(tenths / 10).as_bytes(),
1481 );
1482 buf.extend_from_slice(b".");
1483 buf.extend(
1484 itoa::Buffer::new().format(tenths % 10).as_bytes(),
1485 );
1486 }
1487 },
1488 CtxLogField::CacheLookupTime => {
1489 append_time!(self.timing.cache_lookup)
1490 },
1491 CtxLogField::CacheLookupTimeHuman => {
1492 append_time!(self.timing.cache_lookup, human)
1493 },
1494 CtxLogField::CacheLockTime => {
1495 append_time!(self.timing.cache_lock)
1496 },
1497 CtxLogField::CacheLockTimeHuman => {
1498 append_time!(self.timing.cache_lock, human)
1499 },
1500 CtxLogField::ServiceTime => {
1501 append_time!(Some(self.timing.created_at.elapsed().as_millis()))
1502 },
1503 CtxLogField::ServiceTimeHuman => {
1504 append_time!(
1505 Some(self.timing.created_at.elapsed().as_millis()),
1506 human
1507 )
1508 },
1509 }
1510 }
1511
1512 pub fn generate_server_timing(&self) -> String {
1520 let mut timing_str = String::with_capacity(200);
1521 let mut first = true;
1523
1524 macro_rules! add_timing {
1526 ($name:expr, $dur:expr) => {
1527 if !first {
1528 timing_str.push_str(", ");
1529 }
1530 let _ = write!(&mut timing_str, "{};dur={}", $name, $dur);
1532 first = false;
1533 };
1534 }
1535
1536 let mut upstream_time = 0;
1538 if let Some(time) = self.get_upstream_connect_time() {
1539 upstream_time += time;
1540 add_timing!("upstream.connect", time);
1541 }
1542 if let Some(time) = self.get_upstream_processing_time() {
1543 upstream_time += time;
1544 add_timing!("upstream.processing", time);
1545 }
1546 if upstream_time > 0 {
1547 add_timing!("upstream", upstream_time);
1548 }
1549
1550 let mut cache_time = 0;
1552 if let Some(time) = self.timing.cache_lookup {
1553 cache_time += time;
1554 add_timing!("cache.lookup", time);
1555 }
1556 if let Some(time) = self.timing.cache_lock {
1557 cache_time += time;
1558 add_timing!("cache.lock", time);
1559 }
1560 if cache_time > 0 {
1561 add_timing!("cache", cache_time);
1562 }
1563
1564 if let Some(features) = &self.features
1566 && let Some(times) = &features.plugin_processing_times
1567 {
1568 let mut plugin_time: u32 = 0;
1569 for (name, time) in times {
1570 if *time == 0 {
1571 continue;
1572 }
1573 plugin_time += time;
1574 if !first {
1577 timing_str.push_str(", ");
1578 }
1579 let _ = write!(&mut timing_str, "plugin.{name};dur={time}");
1580 first = false;
1581 }
1582 if plugin_time > 0 {
1583 add_timing!("plugin", plugin_time);
1584 }
1585 }
1586
1587 let service_time = self.timing.created_at.elapsed().as_millis();
1589 if !first {
1591 timing_str.push_str(", ");
1592 }
1593 let _ = write!(&mut timing_str, "total;dur={}", service_time);
1595
1596 timing_str
1597 }
1598
1599 #[inline]
1601 pub fn push_cache_key(&mut self, key: String) {
1602 let cache_info = self.cache.get_or_insert_default();
1603 cache_info
1604 .keys
1605 .get_or_insert_with(|| Vec::with_capacity(2))
1606 .push(key);
1607 }
1608
1609 #[inline]
1611 pub fn extend_cache_keys(&mut self, keys: Vec<String>) {
1612 let cache_info = self.cache.get_or_insert_default();
1613 cache_info
1614 .keys
1615 .get_or_insert_with(|| Vec::with_capacity(keys.len() + 2))
1616 .extend(keys);
1617 }
1618 #[inline]
1622 pub fn push_cache_key_variant(
1623 &mut self,
1624 current: Vec<String>,
1625 alternatives: Arc<Vec<Vec<String>>>,
1626 ) {
1627 let cache_info = self.cache.get_or_insert_default();
1628 let keys = cache_info
1629 .keys
1630 .get_or_insert_with(|| Vec::with_capacity(current.len() + 2));
1631 cache_info
1632 .key_variants
1633 .get_or_insert_default()
1634 .push(CacheKeyVariant {
1635 at: keys.len(),
1636 len: current.len(),
1637 alternatives,
1638 });
1639 keys.extend(current);
1640 }
1641
1642 pub fn cache_key_alternatives(&self) -> Vec<Vec<String>> {
1648 const MAX_ALTERNATIVES: usize = 1024;
1651 let Some(cache_info) = &self.cache else {
1652 return vec![vec![]];
1653 };
1654 let keys = cache_info.keys.as_deref().unwrap_or_default();
1655 let variants = cache_info.key_variants.as_deref().unwrap_or_default();
1656 let count = variants.iter().try_fold(1usize, |count, variant| {
1657 count.checked_mul(variant.alternatives.len().max(1))
1658 });
1659 if variants.is_empty() {
1660 return vec![keys.to_vec()];
1661 }
1662 if count.is_none_or(|n| n > MAX_ALTERNATIVES) {
1663 tracing::warn!(
1665 target: "pingap::core",
1666 variants = variants.len(),
1667 "too many cache key alternatives, only the key of this request is used"
1668 );
1669 return vec![keys.to_vec()];
1670 }
1671 let mut lists = vec![Vec::with_capacity(keys.len())];
1672 let mut cursor = 0;
1673 for variant in variants {
1674 let fixed = keys.get(cursor..variant.at).unwrap_or_default();
1676 lists = lists
1677 .into_iter()
1678 .flat_map(|list: Vec<String>| {
1679 variant.alternatives.iter().map(move |alternative| {
1680 let mut list = list.clone();
1681 list.extend_from_slice(fixed);
1682 list.extend_from_slice(alternative);
1683 list
1684 })
1685 })
1686 .collect();
1687 cursor = variant.at + variant.len;
1688 }
1689 let rest = keys.get(cursor..).unwrap_or_default();
1690 for list in lists.iter_mut() {
1691 list.extend_from_slice(rest);
1692 }
1693 lists
1694 }
1695
1696 #[inline]
1698 pub fn update_upstream_timing_from_digest(
1699 &mut self,
1700 digest: &Digest,
1701 reused: bool,
1702 ) {
1703 let detail = get_digest_detail(digest);
1704 self.timing.upstream_connection_duration = Some(detail.connection_time);
1705 if reused {
1706 return;
1707 }
1708
1709 if let Some(tcp_connect) = detail.tcp_connect {
1712 self.timing.upstream_tcp_connect = Some(tcp_connect as i32);
1713 self.timing.upstream_tls_handshake =
1714 detail.tls_handshake.map(|value| value as i32);
1715 self.timing.upstream_connect_offload_wait =
1716 detail.connect_offload_wait.map(|value| value as i32);
1717 return;
1718 }
1719
1720 let upstream_connect_time =
1725 self.timing.upstream_connect.unwrap_or_default();
1726 let mut upstream_tcp_connect = upstream_connect_time;
1727 if detail.tls_established > detail.tcp_established {
1728 let latency =
1729 (detail.tls_established - detail.tcp_established) as i32;
1730 upstream_tcp_connect -= latency;
1731 self.timing.upstream_tls_handshake = Some(latency);
1732 }
1733 if upstream_tcp_connect > 0 {
1734 self.timing.upstream_tcp_connect = Some(upstream_tcp_connect);
1735 }
1736 }
1737}
1738
1739fn push_key_host(key: &mut String, host: &str) {
1748 for byte in host.bytes() {
1749 match byte {
1750 b'a'..=b'z'
1751 | b'0'..=b'9'
1752 | b'.'
1753 | b'-'
1754 | b'_'
1755 | b':'
1756 | b'['
1757 | b']' => key.push(byte as char),
1758 b'A'..=b'Z' => key.push(byte.to_ascii_lowercase() as char),
1759 _ => {
1760 let _ = write!(key, "%{byte:02X}");
1761 },
1762 }
1763 }
1764}
1765
1766pub fn get_cache_key(
1785 ctx: &Ctx,
1786 method: &str,
1787 header: &RequestHeader,
1788) -> CacheKey {
1789 let Some(cache_info) = &ctx.cache else {
1790 return CacheKey::new("", "");
1792 };
1793 let namespace = cache_info.namespace.as_ref().map_or("", |v| v);
1794 let host = get_request_host(header).unwrap_or_default();
1796 let path = header.uri.path();
1797 let made;
1801 let query = match (&cache_info.key_query, &cache_info.query_rule) {
1802 (Some(query), _) => (!query.is_empty()).then_some(query.as_str()),
1803 (None, Some(rule)) => {
1804 made = rule.key_query(header.uri.query().unwrap_or_default());
1805 (!made.is_empty()).then_some(made.as_str())
1806 },
1807 (None, None) => header.uri.query(),
1808 };
1809 let header_keys = cache_info.header_key_components(header);
1816 let keys_len = cache_info
1817 .keys
1818 .iter()
1819 .flatten()
1820 .chain(&header_keys)
1821 .map(|s| s.len() + 1)
1822 .sum::<usize>();
1823 let mut key_buf = String::with_capacity(
1824 namespace.len()
1825 + keys_len
1826 + method.len()
1827 + 1
1828 + host.len()
1829 + path.len()
1830 + query.map_or(0, |query| query.len() + 1),
1831 );
1832 key_buf.push_str(namespace);
1833 for k in cache_info.keys.iter().flatten().chain(&header_keys) {
1835 key_buf.push_str(k);
1836 key_buf.push(':');
1837 }
1838 key_buf.push_str(method);
1840 key_buf.push(':');
1841 push_key_host(&mut key_buf, host);
1842 key_buf.push_str(path);
1843 if let Some(query) = query {
1844 key_buf.push('?');
1845 key_buf.push_str(query);
1846 }
1847
1848 CacheKey::new(key_buf, namespace)
1849}
1850
1851#[cfg(test)]
1852mod tests {
1853 use super::*;
1854 use bytes::Bytes;
1855 use bytes::BytesMut;
1856 use http::Uri;
1857 use pingora::cache::key::CacheHashKey;
1858 use pingora::protocols::tls::SslDigest;
1859 use pingora::protocols::tls::SslDigestExtension;
1860 use pretty_assertions::assert_eq;
1861 use std::{sync::Arc, time::Duration};
1862
1863 #[test]
1866 fn test_body_pace() {
1867 let start = Instant::now();
1868 let at = |ms: u64| start + Duration::from_millis(ms);
1869 let ms = |delay: Option<Duration>| delay.map(|d| d.as_millis());
1870
1871 let mut pace = BodyPace::new(1000, 0);
1873 assert_eq!(Some(500), ms(pace.delay_at(500, at(0))));
1874 assert_eq!(Some(500), ms(pace.delay_at(500, at(500))));
1876 assert_eq!(None, ms(pace.delay_at(1000, at(5000))));
1878 assert_eq!(Some(3000), ms(pace.delay_at(3000, at(5000))));
1881
1882 let mut pace = BodyPace::new(1000, 2000);
1884 assert_eq!(None, ms(pace.delay_at(1500, at(0))));
1885 assert_eq!(Some(500), ms(pace.delay_at(1000, at(10))));
1887 assert_eq!(Some(1000), ms(pace.delay_at(1000, at(510))));
1888
1889 let mut pace = BodyPace::new(0, 0);
1891 assert_eq!(Some(2000), ms(pace.delay_at(2, at(0))));
1892 let mut pace = BodyPace::new(1000, 0);
1894 assert_eq!(None, ms(pace.delay_at(0, at(0))));
1895
1896 let mut pace = BodyPace::new(1000, 0);
1898 assert_eq!(None, ms(pace.delay_before_at(2000, at(0))));
1900 assert_eq!(Some(2000), ms(pace.delay_before_at(500, at(0))));
1901 assert_eq!(Some(500), ms(pace.delay_before_at(500, at(2000))));
1903 assert_eq!(None, ms(pace.delay_before_at(500, at(3000))));
1904 assert_eq!(None, ms(pace.delay_before_at(1000, at(9000))));
1907 assert_eq!(Some(1000), ms(pace.delay_before_at(1, at(9000))));
1908 let mut pace = BodyPace::new(1000, 2000);
1910 assert_eq!(None, ms(pace.delay_before_at(1500, at(0))));
1911 assert_eq!(None, ms(pace.delay_before_at(1000, at(0))));
1912 assert_eq!(Some(500), ms(pace.delay_before_at(1000, at(0))));
1913 }
1914
1915 #[test]
1918 fn test_ask_with_key_query() {
1919 let ask = |uri: &str, key_query: Option<&str>| {
1920 let mut header = RequestHeader::build("GET", b"/", None).unwrap();
1921 header.set_uri(uri.parse().unwrap());
1922 let info = CacheInfo {
1923 key_query: key_query.map(str::to_string),
1924 ..Default::default()
1925 };
1926 assert_eq!(true, info.ask_with_key_query(&mut header));
1927 header.uri.to_string()
1928 };
1929 assert_eq!("/a?b=2&a=1", ask("/a?b=2&a=1", None));
1931 assert_eq!(
1933 "/a?a=1&b=2",
1934 ask("/a?b=2&utm_source=x&a=1", Some("a=1&b=2"))
1935 );
1936 assert_eq!("/list", ask("/list?p%61ge=2", Some("")));
1938 assert_eq!("/list", ask("/list?x=1;page=2", Some("")));
1939 assert_eq!(
1940 "/list?size=10",
1941 ask("/list?Page=2&size=10", Some("size=10"))
1942 );
1943 assert_eq!("/a?a=1", ask("/a?a=1", Some("a=1")));
1945 assert_eq!("/a", ask("/a", Some("")));
1946 assert_eq!("/a", ask("/a?", Some("")));
1948
1949 let rule =
1952 Arc::new(CacheQueryRule::Ignore(vec!["utm_source".to_string()]));
1953 let mut info = CacheInfo {
1954 query_rule: Some(rule),
1955 ..Default::default()
1956 };
1957 let mut header = RequestHeader::build("GET", b"/", None).unwrap();
1958 header.set_uri("/a?b=2&utm_source=x&a=1".parse().unwrap());
1959 let mut upstream = header.clone();
1960 assert_eq!(true, info.ask_with_key_query(&mut upstream));
1961 assert_eq!("/a?a=1&b=2", upstream.uri.to_string());
1962 header.set_uri("/a?utm_source=x&a=1".parse().unwrap());
1964 info.settle_key_query(&header);
1965 assert_eq!(Some("a=1".to_string()), info.key_query);
1966 let mut ctx = Ctx {
1967 cache: Some(Box::new(info)),
1968 ..Default::default()
1969 };
1970 assert_eq!(
1971 Some("GET:/a?a=1"),
1972 get_cache_key(&ctx, "GET", &header).primary_key_str()
1973 );
1974 if let Some(info) = ctx.cache.as_mut() {
1976 info.key_query = None;
1977 }
1978 header.set_uri("/a?z=9&utm_source=x&b=1".parse().unwrap());
1979 assert_eq!(
1980 Some("GET:/a?b=1&z=9"),
1981 get_cache_key(&ctx, "GET", &header).primary_key_str()
1982 );
1983 assert_eq!(
1985 "https://example.com/a?a=1",
1986 ask("https://example.com/a?z=9&a=1", Some("a=1"))
1987 );
1988 }
1989
1990 #[test]
1991 fn test_cache_key_alternatives() {
1992 let list = |items: &[&str]| {
1993 items
1994 .iter()
1995 .map(|item| item.to_string())
1996 .collect::<Vec<_>>()
1997 };
1998 let mut ctx = Ctx::default();
1999 assert_eq!(vec![list(&[])], ctx.cache_key_alternatives());
2000
2001 ctx.push_cache_key("fixed".to_string());
2002 assert_eq!(vec![list(&["fixed"])], ctx.cache_key_alternatives());
2003
2004 ctx.push_cache_key_variant(
2005 list(&["gzip"]),
2006 Arc::new(vec![list(&[]), list(&["gzip"]), list(&["br"])]),
2007 );
2008 ctx.push_cache_key("lang".to_string());
2009 ctx.push_cache_key_variant(
2011 list(&[]),
2012 Arc::new(vec![list(&[]), list(&["avif", "webp"])]),
2013 );
2014 assert_eq!(
2015 Some(list(&["fixed", "gzip", "lang"])),
2016 ctx.cache.as_ref().unwrap().keys
2017 );
2018 assert_eq!(
2019 vec![
2020 list(&["fixed", "lang"]),
2021 list(&["fixed", "lang", "avif", "webp"]),
2022 list(&["fixed", "gzip", "lang"]),
2023 list(&["fixed", "gzip", "lang", "avif", "webp"]),
2024 list(&["fixed", "br", "lang"]),
2025 list(&["fixed", "br", "lang", "avif", "webp"]),
2026 ],
2027 ctx.cache_key_alternatives()
2028 );
2029
2030 let many: Vec<_> =
2032 (0..40).map(|index| vec![index.to_string()]).collect();
2033 let mut ctx = Ctx::default();
2034 ctx.push_cache_key_variant(list(&["1"]), Arc::new(many.clone()));
2035 ctx.push_cache_key_variant(list(&["2"]), Arc::new(many));
2036 assert_eq!(vec![list(&["1", "2"])], ctx.cache_key_alternatives());
2037 }
2038
2039 #[test]
2040 fn test_ctx_new() {
2041 let ctx = Ctx::new();
2042 let elapsed_ms = ctx.timing.created_at.elapsed().as_millis();
2045 assert!(elapsed_ms < 100, "created_at should be a recent timestamp");
2046 assert!(ctx.cache.is_none());
2048 assert!(ctx.features.is_none());
2049 assert_eq!(ctx.conn.id, 0);
2050 }
2051
2052 #[test]
2056 fn test_tls_client_cert_log_fields() {
2057 let mut ctx = Ctx::new();
2058 let print = |ctx: &Ctx, key: &str| {
2059 let mut buf = BytesMut::new();
2060 ctx.append_log_value(&mut buf, key);
2061 String::from_utf8_lossy(&buf).to_string()
2062 };
2063 assert_eq!("", print(&ctx, "tls_client_subject"));
2064 assert_eq!("false", print(&ctx, "tls_client_verified"));
2065 ctx.conn.tls_client_cert = Some(Arc::new(TlsClientCert {
2066 subject: "O=Example, CN=device-42".to_string(),
2067 fingerprint: "b545db7a".to_string(),
2068 serial: "3429".to_string(),
2069 }));
2070 assert_eq!(
2071 "O=Example, CN=device-42",
2072 print(&ctx, "tls_client_subject")
2073 );
2074 assert_eq!("b545db7a", print(&ctx, "tls_client_fingerprint"));
2075 assert_eq!("3429", print(&ctx, "tls_client_serial"));
2076 assert_eq!("true", print(&ctx, "tls_client_verified"));
2077 }
2078
2079 #[test]
2080 fn test_plugin_notes() {
2081 let mut ctx = Ctx::new();
2082 assert_eq!(None, ctx.get_plugin_note("a"));
2083 ctx.set_plugin_note("a", "gzip");
2084 ctx.set_plugin_note("b", "");
2085 assert_eq!(Some("gzip"), ctx.get_plugin_note("a"));
2086 assert_eq!(Some(""), ctx.get_plugin_note("b"));
2087 assert_eq!(None, ctx.get_plugin_note("c"));
2088 ctx.set_plugin_note("a", "br");
2090 assert_eq!(Some("br"), ctx.get_plugin_note("a"));
2091 assert_eq!(
2092 2,
2093 ctx.features
2094 .as_ref()
2095 .unwrap()
2096 .plugin_notes
2097 .as_ref()
2098 .unwrap()
2099 .len()
2100 );
2101 }
2102
2103 #[test]
2104 fn test_add_and_get_variable() {
2105 let mut ctx = Ctx::new();
2106 assert!(
2107 ctx.get_variable("key1").is_none(),
2108 "Should be None before adding"
2109 );
2110
2111 ctx.add_variable("key1", "value1");
2112 ctx.add_variable("key2", "value2");
2113
2114 assert_eq!(ctx.get_variable("key1"), Some("value1"));
2115 assert_eq!(ctx.get_variable("key2"), Some("value2"));
2116 assert_eq!(ctx.get_variable("nonexistent"), None);
2117 }
2118
2119 #[test]
2121 fn test_get_time_field() {
2122 let mut ctx = Ctx::new();
2123
2124 ctx.timing.upstream_response = Some(100);
2126 assert_eq!(ctx.get_upstream_response_time(), Some(100));
2127
2128 ctx.timing.upstream_response = Some(-1);
2130 assert_eq!(
2131 ctx.get_upstream_response_time(),
2132 None,
2133 "Time exceeding one hour should be None"
2134 );
2135
2136 ctx.timing.upstream_response = None;
2138 assert_eq!(ctx.get_upstream_response_time(), None);
2139 }
2140
2141 #[test]
2144 fn test_ctx_log_field_names() {
2145 for (name, field) in [
2146 ("connection_id", CtxLogField::ConnectionId),
2147 (
2148 "upstream_tcp_connect_time_human",
2149 CtxLogField::UpstreamTcpConnectTimeHuman,
2150 ),
2151 (
2152 "upstream_connect_offload_wait_time",
2153 CtxLogField::UpstreamConnectOffloadWaitTime,
2154 ),
2155 ("tls_version", CtxLogField::TlsVersion),
2156 ("tls_client_subject", CtxLogField::TlsClientSubject),
2157 ("tls_client_fingerprint", CtxLogField::TlsClientFingerprint),
2158 ("tls_client_serial", CtxLogField::TlsClientSerial),
2159 ("tls_client_verified", CtxLogField::TlsClientVerified),
2160 ("ja4", CtxLogField::Ja4),
2161 ("ja4_r", CtxLogField::Ja4R),
2162 ("ja4_o", CtxLogField::Ja4O),
2163 ("ja4_ro", CtxLogField::Ja4Ro),
2164 ("compression_ratio", CtxLogField::CompressionRatio),
2165 ("service_time_human", CtxLogField::ServiceTimeHuman),
2166 ] {
2167 assert_eq!(Ok(field), name.parse::<CtxLogField>(), "{name}");
2168 assert_eq!(name, field.to_string());
2169 }
2170 assert_eq!(true, "unknown_key".parse::<CtxLogField>().is_err());
2171
2172 let mut ctx = Ctx::new();
2173 ctx.conn.id = 7;
2174 let mut by_name = BytesMut::new();
2175 ctx.append_log_value(&mut by_name, "connection_id");
2176 let mut by_field = BytesMut::new();
2177 ctx.append_log_field(&mut by_field, CtxLogField::ConnectionId);
2178 assert_eq!(b"7", by_name.as_ref());
2179 assert_eq!(by_name, by_field);
2180 }
2181
2182 #[test]
2185 fn test_ja4_log_fields() {
2186 let mut ctx = Ctx::new();
2187 let value = |ctx: &Ctx, key: &str| {
2188 let mut buf = BytesMut::new();
2189 ctx.append_log_value(&mut buf, key);
2190 String::from_utf8(buf.to_vec()).unwrap()
2191 };
2192 assert_eq!("", value(&ctx, "ja4"));
2193 let fingerprint = Ja4Fingerprint::from_client_hello(
2194 &crate::ja4::testing::spec_example_body(),
2195 )
2196 .unwrap();
2197 ctx.conn.ja4 = Some(Arc::new(fingerprint.clone()));
2198 assert_eq!("t13d1516h2_8daaf6152771_e5627efa2ab1", value(&ctx, "ja4"));
2199 assert_eq!(fingerprint.ja4_r(), value(&ctx, "ja4_r"));
2200 assert_eq!(fingerprint.ja4_o(), value(&ctx, "ja4_o"));
2201 assert_eq!(fingerprint.ja4_ro(), value(&ctx, "ja4_ro"));
2202 }
2203
2204 #[test]
2206 fn test_append_log_value_coverage() {
2207 let mut ctx = Ctx::new();
2208 let mut buf = BytesMut::new();
2210 ctx.append_log_value(&mut buf, "unknown_key");
2211 assert!(buf.is_empty(), "Unknown key should not append anything");
2212
2213 buf = BytesMut::new();
2215 ctx.conn.reused = true;
2216 ctx.append_log_value(&mut buf, "connection_reused");
2217 assert_eq!(&buf[..], b"true");
2218
2219 ctx.conn.tls_version = Some("TLSv1.3".into());
2221 buf = BytesMut::new();
2222 ctx.append_log_value(&mut buf, "tls_version");
2223 assert_eq!(&buf[..], b"TLSv1.3");
2224
2225 std::thread::sleep(Duration::from_millis(11));
2227 buf = BytesMut::new();
2228 ctx.append_log_value(&mut buf, "service_time");
2229 let service_time: u64 =
2230 std::str::from_utf8(&buf[..]).unwrap().parse().unwrap();
2231 assert!(service_time >= 10, "Service time should be at least 10ms");
2232 }
2233
2234 #[test]
2236 fn test_get_cache_key() {
2237 let method = "GET";
2238 let mut h2 = RequestHeader::build("GET", b"/", None).unwrap();
2240 h2.set_uri(Uri::from_static("https://example.com/path?a=1"));
2241 let h1 = |host: &str, path: &str| {
2243 let mut header =
2244 RequestHeader::build("GET", path.as_bytes(), None).unwrap();
2245 header.insert_header("Host", host).unwrap();
2246 header
2247 };
2248
2249 let ctx_no_cache = Ctx::new();
2251 let key1 = get_cache_key(&ctx_no_cache, method, &h2);
2252 assert_eq!(key1.user_tag, "");
2253 assert_eq!(key1.primary_key_str(), Some(""));
2254
2255 let mut ctx_with_ns = Ctx::new();
2257 ctx_with_ns.cache = Some(Box::new(CacheInfo {
2258 namespace: Some("my-ns".to_string()),
2259 ..Default::default()
2260 }));
2261 let key2 = get_cache_key(&ctx_with_ns, method, &h2);
2262 assert_eq!(key2.user_tag, "my-ns");
2263 assert_eq!(
2264 key2.primary_key_str(),
2265 Some("my-nsGET:example.com/path?a=1")
2266 );
2267 assert_eq!(key2.primary(), "8fe845e5498004cd81203be9074a8e68");
2271
2272 for host in ["example.com", "Example.COM", "example.com:8443"] {
2275 let key =
2276 get_cache_key(&ctx_with_ns, method, &h1(host, "/path?a=1"));
2277 assert_eq!(key2.primary(), key.primary(), "{host}");
2278 }
2279 let other =
2282 get_cache_key(&ctx_with_ns, method, &h1("other.com", "/path?a=1"));
2283 assert_eq!(
2284 other.primary_key_str(),
2285 Some("my-nsGET:other.com/path?a=1")
2286 );
2287 assert_ne!(key2.primary(), other.primary());
2288 let odd = get_cache_key(
2292 &ctx_with_ns,
2293 method,
2294 &h1("example.com/static", "/app.js"),
2295 );
2296 let plain = get_cache_key(
2297 &ctx_with_ns,
2298 method,
2299 &h1("example.com", "/static/app.js"),
2300 );
2301 assert_eq!(
2302 odd.primary_key_str(),
2303 Some("my-nsGET:example.com%2Fstatic/app.js")
2304 );
2305 assert_ne!(odd.primary(), plain.primary());
2306 let escaped = get_cache_key(
2308 &ctx_with_ns,
2309 method,
2310 &h1("example.com%2Fstatic", "/app.js"),
2311 );
2312 assert_ne!(odd.primary(), escaped.primary());
2313
2314 let bare = RequestHeader::build("GET", b"/path", None).unwrap();
2316 assert_eq!(
2317 get_cache_key(&ctx_with_ns, method, &bare).primary_key_str(),
2318 Some("my-nsGET:/path")
2319 );
2320
2321 let mut ctx_with_keys = Ctx::new();
2323 ctx_with_keys.cache = Some(Box::new(CacheInfo {
2324 namespace: Some("my-ns".to_string()),
2325 keys: Some(vec!["user-123".to_string(), "desktop".to_string()]),
2326 ..Default::default()
2327 }));
2328 let key3 = get_cache_key(&ctx_with_keys, method, &h2);
2329 assert_eq!(key3.user_tag, "my-ns");
2330 assert_eq!(
2331 key3.primary_key_str(),
2332 Some("my-nsuser-123:desktop:GET:example.com/path?a=1")
2333 );
2334 }
2335
2336 #[test]
2339 fn test_generate_server_timing() {
2340 let mut ctx = Ctx::new();
2341 ctx.timing.upstream_connect = Some(1);
2342 ctx.timing.upstream_processing = Some(2);
2343 ctx.timing.cache_lookup = Some(6);
2344 ctx.timing.cache_lock = Some(7);
2345 ctx.add_plugin_processing_time(&Arc::from("plugin1"), 100);
2346
2347 let timing_header = ctx.generate_server_timing();
2348
2349 assert!(timing_header.contains("upstream.connect;dur=1"));
2351 assert!(timing_header.contains("upstream.processing;dur=2"));
2352 assert!(timing_header.contains("upstream;dur=3"));
2353 assert!(timing_header.contains("cache.lookup;dur=6"));
2354 assert!(timing_header.contains("cache.lock;dur=7"));
2355 assert!(timing_header.contains("cache;dur=13"));
2356 assert!(timing_header.contains("plugin.plugin1;dur=100"));
2357 assert!(timing_header.contains("plugin;dur=100"));
2358 assert!(timing_header.contains("total;dur="));
2359 }
2360
2361 #[test]
2362 fn test_format_duration() {
2363 let mut buf = BytesMut::new();
2364 format_duration(&mut buf, (3600 + 3500) * 1000);
2365 assert_eq!(b"1.9h", buf.as_ref());
2366
2367 buf = BytesMut::new();
2368 format_duration(&mut buf, (3600 + 1800) * 1000);
2369 assert_eq!(b"1.5h", buf.as_ref());
2370
2371 buf = BytesMut::new();
2372 format_duration(&mut buf, (3600 + 100) * 1000);
2373 assert_eq!(b"1h", buf.as_ref());
2374
2375 buf = BytesMut::new();
2376 format_duration(&mut buf, (60 + 50) * 1000);
2377 assert_eq!(b"1.8m", buf.as_ref());
2378
2379 buf = BytesMut::new();
2380 format_duration(&mut buf, (60 + 2) * 1000);
2381 assert_eq!(b"1m", buf.as_ref());
2382
2383 buf = BytesMut::new();
2384 format_duration(&mut buf, 1000);
2385 assert_eq!(b"1s", buf.as_ref());
2386
2387 buf = BytesMut::new();
2388 format_duration(&mut buf, 512);
2389 assert_eq!(b"512ms", buf.as_ref());
2390
2391 buf = BytesMut::new();
2392 format_duration(&mut buf, 1112);
2393 assert_eq!(b"1.1s", buf.as_ref());
2394 }
2395
2396 #[test]
2397 fn test_add_variable() {
2398 let mut ctx = Ctx::new();
2399 ctx.add_variable("key1", "value1");
2400 ctx.add_variable("key2", "value2");
2401 ctx.extend_variables(AHashMap::from([
2402 ("key3".to_string(), "value3".to_string()),
2403 ("key4".to_string(), "value4".to_string()),
2404 ]));
2405 let variables =
2406 ctx.features.as_ref().unwrap().variables.as_ref().unwrap();
2407 assert_eq!(variables.get("key1"), Some(&"value1".to_string()));
2410 assert_eq!(variables.get("key2"), Some(&"value2".to_string()));
2411 assert_eq!(variables.get("key3"), Some(&"value3".to_string()));
2412 assert_eq!(variables.get("key4"), Some(&"value4".to_string()));
2413 }
2414
2415 #[test]
2416 fn test_cache_key() {
2417 let mut ctx = Ctx::new();
2418 ctx.push_cache_key("key1".to_string());
2419 ctx.extend_cache_keys(vec!["key2".to_string(), "key3".to_string()]);
2420 assert_eq!(
2421 vec!["key1".to_string(), "key2".to_string(), "key3".to_string()],
2422 ctx.cache.unwrap().keys.unwrap()
2423 );
2424
2425 let mut ctx = Ctx::new();
2426 ctx.cache.get_or_insert_default();
2427 let mut header = RequestHeader::build("GET", b"/", None).unwrap();
2428 header.set_uri(Uri::from_static("https://example.com/path"));
2429 let key = get_cache_key(&ctx, "GET", &header);
2430 assert_eq!(key.user_tag, "");
2431 assert_eq!(key.primary_key_str(), Some("GET:example.com/path"));
2432 }
2433
2434 #[test]
2435 fn test_state() {
2436 let mut ctx = Ctx::new();
2437
2438 let mut buf = BytesMut::new();
2439 ctx.conn.id = 10;
2440 ctx.append_log_value(&mut buf, "connection_id");
2441 assert_eq!(b"10", buf.as_ref());
2442
2443 buf = BytesMut::new();
2444 ctx.append_log_value(&mut buf, "upstream_reused");
2445 assert_eq!(b"false", buf.as_ref());
2446
2447 buf = BytesMut::new();
2448 ctx.upstream.reused = true;
2449 ctx.append_log_value(&mut buf, "upstream_reused");
2450 assert_eq!(b"true", buf.as_ref());
2451
2452 buf = BytesMut::new();
2453 ctx.upstream.address = "192.168.1.1:80".to_string();
2454 ctx.append_log_value(&mut buf, "upstream_addr");
2455 assert_eq!(b"192.168.1.1:80", buf.as_ref());
2456
2457 buf = BytesMut::new();
2458 ctx.upstream.status = Some(StatusCode::CREATED);
2459 ctx.append_log_value(&mut buf, "upstream_status");
2460 assert_eq!(b"201", buf.as_ref());
2461
2462 buf = BytesMut::new();
2463 ctx.state.processing_count = 10;
2464 ctx.append_log_value(&mut buf, "processing");
2465 assert_eq!(b"10", buf.as_ref());
2466
2467 buf = BytesMut::new();
2468 ctx.timing.upstream_connect = Some(1);
2469 ctx.append_log_value(&mut buf, "upstream_connect_time");
2470 assert_eq!(b"1", buf.as_ref());
2471
2472 buf = BytesMut::new();
2473 ctx.append_log_value(&mut buf, "upstream_connect_time_human");
2474 assert_eq!(b"1ms", buf.as_ref());
2475
2476 buf = BytesMut::new();
2477 ctx.upstream.connected_count = Some(30);
2478 ctx.append_log_value(&mut buf, "upstream_connected");
2479 assert_eq!(b"30", buf.as_ref());
2480
2481 buf = BytesMut::new();
2482 ctx.timing.upstream_processing = Some(2);
2483 ctx.append_log_value(&mut buf, "upstream_processing_time");
2484 assert_eq!(b"2", buf.as_ref());
2485
2486 buf = BytesMut::new();
2487 ctx.append_log_value(&mut buf, "upstream_processing_time_human");
2488 assert_eq!(b"2ms", buf.as_ref());
2489
2490 buf = BytesMut::new();
2491 ctx.timing.upstream_response = Some(3);
2492 ctx.append_log_value(&mut buf, "upstream_response_time");
2493 assert_eq!(b"3", buf.as_ref());
2494
2495 buf = BytesMut::new();
2496 ctx.append_log_value(&mut buf, "upstream_response_time_human");
2497 assert_eq!(b"3ms", buf.as_ref());
2498
2499 buf = BytesMut::new();
2500 ctx.timing.upstream_tcp_connect = Some(100);
2501 ctx.append_log_value(&mut buf, "upstream_tcp_connect_time");
2502 assert_eq!(b"100", buf.as_ref());
2503
2504 buf = BytesMut::new();
2505 ctx.append_log_value(&mut buf, "upstream_tcp_connect_time_human");
2506 assert_eq!(b"100ms", buf.as_ref());
2507
2508 buf = BytesMut::new();
2509 ctx.timing.upstream_tls_handshake = Some(110);
2510 ctx.append_log_value(&mut buf, "upstream_tls_handshake_time");
2511 assert_eq!(b"110", buf.as_ref());
2512
2513 buf = BytesMut::new();
2514 ctx.timing.upstream_connect_offload_wait = Some(3);
2515 ctx.append_log_value(&mut buf, "upstream_connect_offload_wait_time");
2516 assert_eq!(b"3", buf.as_ref());
2517 buf = BytesMut::new();
2518 ctx.append_log_value(
2519 &mut buf,
2520 "upstream_connect_offload_wait_time_human",
2521 );
2522 assert_eq!(b"3ms", buf.as_ref());
2523
2524 buf = BytesMut::new();
2525 ctx.append_log_value(&mut buf, "upstream_tls_handshake_time_human");
2526 assert_eq!(b"110ms", buf.as_ref());
2527
2528 buf = BytesMut::new();
2529 ctx.timing.upstream_connection_duration = Some(120);
2530 ctx.append_log_value(&mut buf, "upstream_connection_time");
2531 assert_eq!(b"120", buf.as_ref());
2532
2533 buf = BytesMut::new();
2534 ctx.append_log_value(&mut buf, "upstream_connection_time_human");
2535 assert_eq!(b"120ms", buf.as_ref());
2536
2537 buf = BytesMut::new();
2538 ctx.upstream.location = "pingap".to_string().into();
2539 ctx.append_log_value(&mut buf, "location");
2540 assert_eq!(b"pingap", buf.as_ref());
2541
2542 buf = BytesMut::new();
2543 ctx.timing.connection_duration = 4;
2544 ctx.append_log_value(&mut buf, "connection_time");
2545 assert_eq!(b"4", buf.as_ref());
2546
2547 buf = BytesMut::new();
2548 ctx.append_log_value(&mut buf, "connection_time_human");
2549 assert_eq!(b"4ms", buf.as_ref());
2550
2551 buf = BytesMut::new();
2552 ctx.conn.reused = false;
2553 ctx.append_log_value(&mut buf, "connection_reused");
2554 assert_eq!(b"false", buf.as_ref());
2555
2556 buf = BytesMut::new();
2557 ctx.conn.reused = true;
2558 ctx.append_log_value(&mut buf, "connection_reused");
2559 assert_eq!(b"true", buf.as_ref());
2560
2561 buf = BytesMut::new();
2562 ctx.conn.tls_version = Some("TLSv1.3".into());
2563 ctx.append_log_value(&mut buf, "tls_version");
2564 assert_eq!(b"TLSv1.3", buf.as_ref());
2565
2566 buf = BytesMut::new();
2567 ctx.conn.tls_cipher =
2568 Some("ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".into());
2569 ctx.append_log_value(&mut buf, "tls_cipher");
2570 assert_eq!(b"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", buf.as_ref());
2571
2572 buf = BytesMut::new();
2573 ctx.timing.tls_handshake = Some(101);
2574 ctx.append_log_value(&mut buf, "tls_handshake_time");
2575 assert_eq!(b"101", buf.as_ref());
2576
2577 buf = BytesMut::new();
2578 ctx.append_log_value(&mut buf, "tls_handshake_time_human");
2579 assert_eq!(b"101ms", buf.as_ref());
2580
2581 {
2582 let features = ctx.features.get_or_insert_default();
2583 features.compression_stat = Some(CompressionStat {
2584 in_bytes: 1024,
2585 out_bytes: 500,
2586 duration: Duration::from_millis(5),
2587 ..Default::default()
2588 })
2589 }
2590
2591 buf = BytesMut::new();
2592 ctx.append_log_value(&mut buf, "compression_time");
2593 assert_eq!(b"5", buf.as_ref());
2594
2595 buf = BytesMut::new();
2596 ctx.append_log_value(&mut buf, "compression_time_human");
2597 assert_eq!(b"5ms", buf.as_ref());
2598
2599 buf = BytesMut::new();
2600 ctx.append_log_value(&mut buf, "compression_ratio");
2601 assert_eq!(b"2.0", buf.as_ref());
2602
2603 buf = BytesMut::new();
2604 ctx.timing.cache_lookup = Some(6);
2605 ctx.append_log_value(&mut buf, "cache_lookup_time");
2606 assert_eq!(b"6", buf.as_ref());
2607
2608 buf = BytesMut::new();
2609 ctx.append_log_value(&mut buf, "cache_lookup_time_human");
2610 assert_eq!(b"6ms", buf.as_ref());
2611
2612 buf = BytesMut::new();
2613 ctx.timing.cache_lock = Some(7);
2614 ctx.append_log_value(&mut buf, "cache_lock_time");
2615 assert_eq!(b"7", buf.as_ref());
2616
2617 buf = BytesMut::new();
2618 ctx.append_log_value(&mut buf, "cache_lock_time_human");
2619 assert_eq!(b"7ms", buf.as_ref());
2620 }
2621
2622 #[test]
2623 fn test_add_plugin_processing_time() {
2624 let mut ctx = Ctx::new();
2625 ctx.add_plugin_processing_time(&Arc::from("plugin1"), 100);
2626 ctx.add_plugin_processing_time(&Arc::from("plugin2"), 200);
2627 assert_eq!(
2628 ctx.features.unwrap().plugin_processing_times,
2629 Some(vec![
2630 (Arc::from("plugin1"), 100),
2631 (Arc::from("plugin2"), 200)
2632 ])
2633 );
2634 }
2635
2636 #[test]
2637 fn test_get_digest_detail() {
2638 let mut digest = Digest::default();
2639 let detail = get_digest_detail(&digest);
2640 assert_eq!(detail.connection_reused, false);
2641 assert_eq!(detail.connection_time, 0);
2642 assert_eq!(detail.tcp_established, 0);
2643 assert_eq!(detail.tls_established, 0);
2644 assert_eq!(detail.tls_version, None);
2645 assert_eq!(detail.tls_cipher, None);
2646
2647 digest.timing_digest.push(Some(TimingDigest {
2648 established_ts: SystemTime::UNIX_EPOCH
2649 .checked_add(Duration::from_secs(5))
2650 .unwrap(),
2651 ..Default::default()
2652 }));
2653 digest.timing_digest.push(Some(TimingDigest {
2654 established_ts: SystemTime::UNIX_EPOCH
2655 .checked_add(Duration::from_secs(3))
2656 .unwrap(),
2657 ..Default::default()
2658 }));
2659 digest.ssl_digest = Some(Arc::new(SslDigest {
2660 version: "1.3".into(),
2661 cipher: "123".into(),
2662 organization: Some("cloudflare".to_string()),
2663 serial_number: Some(
2664 "0x00000000000000000000000000000abc".to_string(),
2665 ),
2666 cert_digest: vec![],
2667 extension: SslDigestExtension::default(),
2668 }));
2669 let detail = get_digest_detail(&digest);
2670 assert_eq!(detail.connection_reused, true);
2671 assert_eq!(detail.tcp_established, 5000);
2672 assert_eq!(detail.tls_established, 3000);
2673 assert_eq!(detail.tls_version.as_deref(), Some("1.3"));
2674 assert_eq!(detail.tls_cipher.as_deref(), Some("123"));
2675 assert_eq!(detail.tcp_connect, None);
2677 assert_eq!(detail.tls_handshake, None);
2678
2679 digest.timing_digest = vec![
2681 Some(TimingDigest {
2682 establishment_duration: Some(Duration::from_millis(12)),
2683 ..Default::default()
2684 }),
2685 Some(TimingDigest {
2686 establishment_duration: Some(Duration::from_millis(34)),
2687 ..Default::default()
2688 }),
2689 ];
2690 let detail = get_digest_detail(&digest);
2691 assert_eq!(detail.tcp_connect, Some(12));
2692 assert_eq!(detail.tls_handshake, Some(34));
2693 assert_eq!(detail.connect_offload_wait, None);
2695
2696 digest.timing_digest[0] = Some(TimingDigest {
2698 establishment_duration: Some(Duration::from_millis(12)),
2699 offload_wait_duration: Some(Duration::from_millis(2)),
2700 ..Default::default()
2701 });
2702 let detail = get_digest_detail(&digest);
2703 assert_eq!(detail.connect_offload_wait, Some(2));
2704
2705 digest.ssl_digest = None;
2707 digest.timing_digest.truncate(1);
2708 let detail = get_digest_detail(&digest);
2709 assert_eq!(detail.tcp_connect, Some(12));
2710 assert_eq!(detail.tls_handshake, None);
2711 }
2712
2713 #[test]
2714 fn test_update_upstream_timing_from_digest() {
2715 let measured = |tcp: u64, tls: u64| Digest {
2716 timing_digest: vec![
2717 Some(TimingDigest {
2718 establishment_duration: Some(Duration::from_millis(tcp)),
2719 ..Default::default()
2720 }),
2721 Some(TimingDigest {
2722 establishment_duration: Some(Duration::from_millis(tls)),
2723 ..Default::default()
2724 }),
2725 ],
2726 ssl_digest: Some(Arc::new(SslDigest {
2727 version: "1.3".into(),
2728 cipher: "123".into(),
2729 organization: None,
2730 serial_number: None,
2731 cert_digest: vec![],
2732 extension: SslDigestExtension::default(),
2733 })),
2734 ..Default::default()
2735 };
2736
2737 let mut ctx = Ctx::new();
2740 ctx.timing.upstream_connect = Some(100);
2741 ctx.update_upstream_timing_from_digest(&measured(12, 34), false);
2742 assert_eq!(Some(12), ctx.timing.upstream_tcp_connect);
2743 assert_eq!(Some(34), ctx.timing.upstream_tls_handshake);
2744 assert_eq!(None, ctx.timing.upstream_connect_offload_wait);
2745
2746 let mut ctx = Ctx::new();
2749 let mut offloaded = measured(12, 34);
2750 offloaded.timing_digest[0] = Some(TimingDigest {
2751 establishment_duration: Some(Duration::from_millis(12)),
2752 offload_wait_duration: Some(Duration::from_millis(2)),
2753 ..Default::default()
2754 });
2755 ctx.update_upstream_timing_from_digest(&offloaded, false);
2756 assert_eq!(Some(2), ctx.timing.upstream_connect_offload_wait);
2757
2758 let mut ctx = Ctx::new();
2760 ctx.timing.upstream_connect = Some(100);
2761 ctx.update_upstream_timing_from_digest(&measured(12, 34), true);
2762 assert_eq!(None, ctx.timing.upstream_tcp_connect);
2763 assert_eq!(None, ctx.timing.upstream_tls_handshake);
2764
2765 let mut ctx = Ctx::new();
2768 ctx.timing.upstream_connect = Some(100);
2769 let mut unmeasured = measured(0, 0);
2770 unmeasured.timing_digest = vec![
2771 Some(TimingDigest {
2772 established_ts: SystemTime::UNIX_EPOCH
2773 .checked_add(Duration::from_millis(1_000))
2774 .unwrap(),
2775 ..Default::default()
2776 }),
2777 Some(TimingDigest {
2778 established_ts: SystemTime::UNIX_EPOCH
2779 .checked_add(Duration::from_millis(1_030))
2780 .unwrap(),
2781 ..Default::default()
2782 }),
2783 ];
2784 ctx.update_upstream_timing_from_digest(&unmeasured, false);
2785 assert_eq!(Some(70), ctx.timing.upstream_tcp_connect);
2786 assert_eq!(Some(30), ctx.timing.upstream_tls_handshake);
2787 }
2788
2789 #[test]
2790 fn test_modify_body_handler() {
2791 let mut ctx = Ctx::default();
2792
2793 struct TestHandler {}
2794 impl ModifyResponseBody for TestHandler {
2795 fn handle(
2796 &mut self,
2797 _session: &Session,
2798 body: &mut Option<bytes::Bytes>,
2799 _end_of_stream: bool,
2800 ) -> pingora::Result<()> {
2801 *body = Some(Bytes::from("test"));
2802 Ok(())
2803 }
2804 }
2805
2806 ctx.add_modify_body_handler("test", Box::new(TestHandler {}));
2807 assert_eq!(true, ctx.get_modify_body_handler("test").is_some());
2808 }
2809}