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官方出品的正则库。

匹配的规则

  1. 字符匹配:正则表达式可以匹配单个字符,如字母、数字、标点符号等。常见的字符匹配包括:
  • \d:匹配任意数字,等价于[0-9]
  • \D:匹配任意非数字字符,等价于[^0-9]
  • \w:匹配任意字母、数字或下划线字符,等价于[A-Za-z0-9_]
  • \W:匹配任意非字母、数字或下划线字符,等价于[^A-Za-z0-9_]
  • .:匹配除换行符(\n、\r)之外的任意字符。
  1. 字符类匹配:使用字符类可以匹配指定范围内的字符。常见的字符类匹配包括:
  • [abc]:匹配方括号内的任意字符,例如abc
  • [^abc]:匹配除方括号内字符之外的任意字符,例如不是abc的字符。
  • [a-z]:匹配任意小写字母。
  • [A-Z]:匹配任意大写字母。
  • [0-9]:匹配任意数字。
  1. 量词匹配:用于指定字符或字符类出现的次数。常见的量词匹配包括:
  • *:匹配前一项0次或多次,等价于{0,}
  • +:匹配前一项1次或多次,等价于{1,}
  • ?:匹配前一项0次或1次,也就是说前一项是可选的,等价于{0,1}
  • {n}:匹配前一项恰好n次。
  • {n,}:匹配前一项至少n次。
  • {n,m}:匹配前一项至少n次,但不超过m次。
  1. 边界匹配:用于匹配字符串的边界位置。常见的边界匹配包括:
  • ^:匹配字符串的开头位置。
  • $:匹配字符串的结尾位置。
  • \b:匹配单词的边界位置,即字与空白间的位置。
  • \B:匹配非单词边界的位置。
  1. 选择、分组和引用:
  • |:选择符号,匹配该符号左边或右边的表达式。
  • (...):将几项组合成一个单元,这个单元可通过"*"、"+"、"?" 和"|" 等符号加以修饰,也可以记住与这个组匹配的字符以便后面引用。
  • \n:在正则表达式中,n 是一个正整数,引用匹配到的第n个分组。
  1. 预查:预查是一种零宽断言,即匹配的是位置而不是字符。预查包括正向预查和负向预查:
  • (?=...):正向肯定预查,表示要匹配的字符串后面必须紧跟着指定的模式。
  • (?!...):正向否定预查,表示要匹配的字符串后面不能紧跟着指定的模式。
  • (?<=...):反向肯定预查,表示要匹配的字符串前面必须紧跟着指定的模式。
  • (?<!...):反向否定预查,表示要匹配的字符串前面不能紧跟着指定的模式。

需求功能

  • 需要从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编写负载均衡及代理,正则及格式替换的更多相关文章

  1. Docker系列-(3) Docker-compose使用与负载均衡

    上一篇文章介绍了docker镜像的制作与发布,本文主要介绍实际docker工程部署中经常用到的docker-compose工具,以及docker的网络配置和负载均衡. Docker-compose介绍 ...

  2. Nginx服务器部署 负载均衡 反向代理

    Nginx服务器部署负载均衡反向代理 LVS Nginx HAProxy的优缺点 三种负载均衡器的优缺点说明如下: LVS的优点: 1.抗负载能力强.工作在第4层仅作分发之用,没有流量的产生,这个特点 ...

  3. [架构]辨析: 高可用 | 集群 | 主从 | 负载均衡 | 反向代理 | 中间件 | 微服务 | 容器 | 云原生 | DevOps | ...

    词汇集 灾备 冷备份 双机热备份 异地容灾备份 云备份 灾难演练 磁盘阵列(RAID) 故障切换 心跳监测 高可用 集群 主从复制(Master-Slave) 多集群横向扩容(master-clust ...

  4. 死磕nginx系列--使用upsync模块实现负载均衡

    问题描述 nginx reload是有一定损耗的,如果你使用的是长连接的话,那么当reload nginx时长连接所有的worker进程会进行优雅退出,并当该worker进程上的所有连接都释放时,进程 ...

  5. hbase源码系列(一)Balancer 负载均衡

    看源码很久了,终于开始动手写博客了,为什么是先写负载均衡呢,因为一个室友入职新公司了,然后他们遇到这方面的问题,某些机器的硬盘使用明显比别的机器要多,每次用hadoop做完负载均衡,很快又变回来了. ...

  6. 架构之Nginx(负载均衡/反向代理)

    Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器 ,也是一个 IMAP/POP3/SMTP 代理 服务器 . Nginx 是由 Igor Sys ...

  7. nginx负载均衡(反向代理)

    6,安装nginx 6.1 依赖库安装  要安装在root根目录里,不要装在虚拟环境里面 yum install gcc-c++ pcre pcre-devel zlib zlib-devel ope ...

  8. nginx域名转发 负载均衡 反向代理

    公司有三台机器在机房,因为IP不够用,肯定要分出来,所以要建立单IP 多域名的反向代理, 就是当请求www.abc.com 跳转到本机, 请求www.bbc.com 跳转到192.168.0.35 机 ...

  9. Nginx HTTP负载均衡/反向代理的相关参数测试

    原文地址:http://www.cnblogs.com/xiaochaohuashengmi/archive/2011/03/15/1984976.html 测试目的 (1)弄清楚HTTP Upstr ...

  10. nginx 负载均衡 反向代理

    nginx 通过方向代理实现负载均衡,负载均衡是大流量网站要做的措施,单从字面上的意思来理解为N台服务器平均分担负载,不会因为某一台服务器负载高宕机而影响用户访问网站,负载均衡至少需要三台服务器, 既 ...

随机推荐

  1. Vue源码学习(八):生命周期调用

    好家伙,   Vue源码学习(七):合并生命周期(混入Vue.Mixin) 书接上回,在上一篇中,我们已经实现了合并生命周期 现在,我们要在我们的初始化过程中,注册生命周期 1.项目目录  红框为本篇 ...

  2. 人工智能AI绘画全攻略(AI绘画教程分享)

    在过去的三个月一直在研究人工智能生成绘画这个方向,3 月份的时候参加了小红书的小航海,也因为这个方向的选择正好对应到了趋势,小红书在一个半月做到了 1 万粉.我为什么看好这个方向? 主要是从三个方面: ...

  3. The method dismissDialog(int) from the type Activity is deprecated

    The method showDialog(int) from the type Activity is deprecated in android?   up vote6down votefavor ...

  4. 【Postman】以命令行形式执行Postman脚本(使用newman)

    以命令行形式执行Postman脚本(使用Newman) 目录 以命令行形式执行Postman脚本(使用Newman) 一.背景 二.Newman的安装 1.Node.js 2.Newman 三.脚本准 ...

  5. Java算法之动态规划

    ①动态规划 动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程.20世纪50年代初,美国数学家贝尔曼(R.Bellman)等人在研究多阶段决策过程的 ...

  6. Gson替换掉多漏洞的FastJson

    添加依赖: <!-- gson --> <dependency> <groupId>com.google.code.gson</groupId> < ...

  7. xxl-job默认accessToken命令执行漏洞复现

    起因: 昨天看见微步发布XXL-JOB默认accessToken身份绕过漏洞,之前hw期间遇到过几次,都没弱口令和未授权,对其有点印象,遂复现一下. 漏洞影响:2.3.1和2.4 环境准备: 1.下载 ...

  8. 3种web会话管理的方式(session)

    阅读目录  https://www.cnblogs.com/lyzg/p/6067766.html 1. 基于server端session的管理 2. cookie-based的管理方式 3. tok ...

  9. DataGrip安装与使用

    写在前面:同学们记得以命令为主,图形界面为辅.了解图形怎么操作即可 一.DataGrip软件的安装和初始化 首先在浏览器上搜索datagrip,然后打开连接 点击DOWNLOAD   按照你电脑系统来 ...

  10. Python输入某年某月某日,判断这一天是这一年的第几天?

    while 1: year = int(input('year:\n')) #输入年.月.日 month = int(input('month:\n')) day = int(input('day:\ ...