Skip to content

Commit 4429595

Browse files
committed
feat(core): 添加路径处理工具函数
1 parent 137c931 commit 4429595

File tree

8 files changed

+776
-5
lines changed

8 files changed

+776
-5
lines changed

Cargo.lock

Lines changed: 606 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ async-trait = { version = "0.1" }
4444
bytes = { version = "1.10" }
4545
chrono = { version = "0.4" }
4646
cookie = { version = "0.18" }
47+
criterion = { version = "0.5" }
4748
futures = { version = "0.3" }
4849
futures-util = { version = "0.3" }
4950
http = { version = "1.0" }

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Satex
22

3+
[![Build Status](https://github.com/w-sodalite/satex/actions/workflows/rust.yml/badge.svg?branch=master)](https://github.com/w-sodalite/satex/actions/workflows/rust.yml)
4+
[![Crates.io](https://img.shields.io/crates/v/satex)](https://crates.io/crates/satex)
5+
36
一个轻量级、高性能的HTTP网关,使用Rust语言开发,项目灵感来自于`Spring Cloud Gateway`
47

58
> NOTICE(🫡): v0.3版本将以前的代码完全重构,有一些v0.2的功能暂时还未完全实现,目前也在积极开发中。
@@ -30,6 +33,18 @@
3033
| HTTP协议 | [hyper](https://github.com/hyperium/hyper) |
3134
| 中间件 | [tower](https://crates.io/crates/tower) [tower-http](https://crates.io/crates/tower-http) |
3235

36+
## 运行
37+
38+
```shell
39+
# 使用当前目录的satex.yaml文件
40+
cargo run
41+
```
42+
43+
```shell
44+
# 指定配置文件路径
45+
cargo run -- -c ./satex.yaml
46+
```
47+
3348
## 文档
3449

3550
TODO

crates/core/Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,11 @@ serde = { workspace = true, features = ["derive"] }
2222
serde_yaml = { workspace = true }
2323
sync_wrapper = { workspace = true }
2424
tokio = { workspace = true, features = ["rt"] }
25-
tower = { workspace = true, features = ["util"] }
25+
tower = { workspace = true, features = ["util"] }
26+
27+
[dev-dependencies]
28+
criterion = { workspace = true }
29+
30+
[[bench]]
31+
name = "canonicalize"
32+
harness = false
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
2+
use satex_core::util::canonicalize;
3+
4+
fn _bench_canonicalize(c: &mut Criterion) {
5+
c.bench_function("canonicalize", |b| {
6+
b.iter(|| {
7+
let path = canonicalize("/api/v1/resource");
8+
black_box(path)
9+
})
10+
});
11+
12+
c.bench_function("canonicalize_dot1", |b| {
13+
b.iter(|| {
14+
let path = canonicalize("/api/v1/./resource");
15+
black_box(path)
16+
})
17+
});
18+
19+
c.bench_function("canonicalize_dot2", |b| {
20+
b.iter(|| {
21+
let path = canonicalize("/api/v1/../resource");
22+
black_box(path)
23+
})
24+
});
25+
}
26+
27+
criterion_group!(bench_canonicalize, _bench_canonicalize);
28+
29+
criterion_main!(bench_canonicalize);

crates/core/src/util/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
mod clone_service;
22
mod new_type;
3+
mod path;
34
mod response;
45
mod try_downcast;
56
mod with;
67

78
pub use clone_service::SyncBoxCloneService;
9+
pub use path::{canonicalize, remove_end_sep, remove_start_sep};
810
pub use response::*;
911
pub use try_downcast::*;
1012
pub use with::*;

crates/core/src/util/path.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use std::borrow::Cow;
2+
3+
const SEP: &str = "/";
4+
5+
const EMPTY: &str = "";
6+
7+
pub fn canonicalize(path: &str) -> Cow<'_, str> {
8+
if path.is_empty() || path == SEP {
9+
return Cow::from(SEP);
10+
}
11+
let mut legal = true;
12+
let components = path
13+
.split(SEP)
14+
.enumerate()
15+
.filter(|(index, component)| {
16+
if component.is_empty() {
17+
// 如果以/开头则会产生一个空字符串
18+
legal = *index == 0;
19+
false
20+
} else if *component == "." {
21+
legal = false;
22+
false
23+
} else if *component == ".." {
24+
legal = false;
25+
true
26+
} else {
27+
true
28+
}
29+
})
30+
.map(|(_, component)| component)
31+
.collect::<Vec<_>>();
32+
match legal {
33+
true => Cow::from(path),
34+
false => {
35+
let mut parts = Vec::with_capacity(components.len());
36+
for component in components.into_iter() {
37+
if component == ".." {
38+
parts.pop();
39+
} else {
40+
parts.push(component);
41+
}
42+
}
43+
if parts.is_empty() {
44+
Cow::from(SEP)
45+
} else {
46+
parts.insert(0, EMPTY);
47+
parts.join(SEP).into()
48+
}
49+
}
50+
}
51+
}
52+
53+
pub fn remove_start_sep(path: &str) -> &str {
54+
if path.is_empty() {
55+
return path;
56+
}
57+
let index = path
58+
.chars()
59+
.enumerate()
60+
.find(|(_, c)| *c != '/')
61+
.map(|(index, _)| index);
62+
match index {
63+
Some(index) => &path[index..],
64+
None => EMPTY,
65+
}
66+
}
67+
68+
pub fn remove_end_sep(path: &str) -> &str {
69+
if path.is_empty() {
70+
return path;
71+
}
72+
let index = path
73+
.chars()
74+
.rev()
75+
.enumerate()
76+
.find(|(_, c)| *c != '/')
77+
.map(|(index, _)| index);
78+
match index {
79+
Some(index) => &path[..path.len() - index],
80+
None => EMPTY,
81+
}
82+
}

crates/core/tests/path.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use satex_core::util::{canonicalize, remove_end_sep, remove_start_sep};
2+
3+
#[test]
4+
fn test_canonicalize() {
5+
assert_eq!("/api/v1/resource", canonicalize("/api/v1/resource"));
6+
assert_eq!("/api/resource", canonicalize("/api/v1/../resource"));
7+
assert_eq!("/api/v1/resource", canonicalize("/api/v1/./resource"));
8+
assert_eq!("/api/resource", canonicalize("/api/v1/./../resource"));
9+
assert_eq!("/", canonicalize("/.."));
10+
assert_eq!("/", canonicalize("../.."));
11+
assert_eq!("/", canonicalize("/."));
12+
assert_eq!("/", canonicalize("./."));
13+
}
14+
15+
#[test]
16+
fn test_remove_start_sep() {
17+
assert_eq!("api/vi/resource", remove_start_sep("api/vi/resource"));
18+
assert_eq!("api/vi/resource", remove_start_sep("/api/vi/resource"));
19+
assert_eq!("api/vi/resource", remove_start_sep("//api/vi/resource"));
20+
assert_eq!("api/vi/resource", remove_start_sep("///api/vi/resource"));
21+
assert_eq!("", remove_start_sep("///////////////////"));
22+
assert_eq!("a", remove_start_sep("///////////////////a"));
23+
}
24+
25+
#[test]
26+
fn test_remove_end_sep() {
27+
assert_eq!("/api/vi/resource", remove_end_sep("/api/vi/resource"));
28+
assert_eq!("/api/vi/resource", remove_end_sep("/api/vi/resource/"));
29+
assert_eq!("/api/vi/resource", remove_end_sep("/api/vi/resource//"));
30+
assert_eq!("/api/vi/resource", remove_end_sep("/api/vi/resource///"));
31+
assert_eq!("", remove_end_sep("///////////////////"));
32+
assert_eq!("a", remove_end_sep("a///////////////////"));
33+
}

0 commit comments

Comments
 (0)