rust_web_server/proxy/
status.rs1use crate::header::Header;
11use crate::response::Response;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum ProxyError {
16 DnsError,
17 DestinationUnavailable,
18 DestinationIpUnroutable,
19 ConnectionRefused,
20 ConnectionTerminated,
21 ConnectionTimeout,
22 TlsProtocolError,
23 TlsCertificateError,
24 HttpResponseTimeout,
25 HttpResponseIncomplete,
26 HttpProtocolError,
27 ProxyConfigurationError,
28 ProxyInternalError,
29}
30
31impl ProxyError {
32 pub fn as_str(self) -> &'static str {
34 match self {
35 ProxyError::DnsError => "dns_error",
36 ProxyError::DestinationUnavailable => "destination_unavailable",
37 ProxyError::DestinationIpUnroutable => "destination_ip_unroutable",
38 ProxyError::ConnectionRefused => "connection_refused",
39 ProxyError::ConnectionTerminated => "connection_terminated",
40 ProxyError::ConnectionTimeout => "connection_timeout",
41 ProxyError::TlsProtocolError => "tls_protocol_error",
42 ProxyError::TlsCertificateError => "tls_certificate_error",
43 ProxyError::HttpResponseTimeout => "http_response_timeout",
44 ProxyError::HttpResponseIncomplete => "http_response_incomplete",
45 ProxyError::HttpProtocolError => "http_protocol_error",
46 ProxyError::ProxyConfigurationError => "proxy_configuration_error",
47 ProxyError::ProxyInternalError => "proxy_internal_error",
48 }
49 }
50
51 pub fn classify(failure: &str) -> ProxyError {
53 let f = failure.to_ascii_lowercase();
54 let has = |s: &str| f.contains(s);
55 if has("dns lookup") || has("no address resolved") || has("failed to lookup address") {
56 ProxyError::DnsError
57 } else if has("invalid upstream") || has("invalid tls server name") || has("cannot read") || has("cannot parse") || has("need the http2") {
58 ProxyError::ProxyConfigurationError
59 } else if has("tls") || has("handshake") || has("certificate") {
60 if has("certificate") || has("unknownissuer") || has("notvalidforname") || has("expired") {
61 ProxyError::TlsCertificateError
62 } else if has("timed out") {
63 ProxyError::ConnectionTimeout
64 } else {
65 ProxyError::TlsProtocolError
66 }
67 } else if has("connect to") || has("connect(") {
68 if has("timed out") {
69 ProxyError::ConnectionTimeout
70 } else if has("unreachable") {
71 ProxyError::DestinationIpUnroutable
72 } else {
73 ProxyError::ConnectionRefused
74 }
75 } else if has("mid-body") || has("premature eof") {
76 ProxyError::HttpResponseIncomplete
77 } else if has("timed out") || has("would block") || has("temporarily unavailable") {
78 ProxyError::HttpResponseTimeout
79 } else if has("closed connection") || has("reset") || has("broken pipe") || has("write to backend") || has("aborted") {
80 ProxyError::ConnectionTerminated
81 } else if has("interim") || has("parse") || has("invalid") || has("malformed") || has("chunk") || has("status line") {
82 ProxyError::HttpProtocolError
83 } else {
84 ProxyError::ProxyInternalError
85 }
86 }
87}
88
89fn mode() -> (bool, bool) {
91 let config = crate::server_config::ServerConfig::current();
92 match config.proxy_status.as_str() {
93 "off" => (false, false),
94 "errors" => (false, true),
95 _ => (true, true),
96 }
97}
98
99pub fn received(response: &mut Response, next_hop: Option<&str>) {
101 if mode().0 {
102 let status = response.status_code;
103 add(response, &format!("received-status={status}"), next_hop, None);
104 }
105}
106
107pub fn failed(response: &mut Response, error: ProxyError, next_hop: Option<&str>, details: Option<&str>) {
109 if mode().1 {
110 add(response, &format!("error={}", error.as_str()), next_hop, details);
111 }
112}
113
114pub fn failed_with(response: &mut Response, failure: &str, next_hop: Option<&str>) {
116 failed(response, ProxyError::classify(failure), next_hop, Some(failure));
117}
118
119fn add(response: &mut Response, parameter: &str, next_hop: Option<&str>, details: Option<&str>) {
120 let config = crate::server_config::ServerConfig::current();
121 let mut member = format!("{}; {parameter}", crate::header::sf::item(&config.proxy_name));
122 if config.proxy_status_next_hop {
123 if let Some(hop) = next_hop.filter(|h| !h.is_empty()) {
124 member.push_str(&format!("; next-hop={}", crate::header::sf::string(hop)));
125 }
126 if let Some(details) = details.filter(|d| !d.is_empty()) {
127 member.push_str(&format!("; details={}", crate::header::sf::string(details)));
128 }
129 }
130 match response.headers.iter_mut().find(|h| h.name.eq_ignore_ascii_case("proxy-status")) {
132 Some(existing) => existing.value = format!("{}, {member}", existing.value).into(),
133 None => response.headers.push(Header::new("Proxy-Status", member)),
134 }
135}
136
137#[cfg(test)]
138mod tests;