通過`FromStr`及`Display`的重定義,我們可以支持更強大的自定義的序列化操作,系統綁定埠既認埠號也認綁定IP,所以我們可以對同個埠進行多次綁定。 ...
wmproxy
wmproxy
已用Rust
實現http/https
代理, socks5
代理, 反向代理, 負載均衡, 靜態文件伺服器,websocket
代理,四層TCP/UDP轉發,內網穿透等,會將實現過程分享出來,感興趣的可以一起造個輪子
項目地址
國內: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
設計目標
快速的設置多IP綁定,及IP埠段的支持,方便快速的自定義能力。
IP解析示例
以下是常見的IP解析示例情況,本地ip為192.168.0.100
示例:
-
正常IP解析
127.0.0.1:8869
解析成 ipv4 127.0.0.1 埠 8869,只接受本地來的連接信息0.0.0.0:8869
解析成 ipv4 0.0.0.0 埠 8869,可接受所有來自ipv4的連接信息
-
以
:
開頭的地址,且不包含-
:8869
解析成 ipv4 127.0.0.1 埠 8869 及 ipv4 192.168.0.100 埠 8869
-
包含
-
的地址:8869-:8871
解析成 ipv4 127.0.0.1 埠 8869 - 8871 三個埠地址 及 ipv4 192.168.0.100 埠 8869 - 8871 三個埠地址,總共6個埠地址127.0.0.1:8869-:8871
解析成 ipv4 127.0.0.1 埠 8869 - 8871 三個埠地址 總共3個埠地址127.0.0.1:8869-192.168.0.100:8871
解析成 ipv4 127.0.0.1 埠 8869 - 8871 三個埠地址 總共3個埠地址,忽略後面的地址,只接受埠號
-
手動多個地址,可以空格或者
,
做間隔127.0.0.1:8869 127.0.0.1:8899 192.168.0.100:8899
就相應的解析成三個埠地址
定義類
由於解析出來的地址可能是多個或者個單個,這裡用數組來進行表示
#[derive(Debug, Clone)]
pub struct WrapVecAddr(pub Vec<SocketAddr>);
通常序列化會用到FromStr
將字元串轉化成類
反序列化都會用到Display
將類轉化成字元串
所以在這裡,我們將實現FromStr
及Display
impl FromStr for WrapVecAddr {
type Err = AddrParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// 範圍的如:8080-:8090, 表示11埠
if s.contains("-") {
let vals = s
.split(&['-'])
.filter(|s| !s.is_empty())
.collect::<Vec<&str>>();
let start = parse_socker_addr(vals[0])?;
if vals.len() != 2 {
return Ok(WrapVecAddr(start));
} else {
let end = parse_socker_addr(vals[1])?;
let mut results = vec![];
for port in start[0].port()..=end[1].port() {
for idx in &start {
let mut addr = idx.clone();
addr.set_port(port);
results.push(addr);
}
}
return Ok(WrapVecAddr(results));
}
} else {
let vals = s
.split(&[',', ' '])
.filter(|s| !s.is_empty())
.collect::<Vec<&str>>();
let mut results = vec![];
for s in vals {
results.extend(parse_socker_addr(s)?);
}
Ok(WrapVecAddr(results))
}
}
}
impl Display for WrapVecAddr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut values = vec![];
for a in &self.0 {
values.push(format!("{}", a));
}
f.write_str(&values.join(","))
}
}
這樣子後我們將配置加上就可以自動實現序列化及反序列化了
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
#[serde_as(as = "DisplayFromStr")]
pub bind_addr: WrapVecAddr,
// ...
}
獲取本機地址
通過庫local-ip-address
獲取本地IP地址,再根據預設IP時添加本地IP地址訪問,因為預設時有可能是需要本地內網進行訪問。所以需要補上本地網卡地址。所以我們在解析以:
時做了特殊處理:
fn parse_socker_addr(s: &str) -> Result<Vec<SocketAddr>, AddrParseError> {
if s.starts_with(":") {
let port = s.trim_start_matches(':');
let mut results = vec![];
if let Ok(port) = port.parse::<u16>() {
if let Ok(v) = local_ip() {
results.push(SocketAddr::new(v, port));
}
if let Ok(v) = local_ipv6() {
results.push(SocketAddr::new(v, port));
}
results.push(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port));
} else {
results.push(format!("127.0.0.1{s}").parse::<SocketAddr>()?);
}
Ok(results)
} else {
let addr = s.parse::<SocketAddr>()?;
Ok(vec![addr])
}
}
參數獲取
以下舉例
file-server
的參數
#[derive(Debug, Clone, Bpaf)]
#[allow(dead_code)]
struct FileServerConfig {
/// 靜態文件根目錄路徑
#[bpaf(short, long, fallback(String::new()))]
pub(crate) root: String,
#[bpaf(
short,
long,
fallback(WrapVecAddr(vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8869)])),
display_fallback
)]
/// 監聽地址
pub(crate) listen: WrapVecAddr,
#[bpaf(long)]
/// 監聽地址
pub(crate) listen_ssl: Option<WrapVecAddr>,
/// ...
}
如此我們就可以輕鬆的用SSL監聽及普通的監聽添加多個埠的支持。
wmproxy file-server --listen :8869-8871
此時我們可以同時監聽3個埠均支持文件伺服器。此時我們就可以輕鬆控制多個埠地址。
綁定多個地址
以下是負載均衡中的綁定示例
for v in &value.bind_addr.0 {
if bind_addr_set.contains(&v) {
continue;
}
bind_addr_set.insert(v);
let url = format!("http://{}", v);
log::info!("HTTP服務:{},提供http處理及轉發功能。", Style::new().blink().green().apply_to(url));
let listener = Helper::bind(v).await?;
listeners.push(listener);
tlss.push(false);
}
for v in &value.bind_ssl.0 {
if bind_addr_set.contains(&v) {
continue;
}
bind_addr_set.insert(v);
if !is_ssl {
return Err(crate::ProxyError::Extension("配置SSL埠但未配置證書"));
}
let url = format!("https://{}", v);
log::info!("HTTPs服務:{},提供https處理及轉發功能。", Style::new().blink().green().apply_to(url));
let listener = Helper::bind(v).await?;
listeners.push(listener);
tlss.push(is_ssl);
}
支持ssl綁定及非ssl綁定同一個location。
其中鏈接信息使用了console
,輸出了綠色的,可以點擊的url鏈接。可以方便在啟動的時候進行點擊。
圖中圈圈位置是可以點擊跳轉成url,方便本地開發環境的時候測試使用。
總結
通過FromStr
及Display
的重定義,我們可以支持更強大的自定義的序列化操作,系統綁定埠既認埠號也認綁定IP,所以我們可以對同個埠進行多次綁定。
點擊 [關註],[在看],[點贊] 是對作者最大的支持