sozu_lib/router/
mod.rs

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                                // FIXME: the rule order will be important here
90                                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                //FIXME: necessary ti build on stable rust (1.35), can be removed once 1.36 is there
217                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        // _cluster: &Route,
246    ) -> 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/// The cluster to which the traffic will be redirected
591#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
592pub enum Route {
593    /// send a 401 default answer
594    Deny,
595    /// the cluster to which the frontend belongs
596    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    ///  [io]
709    ///      \
710    ///       [sozu]
711    ///             \
712    ///              [*]  <- this wildcard has multiple children
713    ///             /   \
714    ///         (base) (api)
715    #[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    ///  [io]
748    ///      \
749    ///       [sozu]  <- this node has multiple children including a wildcard
750    ///      /      \
751    ///   (api)      [*]  <- this wildcard has multiple children
752    ///                 \
753    ///                (base)
754    #[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}