首页 > 编程语言 >16. 从零用Rust编写正反向代理, 反向代理upstream源码实现

16. 从零用Rust编写正反向代理, 反向代理upstream源码实现

时间:2024-01-09 12:03:09浏览次数:31  
标签:parent 16 代理 value server 源码 let location child

wmproxy

wmproxy是由Rust编写,已实现http/https代理,socks5代理, 反向代理,静态文件服务器,内网穿透,配置热更新等, 后续将实现websocket代理等,同时会将实现过程分享出来, 感兴趣的可以一起造个轮子法

项目 wmproxy

gite: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

了解反向代理

反向代理(Reverse Proxy)是一种服务器架构的技术,位于客户端和目标服务器之间,处理来自客户端的所有请求,并代表目标服务器处理与客户端的交互。

保护源站

在客户端访问服务器的时候,其实并不关心目标的地址在哪,只要数据能够正常返回,签名能够正常的握手,就认为是正常的。 而通常源站的防护等级相对会较弱,比如源站一般没有防御DDOS的能力,暴露了源站的地址也就意味着被渗透被攻击的概率大大升高,从而使服务变得极不稳定。

加速传输

通常反向代理可以遍布各个节点,然后再通过专有线路来访问源站,或者一次请求缓存结果多次返回就可以减少和源站通讯,减少源站压力,就典型的结构如CDN就可以大大的提高客户端的访问速度,减少延迟

防火墙作用

由于所有的客户机请求都必须通过代理服务器访问远程站点,因此可以在代理服务器上设定限制,过滤某些不安全信息,如WAF防火墙之类。

反向代理有哪些配置

以下是一份nginx的反向代理的配置

http {
    upstream backend {
        server 192.168.0.14:8080 weight=10 fail_timeout=3s ; 
        server 192.168.0.15:8081 weight=10; 
    }
    server {
        listen 80;  #监听80的服务端口
        server_name wm-proxy.com;  #监听的域名
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
        location /products {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Origin' '*';
        }
        
        location / {
            root wmproxy;
            index index.html index.htm;
        }
    }
    server {
        listen 80;  #监听80的服务端口
        server_name localhost;  #监听的域名
       
        location / {
            proxy_pass http://www.baidu.com;
            proxy_set_header Host $host;
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Origin' '*';
        }
    }
}

以上配置内容主要有几点需实现:

upstream

这是反向连接的代理池,可能配置了多个的数据可访问的源地址,此处需要实现各种策略来平衡访问它,如weight权重模式,ip_hash按客户端地址来映射相同的源站地址,保证同一个客户端只进入一个源站,如fair按后端服务器的响应时间来分配请求,响应时间短的优先分配。

各自的健康检查参数需要和全局的进行区分

fail_timeout失败的重试时间 max_fails超过这失败次数则认为不可连

多个server同时监听同一个端口

反向代理可配置多个server同时监听同一个端口,按server_name来区分要访问的的源站地址

同一个端口,多个证书的问题

需要根据客户端传输的域名来自动选择对应的证书进行解析来返回数据,保证数据的正确。

父级的配置要映射到子级的选项

比如配置在proxy_set_header的每个选项在他子级的location都需要进行设置,而在Rust中要获取父类的结构相当的麻烦,这点需要正确的解决

location的多种结构支持

location可能是反向代理,可能是文件服务器,需要多种配置支持

实现源码

以下是各upstream的定义

#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SingleStreamConfig {
    /// 访问地址
    pub addr: SocketAddr,
    /// 权重
    #[serde(default = "default_weight")]
    pub weight: u16,
    /// 失败的恢复时间
    #[serde_as(as = "DurationSeconds<u64>")]
    #[serde(default = "fail_timeout")]
    fail_timeout: Duration,
    /// 当前连续失败的次数
    #[serde(default = "default_fall_times")]
    fall_times: usize,
    /// 当前连续成功的次数
    #[serde(default = "default_rise_times")]
    rise_times: usize,
}

这边用到了serde_with库中的serde_as,把数字秒解析成Duration类型。我们在检查是否存活的时候会带入相应的参数与全局的做区分开来

/// 检测状态是否能连接
pub fn check_fall_down(addr: &SocketAddr, fail_timeout: &Duration, fall_times: &usize, rise_times: &usize) -> bool {
    ...
}

关于多个端口监听,开始时我们会遍历所有的端口,且只会绑定一次

let mut bind_port = HashSet::new();
for value in &self.server.clone() {
    // 已监听的端口存到Set里面
    if bind_port.contains(&value.bind_addr.port()) {
        continue;
    }
    bind_port.insert(value.bind_addr.port());
    let listener = TcpListener::bind(value.bind_addr).await?;
    listeners.push(listener);
}

保证只会绑定一次端口。等解析完Req的时候再进行转发,保证能正确的处理转发

同一个端口多个证书的问题

因为客户端发送ClientHello的时候我们可以知道是从哪个域名过来的,所以我们可以根据发过来的域名选择正确的证书,就可以解决多个证书的问题,在rustls中,我们用ResolvesServerCertUsingSni来进行解决,下面相关源码

let config = rustls::ServerConfig::builder().with_safe_defaults();
let mut resolve = ResolvesServerCertUsingSni::new();
for value in &self.server.clone() {
    let mut is_ssl = false;
    if value.cert.is_some() && value.key.is_some() {
        let key = sign::any_supported_type(&Self::load_keys(&value.key)?)
            .map_err(|_| ProtError::Extension("unvaild key"))?;
        let ck = CertifiedKey::new(Self::load_certs(&value.cert)?, key);
        resolve.add(&value.server_name, ck).map_err(|e| {
            println!("{:?}", e); ProtError::Extension("key error")
        })?;
        is_ssl = true;
    }

    tlss.push(is_ssl);
}

let config = config
    .with_no_client_auth()
    .with_cert_resolver(Arc::new(resolve));
Ok((Some(TlsAcceptor::from(Arc::new(config))), tlss, listeners))

ResolvesServerCertUsingSni可以配置多个域名的证书,但证书必须和域名强匹配,Accept的时候会根据域名选择相应的证书。

子级需要能访问父级的配置问题 在Rust因为所有权的问题,一个对象肯定会归属于一个地方的所有权,所以无法在不经常加工的情况实现类似其它语言的parent->getChild()child->getParent(),而此处比如location需要共享server的数据,如root参数。目前查资料比较公认的有以下方式: 用指针的方向(raw pointer),但是指针无法Send,也就是无法在线程间转移。

struct Parent {
    child: Child,
}

struct Child {
    parent: *const Parent,
}

fn main() {
    let mut child = Child {
        parent: std::ptr::null(),
    };
    let parent = Parent { child };
    child.parent = &parent;
}

用共享计数方法(Rc)

use std::rc::Rc;

// 所有的Child都将拥有该对象的引用
struct Inner;

struct Parent {
    child: Child,
    inner: Rc<Inner>,
}

struct Child {
    parent: Rc<Inner>, // or Weak<Inner> if that's desirable
}

fn main() {
    let inner = Rc::new(Inner);
    let child = Child {parent: Rc::clone(&inner)};
    let parent = Parent {child, inner};
}

用临时的生命周期,获取Child的时候做特殊处理

struct Parent {
  pub children: Vec<Child>,
}

impl Parent {
 fn get_child(&'a self, name) -> DynamicChild<'a> {
   DynamicChild { parent: self, child: ...}
 }
}

struct Child {
 a: u64,
 b: String,
}

struct DynamicChild<'a> {
 pub data: &'a Child,
 pub parent: &'a Parent,
}

impl<'a> DynamicChild<'a> {
 fn do_thing_with_parent(&self) -> usize {
  self.parent.children.len()
 }
}

Rust为了保证安全,但凡有所有权归属的问题,就会变得比较麻烦,我们这里会在数据序列化的时候,把父级的配置直接写入到子级的配置,以这种方式子级就有完整的数据,也可以避免访问父级的内容。

/// 将配置参数提前共享给子级
pub fn copy_to_child(&mut self) {
    for server in &mut self.server {
        server.upstream.append(&mut self.upstream.clone());
        server.copy_to_child();
    }
}

此时保证location这一层处理的能得到完整的数据,即可以避免访问父级节点。

location的多种结构支持

location可能是静态文件服务器,也可能是反向代理,也可能是后续的fast-cgi等。 location根据rule进行req中的path匹配,如果填有Method方法也根据Method是否匹配。然后再根据相应的分支选项进行处理匹配。

let host = req.get_host().unwrap_or(String::new());
// 不管有没有匹配, 都执行最后一个
for (index, s) in value.server.iter().enumerate() {
    if s.server_name == host || host.is_empty() || index == server_len - 1 {
        let path = req.path().clone();
        for l in s.location.iter() {
            if l.is_match_rule(&path, req.method()) {
                return l.deal_request(req).await;
            }
        }
        // ...
    }
}

结语

此时关于反向代理的几个初步问题已经处理完成反向代理操作。反向代理在互联网已经组成了密不可分的组成部分,成为了互联网的基石之一。像云服务器的负载均衡,K8S中的数据同步等大的小的均用到了这一项技术。

标签:parent,16,代理,value,server,源码,let,location,child
From: https://blog.51cto.com/u_16321542/9159074

相关文章

  • 唯美心情语录随笔个人博客模板源码
    这是一款关于心情日记随笔个人博客模板,心情语录随笔个人博客模板主要记录心情日记的博客网站。采用html5+css3设计,模板基于dedecms程序搭建测试。1、主页html代码<!doctypehtml><htmllang="zh-cn"><head><metacharset="gb2312"><title>心情日记_心情语录随笔-个人......
  • Java+springboot开发医院智能导诊小程序源码
    智慧医院如何实现智能导诊服务?1、数据收集和整合:医院需要收集和整合患者的医疗数据,包括病历、化验结果、影像资料等。同时,还可以整合相关的医学数据库和知识库,以便为导诊提供支持。2、患者信息采集:在患者来院时,可以通过智能问诊系统收集患者的基本信息、症状描述、病史等。这可以......
  • Java药物不良反应ADR智能监测系统源码
    药物不良反应(AdverseDrugReaction,ADR)是指在使用合格药品时,在正常的用法和用量下出现的与用药目的无关的有害反应。这些反应往往因药物种类、使用方式、个体差异等因素而异,可能导致患者身体不适、病情恶化。 为保障患者用药安全,及时发现药物不良反应迹象,亟需一套智能化监测系统......
  • 使用Ruby编写的代理爬虫程序:抓取dy视频播放量接口数据并解析(附详细中文解释)
    随着互联网的快速发展,网络数据的获取变得愈发重要。在某些情况下,我们可能需要通过代理来访问特定的网站或API,以确保数据的准确性和可靠性。本文将介绍如何使用Ruby编写一个代理爬虫程序,以抓取dy视频播放量接口的数据并进行解析。准备工作首先,我们需要引入两个关键的Ruby库:open-uri......
  • JavaScript Promise超详细源码解读
    Promise超详细源码解读说到promise,相信大家在日常开发中都经常使用到,它是我们异步操作中必不可少的一部分,可以让代码看起来变得更好理解;我曾在技术社区看过许多关于promise底层原理的文章,大概原理明白,这次,我准备系统的分析实现源码并记录下来,本文将一行行代码去分析最后附加流程图......
  • 【设计模式】建造者模式——建造者模式在Android SDK源码中的应用
    建造者模式在AndroidSDK源码中也有广泛的应用,本文挑两个典型的类讨论一下:AlertDialog.Builder在Android源码中最常用到的建造者模式非AlertDialog.Builder莫属,代码如下:AlertDialogalertDialog=newAlertDialog.Builder(mContext) .setTitle("系统提示:").setMessage("请......
  • git增加代理设置
    问题默认的git下载很慢,想加速配置代理[root@lab201nfs-ganesha]#cat/root/.gitconfig[user] [email protected] name=zphj1987[credential] helper=store[push] default=simple[http]proxy=http://192.168.0.12:7890[https]proxy=......
  • ACES 增强版不丹水稻作物地图(2016-2022 年)
    ACES增强版不丹水稻作物地图(2016-2022年)¶用于改善粮食安全决策的2016-2022年年度作物类型稻米地图仍然是不丹的一项挑战。这些地图是与不丹农业部和SERVIR合作开发的。通过专注于发展不丹的科学、技术、工程和数学(STEM),我们共同开发了名为农业分类和估算服务(ACES)的地......
  • Spring MVC 源码分析 - RequestToViewNameTranslator 组件
    RequestToViewNameTranslator组件RequestToViewNameTranslator 组件,视图名称转换器,用于解析出请求的默认视图名。就是说当ModelAndView对象不为 null,但是它的View对象为 null,则需要通过 RequestToViewNameTranslator 组件根据请求解析出一个默认的视图名称。回顾先来回顾......
  • python爬虫之创建属于自己的ip代理池
    在后续需求数据量比较大的情况下,自建一个ip代理池可以帮助我们获得更多的数据。下面我来介绍一下整个过程1.找到目标代理网站https://www.dailiservers.com/go/websharehttps://proxyscrape.com/https://spys.one/https://free-proxy-list.net/http://free-proxy.cz/en/https:......