ToB企服应用市场:ToB评测及商务社交产业平台

标题: 47从零开始用Rust编写nginx,配对还有这么多要求!负载均衡中的路径匹配 [打印本页]

作者: 王海鱼    时间: 2024-4-18 01:45
标题: 47从零开始用Rust编写nginx,配对还有这么多要求!负载均衡中的路径匹配
wmproxy

wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 负载均衡, 静态文件服务器,websocket代理,四层TCP/UDP转发,内网穿透等,会将实现过程分享出来,感兴趣的可以一起造个轮子
项目地址

国内: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
设计目标

负载均衡时通过匹配规则匹配正确的location进行处理相关的操作。
设计方案变更

初始设计方案

初始方案以最快的方式进行支持,仅支持前缀匹配,即如果配置
  1. [[http.server.location]]
  2. rule = "/wmproxy"
复制代码
那么当我们访问/wmproxy/xx时将会被分配到该location,此方案相对简单,但是当我们碰到复杂的需求时将无法被满足。
设计方案需求

除了前缀匹配外,我们将会有其它各种需求的匹配:
设计方案迭代

当前我们就必须将数据进行更迭,但是在通常情况下我们又不想将配置变得复杂,此时就需要我们支持更多的类的自定义化,首先我们定义类:
  1. /// location匹配,将根据该类的匹配信息进行是否匹配
  2. #[serde_as]
  3. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
  4. pub struct Matcher {
  5.     path: Option<String>,
  6.     #[serde_as(as = "Option<DisplayFromStr>")]
  7.     client_ip: Option<IpSets>,
  8.     #[serde_as(as = "Option<DisplayFromStr>")]
  9.     remote_ip: Option<IpSets>,
  10.     host: Option<String>,
  11.     #[serde_as(as = "Option<DisplayFromStr>")]
  12.     method: Option<MatchMethod>,
  13.     #[serde_as(as = "Option<DisplayFromStr>")]
  14.     scheme: Option<MatchScheme>,
  15. }
复制代码
此时我们将location中的rule的类型从String变成了Matcher,那么此时我们首先遇到的一个问题他可能为一个String值或者可能为一个Map值,我们先得对这种情况进行处理。
我们根据serde的提供的解析方案进行如下函数,当前我们重写了visit_str及visit_map表示我们将只支持这两种源格式转化成Matcher
  1. pub fn string_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error>
  2. where
  3.     T: Deserialize<'de> + FromStr<Err = WebError>,
  4.     D: Deserializer<'de>,
  5. {
  6.     struct StringOrStruct<T>(PhantomData<fn() -> T>);
  7.     impl<'de, T> Visitor<'de> for StringOrStruct<T>
  8.     where
  9.         T: Deserialize<'de> + FromStr<Err = WebError>,
  10.     {
  11.         type Value = T;
  12.         fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
  13.             formatter.write_str("string or map")
  14.         }
  15.         fn visit_str<E>(self, value: &str) -> Result<T, E>
  16.         where
  17.             E: de::Error,
  18.         {
  19.             Ok(FromStr::from_str(value).unwrap())
  20.         }
  21.         fn visit_map<M>(self, map: M) -> Result<T, M::Error>
  22.         where
  23.             M: MapAccess<'de>,
  24.         {
  25.             Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
  26.         }
  27.     }
  28.     deserializer.deserialize_any(StringOrStruct(PhantomData))
  29. }
复制代码
此处我们用到了static变量,也就是将某部分数据进行了静态化处理,且此处我们将String转化成了&'static str可能存在一定的内存泄漏,大小值跟配置的数据有关,可以接受这空间换取时间。然后用正则的is_match进行匹配即可。
  1. /// 负载均衡中的location匹配,将匹配合适的处理逻辑
  2. #[serde_as]
  3. #[derive(Debug, Clone, Serialize, Deserialize)]
  4. pub struct LocationConfig {
  5.     #[serde(deserialize_with = "string_or_struct")]
  6.     pub rule: Matcher,
  7.     //...
  8. }
复制代码
  1. /// may memory leak
  2. pub fn try_cache_regex(origin: &str) -> Option<Regex> {
  3.     // 因为均是从配置中读取的数据, 在这里缓存正则表达示会在总量上受到配置的限制
  4.     lazy_static! {
  5.         static ref RE_CACHES: Mutex<HashMap<&'static str, Option<Regex>>> =
  6.             Mutex::new(HashMap::new());
  7.     };
  8.     if origin.len() == 0 {
  9.         return None;
  10.     }
  11.     if let Ok(mut guard) = RE_CACHES.lock() {
  12.         if let Some(re) = guard.get(origin) {
  13.             return re.clone();
  14.         } else {
  15.             if let Ok(re) = Regex::new(origin) {
  16.                 guard.insert(
  17.                     Box::leak(origin.to_string().into_boxed_str()),
  18.                     Some(re.clone()),
  19.                 );
  20.                 return Some(re);
  21.             }
  22.         }
  23.     }
  24.     return None;
  25. }
复制代码
以上各数据均引用src的资源,即在这过程中并没有创建内存对象。
那么匹配函数则先将'*'进行分割,数组的第一个则前缀匹配,最后一个则后缀匹配,若不存在'*'则数组数量为1,符合前缀匹配。
  1. if let Some(re) = Helper::try_cache_regex(&p) {
  2.     if !re.is_match(path) {
  3.         return Ok(false);
  4.     }
  5. }
复制代码
那么完整的匹配函数在Matcher
  1. let src = "wmproxy is good";
  2. let first = &src[..7];
  3. let second = &src[3..8];
  4. let end = &src[8..];
  5. let vals = src.split(" ").collect::<Vec<&str>>();
复制代码
小结

匹配规则在对于复杂匹配的时候尤为重要,我们可以轻松的将各个请求分配到合适的位置,此处我们着重介绍了正则匹配及带*的路径匹配。
点击 [关注][在看][点赞] 是对作者最大的支持

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4