49从零开始用Rust编写nginx,我竟然在同一个端口上绑定了多少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,所以我们可以对同个端口进行多次绑定。
点击
[关注]
,
[在看]
,
[点赞]
是对作者最大的支持