pingap_upstream/
hash_strategy.rs1use pingap_core::{
16 get_client_ip, get_cookie_value, get_query_value, get_req_header_value,
17};
18use pingora::proxy::Session;
19use std::borrow::Cow;
20
21#[derive(PartialEq)]
23pub enum HashStrategy {
24 Url,
25 Ip,
26 Header(String),
27 Cookie(String),
28 Query(String),
29 Path, }
31
32impl HashStrategy {
33 pub fn get_value<'a>(
36 &self,
37 session: &'a Session,
38 client_ip: &'a Option<String>,
39 ) -> Cow<'a, str> {
40 match self {
41 HashStrategy::Url => {
42 Cow::Owned(session.req_header().uri.to_string())
43 },
44 HashStrategy::Ip => {
45 if let Some(ip) = client_ip {
46 Cow::Borrowed(ip)
47 } else {
48 Cow::Owned(get_client_ip(session))
49 }
50 },
51 HashStrategy::Header(key) => {
52 get_req_header_value(session.req_header(), key)
53 .map(Cow::Borrowed)
54 .unwrap_or(Cow::Borrowed(""))
55 },
56 HashStrategy::Cookie(key) => {
57 get_cookie_value(session.req_header(), key)
58 .map(Cow::Borrowed)
59 .unwrap_or(Cow::Borrowed(""))
60 },
61 HashStrategy::Query(key) => {
62 get_query_value(session.req_header(), key)
63 .map(Cow::Borrowed)
64 .unwrap_or(Cow::Borrowed(""))
65 },
66 HashStrategy::Path => {
67 Cow::Borrowed(session.req_header().uri.path())
68 },
69 }
70 }
71}
72
73impl From<(&str, &str)> for HashStrategy {
74 fn from(tuple: (&str, &str)) -> Self {
75 match tuple.0 {
76 "url" => HashStrategy::Url,
77 "ip" => HashStrategy::Ip,
78 "header" => HashStrategy::Header(tuple.1.to_string()),
79 "cookie" => HashStrategy::Cookie(tuple.1.to_string()),
80 "query" => HashStrategy::Query(tuple.1.to_string()),
81 _ => HashStrategy::Path,
82 }
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::HashStrategy;
89 use pingora::proxy::Session;
90 use pretty_assertions::assert_eq;
91 use tokio_test::io::Builder;
92
93 #[test]
94 fn test_new_hash_strategy() {
95 assert!(HashStrategy::Url == HashStrategy::from(("url", "")));
96 assert!(HashStrategy::Ip == HashStrategy::from(("ip", "")));
97 assert!(
98 HashStrategy::Header("User-Agent".to_string())
99 == HashStrategy::from(("header", "User-Agent"))
100 );
101 assert!(
102 HashStrategy::Cookie("deviceId".to_string())
103 == HashStrategy::from(("cookie", "deviceId"))
104 );
105 assert!(
106 HashStrategy::Query("id".to_string())
107 == HashStrategy::from(("query", "id"))
108 );
109 assert!(HashStrategy::Path == HashStrategy::from(("", "")));
110 }
111
112 #[tokio::test]
113 async fn test_get_hash_key_value() {
114 let headers = [
115 "Host: github.com",
116 "Referer: https://github.com/",
117 "User-Agent: pingap/0.1.1",
118 "Cookie: deviceId=abc",
119 "Accept: application/json",
120 "X-Forwarded-For: 1.1.1.1",
121 ]
122 .join("\r\n");
123 let input_header = format!(
124 "GET /vicanso/pingap?id=1234 HTTP/1.1\r\n{headers}\r\n\r\n"
125 );
126 let mock_io = Builder::new().read(input_header.as_bytes()).build();
127
128 let mut session = Session::new_h1(Box::new(mock_io));
129 session.read_request().await.unwrap();
130
131 assert_eq!(
132 "/vicanso/pingap?id=1234",
133 HashStrategy::Url.get_value(&session, &None)
134 );
135
136 assert_eq!("1.1.1.1", HashStrategy::Ip.get_value(&session, &None));
137 assert_eq!(
138 "2.2.2.2",
139 HashStrategy::Ip.get_value(&session, &Some("2.2.2.2".to_string()))
140 );
141
142 assert_eq!(
143 "pingap/0.1.1",
144 HashStrategy::Header("User-Agent".to_string())
145 .get_value(&session, &None)
146 );
147
148 assert_eq!(
149 "abc",
150 HashStrategy::Cookie("deviceId".to_string())
151 .get_value(&session, &None)
152 );
153 assert_eq!(
154 "1234",
155 HashStrategy::Query("id".to_string()).get_value(&session, &None)
156 );
157 assert_eq!(
158 "/vicanso/pingap",
159 HashStrategy::Path.get_value(&session, &None)
160 );
161 }
162}