sozu_lib/router/
mod.rs

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                                // FIXME: the rule order will be important here
91                                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                //FIXME: necessary ti build on stable rust (1.35), can be removed once 1.36 is there
218                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        // _cluster: &Route,
249    ) -> 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/// The cluster to which the traffic will be redirected
594#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
595pub enum Route {
596    /// send a 401 default answer
597    Deny,
598    /// the cluster to which the frontend belongs
599    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    ///  [io]
704    ///      \
705    ///       [sozu]
706    ///             \
707    ///              [*]  <- this wildcard has multiple children
708    ///             /   \
709    ///         (base) (api)
710    #[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    ///  [io]
743    ///      \
744    ///       [sozu]  <- this node has multiple children including a wildcard
745    ///      /      \
746    ///   (api)      [*]  <- this wildcard has multiple children
747    ///                 \
748    ///                (base)
749    #[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}