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