• 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);

    通常序列化会用到FromStr将字符串转化成类
    反序列化都会用到Display将类转化成字符串
    所以在这里,我们将实现FromStrDisplay

    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, 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::()?);
    }
    Ok(results)
    } else {
    let addr = s.parse::()?;
    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,
    /// ...
    }

    如此我们就可以轻松的用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,方便本地开发环境的时候测试使用。

    总结

    通过FromStrDisplay的重定义,我们可以支持更强大的自定义的序列化操作,系统绑定端口既认端口号也认绑定IP,所以我们可以对同个端口进行多次绑定。

    点击 [关注][在看][点赞] 是对作者最大的支持

  • 相关阅读:
    Linux Centos系统 磁盘分区和文件系统管理 (深入理解)
    为什么要使用 kafka,为什么要使用消息队列?
    并发和并行以及他们的区别
    【长难句分析精讲】从属复合句
    【Node.js---学习小结(2)】
    瞅一瞅JUC提供的限流工具Semaphore
    文件批量管理:轻松复制备份并删除原文件
    C语言学习1==gcc 创建动态库
    Android Studio查看第三方库依赖树
    vue2项目使用ant design vue组件库table组件时设置column根据接口返回的字段值显示不同内容
  • 原文地址:https://www.cnblogs.com/wmproxy/p/18020327/wmproxy49