39. 干货系列从零用Rust编写负载均衡及代理,正则及格式替换
wmproxy
wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子
项目地址
国内: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
项目设计目标
利用正则替换的能力,能把指定的字符串替换成想要的字符串。
正则库
因为rust官方团队并未将正则正式的加入到std标准库里面,目前我们引用的是regex也是rust-lang官方出品的正则库。
匹配的规则
- 字符匹配:正则表达式可以匹配单个字符,如字母、数字、标点符号等。常见的字符匹配包括:
\d:匹配任意数字,等价于[0-9]。\D:匹配任意非数字字符,等价于[^0-9]。\w:匹配任意字母、数字或下划线字符,等价于[A-Za-z0-9_]。\W:匹配任意非字母、数字或下划线字符,等价于[^A-Za-z0-9_]。.:匹配除换行符(\n、\r)之外的任意字符。
- 字符类匹配:使用字符类可以匹配指定范围内的字符。常见的字符类匹配包括:
[abc]:匹配方括号内的任意字符,例如a、b或c。[^abc]:匹配除方括号内字符之外的任意字符,例如不是a、b或c的字符。[a-z]:匹配任意小写字母。[A-Z]:匹配任意大写字母。[0-9]:匹配任意数字。
- 量词匹配:用于指定字符或字符类出现的次数。常见的量词匹配包括:
*:匹配前一项0次或多次,等价于{0,}。+:匹配前一项1次或多次,等价于{1,}。?:匹配前一项0次或1次,也就是说前一项是可选的,等价于{0,1}。{n}:匹配前一项恰好n次。{n,}:匹配前一项至少n次。{n,m}:匹配前一项至少n次,但不超过m次。
- 边界匹配:用于匹配字符串的边界位置。常见的边界匹配包括:
^:匹配字符串的开头位置。$:匹配字符串的结尾位置。\b:匹配单词的边界位置,即字与空白间的位置。\B:匹配非单词边界的位置。
- 选择、分组和引用:
|:选择符号,匹配该符号左边或右边的表达式。(...):将几项组合成一个单元,这个单元可通过"*"、"+"、"?" 和"|" 等符号加以修饰,也可以记住与这个组匹配的字符以便后面引用。\n:在正则表达式中,n 是一个正整数,引用匹配到的第n个分组。
- 预查:预查是一种零宽断言,即匹配的是位置而不是字符。预查包括正向预查和负向预查:
(?=...):正向肯定预查,表示要匹配的字符串后面必须紧跟着指定的模式。(?!...):正向否定预查,表示要匹配的字符串后面不能紧跟着指定的模式。(?<=...):反向肯定预查,表示要匹配的字符串前面必须紧跟着指定的模式。(?<!...):反向否定预查,表示要匹配的字符串前面不能紧跟着指定的模式。
需求功能
需要从
Request中获取Url或者Path并将其中间的某一部分替换成另一部分,且需兼容部分不需要替换,两种模式需均能正常的。需要配置中正确的读取分割的信息,如
"{path}/ '/ro(\\w+)/(.*) {path} /ro$1/Cargo.toml' /root/README.md"需要正确的分割成{path}/,/ro(\\w+)/(.*) {path} /ro$1/Cargo.toml,/root/README.md三个部分。
需求实现
以下是一段try_paths的配置
[[http.server.location]]
rate_limit = "4m/s"
rule = "/root"
file_server = { browse = true }
proxy_pass = ""
try_paths = "{path}/ '/ro(\\w+)/(.*) {path} /ro$1/Cargo.toml' /root/README.md"
我们需要将try_paths做正确的拆分,我们需要将一个字符串按空格做分割,且如果有单引号'或者双引号"需要找到其对应的结尾,防止将其中的字符串做切割。
我们将利用以下正则,其中小括号括起来是我们将匹配的内容,用|则表示并行的匹配规则,当第一个没有匹配到将匹配第二个选项。
r#"([^\s'"]+)|"([^"]*)"|'([^']*)'"#
其中([^\s'"]+)表示非空白字符开头也非单号双引号开头,直到碰到空格或者单引号双引号停止,那我们将获取第一个匹配项{path}/
其中"([^"]*)"则表示以双引号开头,中间不能添加任何双引号的其它任意字符,直到匹配到另一个双引号停止,取中间的数据,不取双引号。'([^']*)'则类似双引号,那么'/ro(\\w+)/(.*) {path} /ro$1/Cargo.toml'将是单引号开头的匹配项直到另一个单引号截止,那么匹配的结果为/ro(\\w+)/(.*) {path} /ro$1/Cargo.toml
另一个匹配第一个规则/root/README.md,此时我们已经正确将数据进行切割,以下是源码实现,用lazy_static是为了只初始化一次,无需重复耗性能。
pub fn split_by_whitespace<'a>(key: &'a str) -> Vec<&'a str> {
lazy_static! {
static ref RE: Regex = Regex::new(r#"([^\s'"]+)|"([^"]*)"|'([^']*)'"#).unwrap();
};
let mut vals = vec![];
for (_, [value]) in RE.captures_iter(key).map(|c| c.extract()) {
vals.push(value);
}
vals
}
以下是正确的替换,假设我们收到的是
GET /root/index.html HTTP/1.1\r\n
那么第一个参数{path}/将通过Request取得为/root/index.html/
那么第二个参数/ro(\\w+)/(.*) {path} /ro$1/Cargo.toml,按空格切割,切割结果有/ro(\\w+)/(.*),{path},/ro$1/Cargo.toml有三个参数,且第一个参数为正则,那么我们尝试将第一个参数正则化,与第二个参数相匹配,并替换成第三个参数的内容同时将第二个参数格式化为/root/index.html,那么与正则相匹配
| 匹配项 | 匹配结果 |
|---|---|
| $0 | /root/index.html 正则表达示里0均为整个字符串 |
| $1 | ot (\w+)的内容 |
| $2 | index.html (.*)的内容 |
那么/ro$1/Cargo.toml替换成结果后将为/root/Cargo.toml通过这种方法我们可以将任意的字符串按照一定的规则匹配成另一个字符串来达到自定义的目的。
源码实现:
pub fn format_req(req: &Request<Body>, formats: &str) -> String {
let pw = FORMAT_PATTERN_CACHE.with(|m| {
if !m.borrow().contains_key(&formats) {
let p = PatternEncoder::new(formats);
m.borrow_mut().insert(
Box::leak(formats.to_string().clone().into_boxed_str()),
Arc::new(p),
);
}
m.borrow()[&formats].clone()
});
// 将其转化成Record然后进行encode
let record = ProxyRecord::new_req(Record::builder().level(Level::Info).build(), req);
let mut buf = vec![];
pw.encode(&mut SimpleWriter(&mut buf), &record).unwrap();
String::from_utf8_lossy(&buf[..]).to_string()
}
fn inner_oper_regex(req: &Request<Body>, re: &Regex, vals: &[&str]) -> String {
let mut ret = String::new();
let key = Self::format_req(req, vals[0]);
for idx in 1..vals.len() {
if idx != 1 {
ret += " ";
}
let val = re.replace_all(&key, vals[idx]);
ret += &val;
}
ret
}
pub fn format_req_may_regex(req: &Request<Body>, formats: &str) -> String {
let formats = formats.trim();
if formats.contains(char::is_whitespace) {
// 因为均是从配置中读取的数据, 在这里缓存正则表达示会在总量上受到配置的限制
lazy_static! {
static ref RE_CACHES: Mutex<HashMap<&'static str, Regex>> =
Mutex::new(HashMap::new());
};
if formats.len() == 0 {
return String::new();
}
let vals = Self::split_by_whitespace(formats);
if vals.len() < 2 {
return String::new();
}
if let Ok(mut guard) = RE_CACHES.lock() {
if let Some(re) = guard.get(&vals[1]) {
return Self::inner_oper_regex(req, re, &vals[1..]);
} else {
if let Ok(re) = Regex::new(vals[0]) {
let ret = Self::inner_oper_regex(req, &re, &vals[1..]);
guard.insert(Box::leak(vals[0].to_string().into_boxed_str()), re);
return ret;
}
}
}
}
Self::format_req(req, formats)
}
测试用例
根据Request生成我们任意想要的内容
mod tests {
use webparse::Request;
use wenmeng::Body;
use crate::Helper;
fn build_request() -> Request<Body> {
Request::builder()
.url("http://127.0.0.1/test/root?query=1&a=b")
.header("Accept", "text/html")
.body("ok")
.unwrap()
.into_type()
}
#[test]
fn do_test_reg() {
let req = &build_request();
let format = r" /test/(.*) {path} /formal/$1 ";
let val = Helper::format_req_may_regex(req, format);
assert_eq!(val, "/formal/root");
let format = r" /te(\w+)/(.*) {path} /formal/$1/$2 ";
let val = Helper::format_req_may_regex(req, format);
assert_eq!(val, "/formal/st/root");
let format = r" /te(\w+)/(.*) {url} /formal/$1/$2 ";
let val = Helper::format_req_may_regex(req, format);
assert_eq!(val, "http://127.0.0.1/formal/st/root?query=1&a=b");
}
}
小结
正则在计算机的处理中是非常的常用的一种技术,具有许多优点,使得它在文本处理和模式匹配方面非常强大和灵活,有强大的文本匹配和搜索功能,跨平台性跨语言,每种语言都有相应的实现,既简洁又高效便捷,是受欢迎的一种又相处较难的字符串处理技术。
点击 [关注],[在看],[点赞] 是对作者最大的支持
39. 干货系列从零用Rust编写负载均衡及代理,正则及格式替换的更多相关文章
- Docker系列-(3) Docker-compose使用与负载均衡
上一篇文章介绍了docker镜像的制作与发布,本文主要介绍实际docker工程部署中经常用到的docker-compose工具,以及docker的网络配置和负载均衡. Docker-compose介绍 ...
- Nginx服务器部署 负载均衡 反向代理
Nginx服务器部署负载均衡反向代理 LVS Nginx HAProxy的优缺点 三种负载均衡器的优缺点说明如下: LVS的优点: 1.抗负载能力强.工作在第4层仅作分发之用,没有流量的产生,这个特点 ...
- [架构]辨析: 高可用 | 集群 | 主从 | 负载均衡 | 反向代理 | 中间件 | 微服务 | 容器 | 云原生 | DevOps | ...
词汇集 灾备 冷备份 双机热备份 异地容灾备份 云备份 灾难演练 磁盘阵列(RAID) 故障切换 心跳监测 高可用 集群 主从复制(Master-Slave) 多集群横向扩容(master-clust ...
- 死磕nginx系列--使用upsync模块实现负载均衡
问题描述 nginx reload是有一定损耗的,如果你使用的是长连接的话,那么当reload nginx时长连接所有的worker进程会进行优雅退出,并当该worker进程上的所有连接都释放时,进程 ...
- hbase源码系列(一)Balancer 负载均衡
看源码很久了,终于开始动手写博客了,为什么是先写负载均衡呢,因为一个室友入职新公司了,然后他们遇到这方面的问题,某些机器的硬盘使用明显比别的机器要多,每次用hadoop做完负载均衡,很快又变回来了. ...
- 架构之Nginx(负载均衡/反向代理)
Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器 ,也是一个 IMAP/POP3/SMTP 代理 服务器 . Nginx 是由 Igor Sys ...
- nginx负载均衡(反向代理)
6,安装nginx 6.1 依赖库安装 要安装在root根目录里,不要装在虚拟环境里面 yum install gcc-c++ pcre pcre-devel zlib zlib-devel ope ...
- nginx域名转发 负载均衡 反向代理
公司有三台机器在机房,因为IP不够用,肯定要分出来,所以要建立单IP 多域名的反向代理, 就是当请求www.abc.com 跳转到本机, 请求www.bbc.com 跳转到192.168.0.35 机 ...
- Nginx HTTP负载均衡/反向代理的相关参数测试
原文地址:http://www.cnblogs.com/xiaochaohuashengmi/archive/2011/03/15/1984976.html 测试目的 (1)弄清楚HTTP Upstr ...
- nginx 负载均衡 反向代理
nginx 通过方向代理实现负载均衡,负载均衡是大流量网站要做的措施,单从字面上的意思来理解为N台服务器平均分担负载,不会因为某一台服务器负载高宕机而影响用户访问网站,负载均衡至少需要三台服务器, 既 ...
随机推荐
- vue3 + mark.js | 实现文字标注功能
页面效果 具体实现 新增 1.监听鼠标抬起事件,通过window.getSelection()方法获取鼠标用户选择的文本范围或光标的当前位置. 2.通过 选中的文字长度是否大于0或window.get ...
- k8s集群部署初体验
目录 ██ 环境准备[所有节点] ██ 安装Docker/kubeadm/kubelet[所有节点] ██ 部署 k8s master ██ 部署 k8s node ██ 部署网络插件[CNI] ██ ...
- Keycloak 创建和修改自定义用户信息
前言 公司在用 Keycloak 作为认证服务器,之前在系统数据库里存的,后来想了想是不是可以在 Keycloak 中存.在网上找的方法大多都是通过 admin 接口去改,但这种方法就需要两种解决方案 ...
- DeepSpeed框架:1-大纲和资料梳理
DeepSpeed是一个深度学习优化软件套件,使分布式训练和推理变得简单.高效和有效.它可以做些什么呢?训练/推理具有数十亿或数万亿参数的密集或稀疏模型:实现出色的系统吞吐量并有效扩展到数千个GP ...
- 【干货】前端开发VUE实例
最近公司招聘前端开发----VUE方向. 技术面试是必不可少的,发现大多数人应该没有掌握其开发技术,今天就大概总结一下 一.准备工作 需要用到VSCODE最新版,nodejs,vue2.0(现在vue ...
- 【分段传输】c#使用IAsyncEnumerable实现流式分段传输
引言 在使用SSE的时候,前端可以实现流式传输,但是有个问题就是这是一个独占的连接,相当于如果你不手动关闭连接,就会一直请求,一直连接调用接口,而且发送的数据格式也是按照定义好的协议来,而使用c#自带 ...
- ExtJS的使用方法汇总(1)——配置和表格控件使用
在网上差一些关于ExtJS的相关资料,看到这篇博客写的不错,拿出来分享一下! 博客文章:ExtJS的使用方法汇总(1)--配置和表格控件使用 ExtJS的使用方法汇总(2)- ...
- Codeforces Round #706 (Div. 2) A-D题解
写在前边 链接:Codeforces Round #706 (Div. 2) \(A,B,C,D\),这场有点简单,不过由于A写炸了后边题连看都没看就溜了,就从上大分变成了掉大分 A. Split i ...
- 最小生成树(Kruskal Prim)
最小生成树 (克鲁斯卡尔算法) Kruskal 给定一个n个点m条边的无向图,图中可能存在重边和自环,边权可能为负数. 求最小生成树的树边权重之和,如果最小生成树不存在则输出impossible. 给 ...
- SQLBI_精通DAX课程笔记_03_计算列
计算列是由DAX在表中生成的列,逐行计算并储存在模式之中. 以下链接是采悟老师关于度量值和计算列的区别的文章,可以同步查看. https://zhuanlan.zhihu.com/p/75462046 ...