1pub mod pattern_trie;
2
3use std::{str::from_utf8, time::Instant};
4
5use regex::bytes::Regex;
6
7use sozu_command::{
8 proto::command::{PathRule as CommandPathRule, PathRuleKind, RulePosition},
9 response::HttpFrontend,
10 state::ClusterId,
11};
12
13use crate::{protocol::http::parser::Method, router::pattern_trie::TrieNode};
14
15#[derive(thiserror::Error, Debug, PartialEq)]
16pub enum RouterError {
17 #[error("Could not parse rule from frontend path {0:?}")]
18 InvalidPathRule(String),
19 #[error("parsing hostname {hostname} failed")]
20 InvalidDomain { hostname: String },
21 #[error("Could not add route {0}")]
22 AddRoute(String),
23 #[error("Could not remove route {0}")]
24 RemoveRoute(String),
25 #[error("no route for {method} {host} {path}")]
26 RouteNotFound {
27 host: String,
28 path: String,
29 method: Method,
30 },
31}
32
33pub struct Router {
34 pre: Vec<(DomainRule, PathRule, MethodRule, Route)>,
35 pub tree: TrieNode<Vec<(PathRule, MethodRule, Route)>>,
36 post: Vec<(DomainRule, PathRule, MethodRule, Route)>,
37}
38
39impl Default for Router {
40 fn default() -> Self {
41 Self::new()
42 }
43}
44
45impl Router {
46 pub fn new() -> Router {
47 Router {
48 pre: Vec::new(),
49 tree: TrieNode::root(),
50 post: Vec::new(),
51 }
52 }
53
54 pub fn lookup(
55 &self,
56 hostname: &str,
57 path: &str,
58 method: &Method,
59 ) -> Result<Route, RouterError> {
60 let hostname_b = hostname.as_bytes();
61 let path_b = path.as_bytes();
62 for (domain_rule, path_rule, method_rule, cluster_id) in &self.pre {
63 if domain_rule.matches(hostname_b)
64 && path_rule.matches(path_b) != PathRuleResult::None
65 && method_rule.matches(method) != MethodRuleResult::None
66 {
67 return Ok(cluster_id.clone());
68 }
69 }
70
71 if let Some((_, path_rules)) = self.tree.lookup(hostname_b, true) {
72 let mut prefix_length = 0;
73 let mut route = None;
74
75 for (rule, method_rule, cluster_id) in path_rules {
76 match rule.matches(path_b) {
77 PathRuleResult::Regex | PathRuleResult::Equals => {
78 match method_rule.matches(method) {
79 MethodRuleResult::Equals => return Ok(cluster_id.clone()),
80 MethodRuleResult::All => {
81 prefix_length = path_b.len();
82 route = Some(cluster_id);
83 }
84 MethodRuleResult::None => {}
85 }
86 }
87 PathRuleResult::Prefix(size) => {
88 if size >= prefix_length {
89 match method_rule.matches(method) {
90 MethodRuleResult::Equals => {
92 prefix_length = size;
93 route = Some(cluster_id);
94 }
95 MethodRuleResult::All => {
96 prefix_length = size;
97 route = Some(cluster_id);
98 }
99 MethodRuleResult::None => {}
100 }
101 }
102 }
103 PathRuleResult::None => {}
104 }
105 }
106
107 if let Some(cluster_id) = route {
108 return Ok(cluster_id.clone());
109 }
110 }
111
112 for (domain_rule, path_rule, method_rule, cluster_id) in self.post.iter() {
113 if domain_rule.matches(hostname_b)
114 && path_rule.matches(path_b) != PathRuleResult::None
115 && method_rule.matches(method) != MethodRuleResult::None
116 {
117 return Ok(cluster_id.clone());
118 }
119 }
120
121 Err(RouterError::RouteNotFound {
122 host: hostname.to_owned(),
123 path: path.to_owned(),
124 method: method.to_owned(),
125 })
126 }
127
128 pub fn add_http_front(&mut self, front: &HttpFrontend) -> Result<(), RouterError> {
129 let path_rule = PathRule::from_config(front.path.clone())
130 .ok_or(RouterError::InvalidPathRule(front.path.to_string()))?;
131
132 let method_rule = MethodRule::new(front.method.clone());
133
134 let route = match &front.cluster_id {
135 Some(cluster_id) => Route::ClusterId(cluster_id.clone()),
136 None => Route::Deny,
137 };
138
139 let success = match front.position {
140 RulePosition::Pre => {
141 let domain = front.hostname.parse::<DomainRule>().map_err(|_| {
142 RouterError::InvalidDomain {
143 hostname: front.hostname.clone(),
144 }
145 })?;
146
147 self.add_pre_rule(&domain, &path_rule, &method_rule, &route)
148 }
149 RulePosition::Post => {
150 let domain = front.hostname.parse::<DomainRule>().map_err(|_| {
151 RouterError::InvalidDomain {
152 hostname: front.hostname.clone(),
153 }
154 })?;
155
156 self.add_post_rule(&domain, &path_rule, &method_rule, &route)
157 }
158 RulePosition::Tree => {
159 self.add_tree_rule(front.hostname.as_bytes(), &path_rule, &method_rule, &route)
160 }
161 };
162 if !success {
163 return Err(RouterError::AddRoute(format!("{:?}", front)));
164 }
165 Ok(())
166 }
167
168 pub fn remove_http_front(&mut self, front: &HttpFrontend) -> Result<(), RouterError> {
169 let path_rule = PathRule::from_config(front.path.clone())
170 .ok_or(RouterError::InvalidPathRule(front.path.to_string()))?;
171
172 let method_rule = MethodRule::new(front.method.clone());
173
174 let remove_success = match front.position {
175 RulePosition::Pre => {
176 let domain = front.hostname.parse::<DomainRule>().map_err(|_| {
177 RouterError::InvalidDomain {
178 hostname: front.hostname.clone(),
179 }
180 })?;
181
182 self.remove_pre_rule(&domain, &path_rule, &method_rule)
183 }
184 RulePosition::Post => {
185 let domain = front.hostname.parse::<DomainRule>().map_err(|_| {
186 RouterError::InvalidDomain {
187 hostname: front.hostname.clone(),
188 }
189 })?;
190
191 self.remove_post_rule(&domain, &path_rule, &method_rule)
192 }
193 RulePosition::Tree => {
194 self.remove_tree_rule(front.hostname.as_bytes(), &path_rule, &method_rule)
195 }
196 };
197 if !remove_success {
198 return Err(RouterError::RemoveRoute(format!("{:?}", front)));
199 }
200 Ok(())
201 }
202
203 pub fn add_tree_rule(
204 &mut self,
205 hostname: &[u8],
206 path: &PathRule,
207 method: &MethodRule,
208 cluster: &Route,
209 ) -> bool {
210 let hostname = match from_utf8(hostname) {
211 Err(_) => return false,
212 Ok(h) => h,
213 };
214
215 match ::idna::domain_to_ascii(hostname) {
216 Ok(hostname) => {
217 let mut empty = true;
219 if let Some((_, ref mut paths)) =
220 self.tree.domain_lookup_mut(hostname.as_bytes(), false)
221 {
222 empty = false;
223 if !paths.iter().any(|(p, m, _)| p == path && m == method) {
224 paths.push((path.to_owned(), method.to_owned(), cluster.to_owned()));
225 return true;
226 }
227 }
228
229 if empty {
230 self.tree.domain_insert(
231 hostname.into_bytes(),
232 vec![(path.to_owned(), method.to_owned(), cluster.to_owned())],
233 );
234 return true;
235 }
236
237 false
238 }
239 Err(_) => false,
240 }
241 }
242
243 pub fn remove_tree_rule(
244 &mut self,
245 hostname: &[u8],
246 path: &PathRule,
247 method: &MethodRule,
248 ) -> bool {
250 let hostname = match from_utf8(hostname) {
251 Err(_) => return false,
252 Ok(h) => h,
253 };
254
255 match ::idna::domain_to_ascii(hostname) {
256 Ok(hostname) => {
257 let should_delete = {
258 let paths_opt = self.tree.domain_lookup_mut(hostname.as_bytes(), false);
259
260 if let Some((_, paths)) = paths_opt {
261 paths.retain(|(p, m, _)| p != path || m != method);
262 }
263
264 paths_opt
265 .as_ref()
266 .map(|(_, paths)| paths.is_empty())
267 .unwrap_or(false)
268 };
269
270 if should_delete {
271 self.tree.domain_remove(&hostname.into_bytes());
272 }
273
274 true
275 }
276 Err(_) => false,
277 }
278 }
279
280 pub fn add_pre_rule(
281 &mut self,
282 domain: &DomainRule,
283 path: &PathRule,
284 method: &MethodRule,
285 cluster_id: &Route,
286 ) -> bool {
287 if !self
288 .pre
289 .iter()
290 .any(|(d, p, m, _)| d == domain && p == path && m == method)
291 {
292 self.pre.push((
293 domain.to_owned(),
294 path.to_owned(),
295 method.to_owned(),
296 cluster_id.to_owned(),
297 ));
298 true
299 } else {
300 false
301 }
302 }
303
304 pub fn add_post_rule(
305 &mut self,
306 domain: &DomainRule,
307 path: &PathRule,
308 method: &MethodRule,
309 cluster_id: &Route,
310 ) -> bool {
311 if !self
312 .post
313 .iter()
314 .any(|(d, p, m, _)| d == domain && p == path && m == method)
315 {
316 self.post.push((
317 domain.to_owned(),
318 path.to_owned(),
319 method.to_owned(),
320 cluster_id.to_owned(),
321 ));
322 true
323 } else {
324 false
325 }
326 }
327
328 pub fn remove_pre_rule(
329 &mut self,
330 domain: &DomainRule,
331 path: &PathRule,
332 method: &MethodRule,
333 ) -> bool {
334 match self
335 .pre
336 .iter()
337 .position(|(d, p, m, _)| d == domain && p == path && m == method)
338 {
339 None => false,
340 Some(index) => {
341 self.pre.remove(index);
342 true
343 }
344 }
345 }
346
347 pub fn remove_post_rule(
348 &mut self,
349 domain: &DomainRule,
350 path: &PathRule,
351 method: &MethodRule,
352 ) -> bool {
353 match self
354 .post
355 .iter()
356 .position(|(d, p, m, _)| d == domain && p == path && m == method)
357 {
358 None => false,
359 Some(index) => {
360 self.post.remove(index);
361 true
362 }
363 }
364 }
365}
366
367#[derive(Clone, Debug)]
368pub enum DomainRule {
369 Any,
370 Exact(String),
371 Wildcard(String),
372 Regex(Regex),
373}
374
375fn convert_regex_domain_rule(hostname: &str) -> Option<String> {
376 let mut result = String::new();
377
378 let s = hostname.as_bytes();
379 let mut index = 0;
380 loop {
381 if s[index] == b'/' {
382 let mut found = false;
383 for i in index + 1..s.len() {
384 if s[i] == b'/' {
385 match std::str::from_utf8(&s[index + 1..i]) {
386 Ok(r) => result.push_str(r),
387 Err(_) => return None,
388 }
389 index = i + 1;
390 found = true;
391 }
392 }
393
394 if !found {
395 return None;
396 }
397 } else {
398 let start = index;
399 for i in start..s.len() + 1 {
400 index = i;
401 if i < s.len() && s[i] == b'.' {
402 match std::str::from_utf8(&s[start..i]) {
403 Ok(r) => result.push_str(r),
404 Err(_) => return None,
405 }
406 break;
407 }
408 }
409 if index == s.len() {
410 match std::str::from_utf8(&s[start..]) {
411 Ok(r) => result.push_str(r),
412 Err(_) => return None,
413 }
414 }
415 }
416
417 if index == s.len() {
418 return Some(result);
419 } else if s[index] == b'.' {
420 result.push_str("\\.");
421 index += 1;
422 } else {
423 return None;
424 }
425 }
426}
427
428impl DomainRule {
429 pub fn matches(&self, hostname: &[u8]) -> bool {
430 match self {
431 DomainRule::Any => true,
432 DomainRule::Wildcard(s) => {
433 let len_without_suffix = hostname.len() - s.len() + 1;
434 hostname.ends_with(s[1..].as_bytes())
435 && !&hostname[..len_without_suffix].contains(&b'.')
436 }
437 DomainRule::Exact(s) => s.as_bytes() == hostname,
438 DomainRule::Regex(r) => {
439 let start = Instant::now();
440 let is_a_match = r.is_match(hostname);
441 let now = Instant::now();
442 time!("regex_matching_time", (now - start).as_millis());
443 is_a_match
444 }
445 }
446 }
447}
448
449impl std::cmp::PartialEq for DomainRule {
450 fn eq(&self, other: &Self) -> bool {
451 match (self, other) {
452 (DomainRule::Any, DomainRule::Any) => true,
453 (DomainRule::Wildcard(s1), DomainRule::Wildcard(s2)) => s1 == s2,
454 (DomainRule::Exact(s1), DomainRule::Exact(s2)) => s1 == s2,
455 (DomainRule::Regex(r1), DomainRule::Regex(r2)) => r1.as_str() == r2.as_str(),
456 _ => false,
457 }
458 }
459}
460
461impl std::str::FromStr for DomainRule {
462 type Err = ();
463
464 fn from_str(s: &str) -> Result<Self, Self::Err> {
465 Ok(if s == "*" {
466 DomainRule::Any
467 } else if s.contains('/') {
468 match convert_regex_domain_rule(s) {
469 Some(s) => match regex::bytes::Regex::new(&s) {
470 Ok(r) => DomainRule::Regex(r),
471 Err(_) => return Err(()),
472 },
473 None => return Err(()),
474 }
475 } else if s.contains('*') {
476 if s.starts_with('*') {
477 match ::idna::domain_to_ascii(s) {
478 Ok(r) => DomainRule::Wildcard(r),
479 Err(_) => return Err(()),
480 }
481 } else {
482 return Err(());
483 }
484 } else {
485 match ::idna::domain_to_ascii(s) {
486 Ok(r) => DomainRule::Exact(r),
487 Err(_) => return Err(()),
488 }
489 })
490 }
491}
492
493#[derive(Clone, Debug)]
494pub enum PathRule {
495 Prefix(String),
496 Regex(Regex),
497 Equals(String),
498}
499
500#[derive(PartialEq, Eq)]
501pub enum PathRuleResult {
502 Regex,
503 Prefix(usize),
504 Equals,
505 None,
506}
507
508impl PathRule {
509 pub fn matches(&self, path: &[u8]) -> PathRuleResult {
510 match self {
511 PathRule::Prefix(prefix) => {
512 if path.starts_with(prefix.as_bytes()) {
513 PathRuleResult::Prefix(prefix.len())
514 } else {
515 PathRuleResult::None
516 }
517 }
518 PathRule::Regex(regex) => {
519 let start = Instant::now();
520 let is_a_match = regex.is_match(path);
521 let now = Instant::now();
522 time!("regex_matching_time", (now - start).as_millis());
523
524 if is_a_match {
525 PathRuleResult::Regex
526 } else {
527 PathRuleResult::None
528 }
529 }
530 PathRule::Equals(pattern) => {
531 if path == pattern.as_bytes() {
532 PathRuleResult::Equals
533 } else {
534 PathRuleResult::None
535 }
536 }
537 }
538 }
539
540 pub fn from_config(rule: CommandPathRule) -> Option<Self> {
541 match PathRuleKind::try_from(rule.kind) {
542 Ok(PathRuleKind::Prefix) => Some(PathRule::Prefix(rule.value)),
543 Ok(PathRuleKind::Regex) => Regex::new(&rule.value).ok().map(PathRule::Regex),
544 Ok(PathRuleKind::Equals) => Some(PathRule::Equals(rule.value)),
545 Err(_) => None,
546 }
547 }
548}
549
550impl std::cmp::PartialEq for PathRule {
551 fn eq(&self, other: &Self) -> bool {
552 match (self, other) {
553 (PathRule::Prefix(s1), PathRule::Prefix(s2)) => s1 == s2,
554 (PathRule::Regex(r1), PathRule::Regex(r2)) => r1.as_str() == r2.as_str(),
555 _ => false,
556 }
557 }
558}
559
560#[derive(Clone, Debug, PartialEq, Eq)]
561pub struct MethodRule {
562 pub inner: Option<Method>,
563}
564
565#[derive(PartialEq, Eq)]
566pub enum MethodRuleResult {
567 All,
568 Equals,
569 None,
570}
571
572impl MethodRule {
573 pub fn new(method: Option<String>) -> Self {
574 MethodRule {
575 inner: method.map(|s| Method::new(s.as_bytes())),
576 }
577 }
578
579 pub fn matches(&self, method: &Method) -> MethodRuleResult {
580 match self.inner {
581 None => MethodRuleResult::All,
582 Some(ref m) => {
583 if method == m {
584 MethodRuleResult::Equals
585 } else {
586 MethodRuleResult::None
587 }
588 }
589 }
590 }
591}
592
593#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
595pub enum Route {
596 Deny,
598 ClusterId(ClusterId),
600}
601
602#[cfg(test)]
603mod tests {
604 use super::*;
605
606 #[test]
607 fn convert_regex() {
608 assert_eq!(
609 convert_regex_domain_rule("www.example.com")
610 .unwrap()
611 .as_str(),
612 "www\\.example\\.com"
613 );
614 assert_eq!(
615 convert_regex_domain_rule("*.example.com").unwrap().as_str(),
616 "*\\.example\\.com"
617 );
618 assert_eq!(
619 convert_regex_domain_rule("test.*.example.com")
620 .unwrap()
621 .as_str(),
622 "test\\.*\\.example\\.com"
623 );
624 assert_eq!(
625 convert_regex_domain_rule("css./cdn[a-z0-9]+/.example.com")
626 .unwrap()
627 .as_str(),
628 "css\\.cdn[a-z0-9]+\\.example\\.com"
629 );
630
631 assert_eq!(
632 convert_regex_domain_rule("css./cdn[a-z0-9]+.example.com"),
633 None
634 );
635 assert_eq!(
636 convert_regex_domain_rule("css./cdn[a-z0-9]+/a.example.com"),
637 None
638 );
639 }
640
641 #[test]
642 fn parse_domain_rule() {
643 assert_eq!("*".parse::<DomainRule>().unwrap(), DomainRule::Any);
644 assert_eq!(
645 "www.example.com".parse::<DomainRule>().unwrap(),
646 DomainRule::Exact("www.example.com".to_string())
647 );
648 assert_eq!(
649 "*.example.com".parse::<DomainRule>().unwrap(),
650 DomainRule::Wildcard("*.example.com".to_string())
651 );
652 assert_eq!("test.*.example.com".parse::<DomainRule>(), Err(()));
653 assert_eq!(
654 "/cdn[0-9]+/.example.com".parse::<DomainRule>().unwrap(),
655 DomainRule::Regex(Regex::new("cdn[0-9]+\\.example\\.com").unwrap())
656 );
657 }
658
659 #[test]
660 fn match_domain_rule() {
661 assert!(DomainRule::Any.matches("www.example.com".as_bytes()));
662 assert!(
663 DomainRule::Exact("www.example.com".to_string()).matches("www.example.com".as_bytes())
664 );
665 assert!(
666 DomainRule::Wildcard("*.example.com".to_string()).matches("www.example.com".as_bytes())
667 );
668 assert!(!DomainRule::Wildcard("*.example.com".to_string())
669 .matches("test.www.example.com".as_bytes()));
670 assert!("/cdn[0-9]+/.example.com"
671 .parse::<DomainRule>()
672 .unwrap()
673 .matches("cdn1.example.com".as_bytes()));
674 assert!(!"/cdn[0-9]+/.example.com"
675 .parse::<DomainRule>()
676 .unwrap()
677 .matches("www.example.com".as_bytes()));
678 assert!(!"/cdn[0-9]+/.example.com"
679 .parse::<DomainRule>()
680 .unwrap()
681 .matches("cdn10.exampleAcom".as_bytes()));
682 }
683
684 #[test]
685 fn match_path_rule() {
686 assert!(PathRule::Prefix("".to_string()).matches("/".as_bytes()) != PathRuleResult::None);
687 assert!(
688 PathRule::Prefix("".to_string()).matches("/hello".as_bytes()) != PathRuleResult::None
689 );
690 assert!(
691 PathRule::Prefix("/hello".to_string()).matches("/hello".as_bytes())
692 != PathRuleResult::None
693 );
694 assert!(
695 PathRule::Prefix("/hello".to_string()).matches("/hello/world".as_bytes())
696 != PathRuleResult::None
697 );
698 assert!(
699 PathRule::Prefix("/hello".to_string()).matches("/".as_bytes()) == PathRuleResult::None
700 );
701 }
702
703 #[test]
711 fn multiple_children_on_a_wildcard() {
712 let mut router = Router::new();
713
714 assert!(router.add_tree_rule(
715 b"*.sozu.io",
716 &PathRule::Prefix("".to_string()),
717 &MethodRule::new(Some("GET".to_string())),
718 &Route::ClusterId("base".to_string())
719 ));
720 println!("{:#?}", router.tree);
721 assert_eq!(
722 router.lookup("www.sozu.io", "/api", &Method::Get),
723 Ok(Route::ClusterId("base".to_string()))
724 );
725 assert!(router.add_tree_rule(
726 b"*.sozu.io",
727 &PathRule::Prefix("/api".to_string()),
728 &MethodRule::new(Some("GET".to_string())),
729 &Route::ClusterId("api".to_string())
730 ));
731 println!("{:#?}", router.tree);
732 assert_eq!(
733 router.lookup("www.sozu.io", "/ap", &Method::Get),
734 Ok(Route::ClusterId("base".to_string()))
735 );
736 assert_eq!(
737 router.lookup("www.sozu.io", "/api", &Method::Get),
738 Ok(Route::ClusterId("api".to_string()))
739 );
740 }
741
742 #[test]
750 fn multiple_children_including_one_with_wildcard() {
751 let mut router = Router::new();
752
753 assert!(router.add_tree_rule(
754 b"*.sozu.io",
755 &PathRule::Prefix("".to_string()),
756 &MethodRule::new(Some("GET".to_string())),
757 &Route::ClusterId("base".to_string())
758 ));
759 println!("{:#?}", router.tree);
760 assert_eq!(
761 router.lookup("www.sozu.io", "/api", &Method::Get),
762 Ok(Route::ClusterId("base".to_string()))
763 );
764 assert!(router.add_tree_rule(
765 b"api.sozu.io",
766 &PathRule::Prefix("".to_string()),
767 &MethodRule::new(Some("GET".to_string())),
768 &Route::ClusterId("api".to_string())
769 ));
770 println!("{:#?}", router.tree);
771 assert_eq!(
772 router.lookup("www.sozu.io", "/api", &Method::Get),
773 Ok(Route::ClusterId("base".to_string()))
774 );
775 assert_eq!(
776 router.lookup("api.sozu.io", "/api", &Method::Get),
777 Ok(Route::ClusterId("api".to_string()))
778 );
779 }
780
781 #[test]
782 fn router_insert_remove_through_regex() {
783 let mut router = Router::new();
784
785 assert!(router.add_tree_rule(
786 b"www./.*/.io",
787 &PathRule::Prefix("".to_string()),
788 &MethodRule::new(Some("GET".to_string())),
789 &Route::ClusterId("base".to_string())
790 ));
791 println!("{:#?}", router.tree);
792 assert!(router.add_tree_rule(
793 b"www.doc./.*/.io",
794 &PathRule::Prefix("".to_string()),
795 &MethodRule::new(Some("GET".to_string())),
796 &Route::ClusterId("doc".to_string())
797 ));
798 println!("{:#?}", router.tree);
799 assert_eq!(
800 router.lookup("www.sozu.io", "/", &Method::Get),
801 Ok(Route::ClusterId("base".to_string()))
802 );
803 assert_eq!(
804 router.lookup("www.doc.sozu.io", "/", &Method::Get),
805 Ok(Route::ClusterId("doc".to_string()))
806 );
807 assert!(router.remove_tree_rule(
808 b"www./.*/.io",
809 &PathRule::Prefix("".to_string()),
810 &MethodRule::new(Some("GET".to_string()))
811 ));
812 println!("{:#?}", router.tree);
813 assert!(router.lookup("www.sozu.io", "/", &Method::Get).is_err());
814 assert_eq!(
815 router.lookup("www.doc.sozu.io", "/", &Method::Get),
816 Ok(Route::ClusterId("doc".to_string()))
817 );
818 }
819
820 #[test]
821 fn match_router() {
822 let mut router = Router::new();
823
824 assert!(router.add_pre_rule(
825 &"*".parse::<DomainRule>().unwrap(),
826 &PathRule::Prefix("/.well-known/acme-challenge".to_string()),
827 &MethodRule::new(Some("GET".to_string())),
828 &Route::ClusterId("acme".to_string())
829 ));
830 assert!(router.add_tree_rule(
831 "www.example.com".as_bytes(),
832 &PathRule::Prefix("/".to_string()),
833 &MethodRule::new(Some("GET".to_string())),
834 &Route::ClusterId("example".to_string())
835 ));
836 assert!(router.add_tree_rule(
837 "*.test.example.com".as_bytes(),
838 &PathRule::Regex(Regex::new("/hello[A-Z]+/").unwrap()),
839 &MethodRule::new(Some("GET".to_string())),
840 &Route::ClusterId("examplewildcard".to_string())
841 ));
842 assert!(router.add_tree_rule(
843 "/test[0-9]/.example.com".as_bytes(),
844 &PathRule::Prefix("/".to_string()),
845 &MethodRule::new(Some("GET".to_string())),
846 &Route::ClusterId("exampleregex".to_string())
847 ));
848
849 assert_eq!(
850 router.lookup("www.example.com", "/helloA", &Method::new(&b"GET"[..])),
851 Ok(Route::ClusterId("example".to_string()))
852 );
853 assert_eq!(
854 router.lookup(
855 "www.example.com",
856 "/.well-known/acme-challenge",
857 &Method::new(&b"GET"[..])
858 ),
859 Ok(Route::ClusterId("acme".to_string()))
860 );
861 assert!(router
862 .lookup("www.test.example.com", "/", &Method::new(&b"GET"[..]))
863 .is_err());
864 assert_eq!(
865 router.lookup(
866 "www.test.example.com",
867 "/helloAB/",
868 &Method::new(&b"GET"[..])
869 ),
870 Ok(Route::ClusterId("examplewildcard".to_string()))
871 );
872 assert_eq!(
873 router.lookup("test1.example.com", "/helloAB/", &Method::new(&b"GET"[..])),
874 Ok(Route::ClusterId("exampleregex".to_string()))
875 );
876 }
877}