首页 > 编程语言 >35. 干货系列从零用Rust编写负载均衡及代理,代理服务器的源码升级改造

35. 干货系列从零用Rust编写负载均衡及代理,代理服务器的源码升级改造

时间:2023-12-19 09:02:08浏览次数:40  
标签:mut return buffer await 35 源码 let 代理服务器 HTTP

wmproxy

wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子

项目地址

国内: https://gitee.com/tickbh/wmproxy

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

项目设计目标

在同一个端口上同时支持HTTP/HTTPS/SOCKS5代理,即假设监听8090端口,那么可以设置如下:

curl --proxy socks5://127.0.0.1:8090 http://www.baidu.com

curl --proxy http://127.0.0.1:8090 http://www.baidu.com

curl --proxy http://127.0.0.1:8090 https://www.baidu.com

以上方案需要都可以兼容打通,才算成功。

初始方案

不做HTTP服务器,仅简单的解析数据流,然后进行数据转发

pub async fn process<T>(
    username: &Option<String>,
    password: &Option<String>,
    mut inbound: T,
) -> Result<(), ProxyError<T>>
where
    T: AsyncRead + AsyncWrite + Unpin,
{
    let mut outbound;
    let mut request;
    let mut buffer = BinaryMut::new();
    loop {
        let size = {
            let mut buf = ReadBuf::uninit(buffer.chunk_mut());
            inbound.read_buf(&mut buf).await?;
            buf.filled().len()
        };

        if size == 0 {
            return Err(ProxyError::Extension("empty"));
        }
        unsafe {
            buffer.advance_mut(size);
        }
        request = webparse::Request::new();
        // 通过该方法解析标头是否合法, 若是partial(部分)则继续读数据
        // 若解析失败, 则表示非http协议能处理, 则抛出错误
        // 此处clone为浅拷贝,不确定是否一定能解析成功,不能影响偏移
        match request.parse_buffer(&mut buffer.clone()) {
            Ok(_) => match request.get_connect_url() {
                Some(host) => {
                    match HealthCheck::connect(&host).await {
                        Ok(v) => outbound = v,
                        Err(e) => {
                            Self::err_server_status(inbound, 503).await?;
                            return Err(ProxyError::from(e));
                        }
                    }
                    break;
                }
                None => {
                    if !request.is_partial() {
                        Self::err_server_status(inbound, 503).await?;
                        return Err(ProxyError::UnknownHost);
                    }
                }
            },
            Err(WebError::Http(HttpError::Partial)) => {
                continue;
            }
            Err(_) => {
                return Err(ProxyError::Continue((Some(buffer), inbound)));
            }
        }
    }

    match request.method() {
        &Method::Connect => {
            log::trace!(
                "https connect {:?}",
                String::from_utf8_lossy(buffer.chunk())
            );
            inbound.write_all(b"HTTP/1.1 200 OK\r\n\r\n").await?;
        }
        _ => {
            outbound.write_all(buffer.chunk()).await?;
        }
    }
    let _ = copy_bidirectional(&mut inbound, &mut outbound).await?;
    Ok(())
}

此方案仅做浅解析,处理相当高效,但遇到如下问题:

  • HTTP/HTTPS代理服务器需要验证密码
  • HTTP服务存在不同的协议,此方法只兼容HTTP/1.1,无法兼容明确的HTTP/2协议
  • 请求的协议头有些得做修改,此方法无法修改

改造方案

  • 引入HTTP服务器介入
  • 但是因为需要兼容不同协议,只有等确定协议后才能引入协议,需要预读数据,进行协议判定。
  • HTTPS代理协议只处理一组Connect协议,之后需要解除http协议进行双向绑定。

完整源码

  1. 预读数据
  • Socks5:第一个字节为0X05,非ascii字符,其它协议不会影响
  • Https: https代理必须发送Connect方法,所以必须以CONNECT或者connect开头,且查询其它HTTP方法没有以C开头的,这里仅判断第一个字符为C或者c,该协议仅处理一条http请求不参与后续TLS握手协议等保证数据安全
  • 其它开头的均被认为http代理
let mut buffer = BinaryMut::with_capacity(24);
let size = {
    let mut buf = ReadBuf::uninit(buffer.chunk_mut());
    inbound.read_buf(&mut buf).await?;
    buf.filled().len()
};

if size == 0 {
    return Err(ProxyError::Extension("empty"));
}
unsafe {
    buffer.advance_mut(size);
}
// socks5 协议, 直接返回, 交给socks5层处理
if buffer.as_slice()[0] == 5 {
    return Err(ProxyError::Continue((Some(buffer), inbound)));
}

let mut max_req_num = usize::MAX;
// https 协议, 以connect开头, 仅处理一条HTTP请求
if buffer.as_slice()[0] == b'C' || buffer.as_slice()[0] == b'c' {
    max_req_num = 1;
}
  1. 构建HTTP服务器,构建服务类:
/// http代理类处理类
struct Operate {
    /// 用户名
    username: Option<String>,
    /// 密码
    password: Option<String>,
    /// Stream类, https连接后给后续https使用
    stream: Option<TcpStream>,
    /// http代理keep-alive的复用
    sender: Option<Sender<RecvRequest>>,
    /// http代理keep-alive的复用
    receiver: Option<Receiver<ProtResult<RecvResponse>>>,
}

构建HTTP服务

// 需要将已读的数据buffer重新加到server的已读cache中, 否则解析会出错
let mut server = Server::new_by_cache(inbound, None, buffer);
// 构建HTTP服务回调
let mut operate = Operate {
    username: username.clone(),
    password: password.clone(),
    stream: None,
    sender: None,
    receiver: None,
};
server.set_max_req(max_req_num);
let _e = server.incoming(&mut operate).await?;
if let Some(outbound) = &mut operate.stream {
    let mut inbound = server.into_io();
    let _ = copy_bidirectional(&mut inbound, outbound).await?;
}

此时我们已将数据用HTTP服务进行处理,收到相应的请求再进行给远端做转发:

HTTP核心处理回调,此处我们用的是async_trait异步回调


#[async_trait]
impl OperateTrait for &mut Operate {
    async fn operate(&mut self, request: &mut RecvRequest) -> ProtResult<RecvResponse> {
        // 已连接直接进行后续处理
        if let Some(sender) = &self.sender {
            sender.send(request.replace_clone(Body::empty())).await?;
            if let Some(res) = self.receiver.as_mut().unwrap().recv().await {
                return Ok(res?)
            }
            return Err(ProtError::Extension("already close by other"))
        }
        // 获取要连接的对象
        let stream = if let Some(host) = request.get_connect_url() {
            match HealthCheck::connect(&host).await {
                Ok(v) => v,
                Err(e) => {
                    return Err(ProtError::from(e));
                }
            }
        } else {
            return Err(ProtError::Extension("unknow tcp stream"));
        };

        // 账号密码存在,将获取`Proxy-Authorization`进行校验,如果检验错误返回407协议
        if self.username.is_some() && self.password.is_some() {
            let mut is_auth = false;
            if let Some(auth) = request.headers_mut().remove(&"Proxy-Authorization") {
                if let Some(val) = auth.as_string() {
                    is_auth = self.check_basic_auth(&val);
                }
            }
            if !is_auth {
                return Ok(Response::builder().status(407).body("")?.into_type());
            }
        }

        // 判断用户协议
        match request.method() {
            &Method::Connect => {
                // https返回200内容直接进行远端和客户端的双向绑定
                self.stream = Some(stream);
                return Ok(Response::builder().status(200).body("")?.into_type());
            }
            _ => {
                // http协议,需要将客户端的内容转发到服务端,并将服务端数据转回客户端
                let client = Client::new(ClientOption::default(), MaybeHttpsStream::Http(stream));
                let (mut recv, sender) = client.send2(request.replace_clone(Body::empty())).await?;
                match recv.recv().await {
                    Some(res) => {
                        self.sender = Some(sender);
                        self.receiver = Some(recv);
                        return Ok(res?)
                    },
                    None => return Err(ProtError::Extension("already close by other")),
                }
            }
        }

    }
}

密码校验,由Basic的密码加密方法,先用base64解密,再用:做拆分,再与用户密码比较

pub fn check_basic_auth(&self, value: &str) -> bool
{
    use base64::engine::general_purpose;
    use std::io::Read;

    let vals: Vec<&str> = value.split_whitespace().collect();
    if vals.len() == 1 {
        return false;
    }

    let mut wrapped_reader = Cursor::new(vals[1].as_bytes());
    let mut decoder = base64::read::DecoderReader::new(
        &mut wrapped_reader,
        &general_purpose::STANDARD);
    // handle errors as you normally would
    let mut result: Vec<u8> = Vec::new();
    decoder.read_to_end(&mut result).unwrap();

    if let Ok(value) = String::from_utf8(result) {
        let up: Vec<&str> = value.split(":").collect();
        if up.len() != 2 {
            return false;
        }
        if up[0] == self.username.as_ref().unwrap() ||
            up[1] == self.password.as_ref().unwrap() {
            return true;
        }
    }

    return false;
}

小结

代理在计算机网络很常见,比如服务器群组内部通常只会开一个口进行对外访问,就可以通过内网代理来进行处理,从而更好的保护内网服务器。代理让我们网络更安全,但是警惕非正规的代理可能会窃取您的数据。请用HTTPS内容访问更安全。

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

标签:mut,return,buffer,await,35,源码,let,代理服务器,HTTP
From: https://www.cnblogs.com/wmproxy/p/wmproxy35.html

相关文章

  • springboot015粮食仓库管理系统(毕业设计,附数据库和源码)
    一.4开发的技术介绍一.4.1Springboot介绍一.4.2Java语言一.4.3MySQL数据库一.5论文的结构二需求分析二.1需求设计二.2可行性分析二.2.1技术可行性二.2.2经济可行性二.2.3操作可行性二.3功能需求分析表2-1粮食仓库管理系统功能结构图三系统设计三.1数据库概念结构......
  • springboot012响应式企业员工绩效考评系统(vue,毕业设计,附源码和数据库)
    2 关键技术2.1SpringBoot框架2.2 Maven环境2.3Mysql数据库2.4Vue.js框架2.5小结4 系统分析与设计4.1系统架构在对一个系统的开发中,必须全面的考虑用户对学校系统的需求,这个步骤需要开发出系统的功能的用途,每个图应代表系统的一个功能模块。系统架构图:4.2系统功能设......
  • springboot045新闻推荐系统-计算机毕业设计源码+LW文档
    摘要随着信息互联网购物的飞速发展,国内放开了自媒体的政策,一般企业都开始开发属于自己内容分发平台的网站。本文介绍了新闻推荐系统的开发全过程。通过分析企业对于新闻推荐系统的需求,创建了一个计算机管理新闻推荐系统的方案。文章介绍了新闻推荐系统的系统分析部分,包括可行性分......
  • 35道HTML高频题整理(附答案背诵版)
    1、简述HTML5新特性?HTML5是HTML的最新版本,它引入了很多新的特性和元素,以提供更丰富的网页内容和更好的用户体验。以下是一些主要的新特性:语义元素:HTML5引入了新的语义元素,像<article>,<section>,<nav>,<header>,<footer>,<aside>等。这些元素可以帮助更好地描述......
  • 智慧工地源码,劳务实名制信息化管理解决方案
     智慧工地劳务实名制信息化管理解决方案是基于物联网、人脸识别、云计算等信息化技术,与智能终端硬件设备互联互通,完成劳务人员基础数据收集,实现对劳务人员从信息登记、合同管理、劳务进场、安全教育、项目管理、考勤统计、工资结算、工资支付的全过程管控。    劳务实名制......
  • 更新升级 | iTOP-RK3588开发板手册分类详解
    迅为iTOP-RK3588开发板配套手册升级!因为开发资料众多(目前手册资料已达2700+页),为了方便大家更快速上手使用开发板,迅为iTOP-RK3588开发板配套手册按功能性分为了13大类,如下所示  1快速定位 每个分类下包含了对应主题的PDF文档资料,这样大家可以快读定位需要使用的文档。每个主题下......
  • RK3568 android12 动态替换开机logo
    前言:最近客户有个需要,通过adbpush来动态替换开机logo。通过网上查阅相关资料,现整理如下。参考:RK3568Android/Linux系统动态更换U-Boot/KernelLogo解决方法:通过自定义一个分区来存储开机logo,这样在恢复出厂时不会丢失开机logo。然后通过修改u-boot/drivers/video/drm/rock......
  • OpenHarmony应用编译 - 如何在源码中编译复杂应用(4.0-Release)
    概述文档环境开发环境:Windows11编译环境:Ubuntu22.04开发板型号:DAYU200(RK3568)系统版本:OpenHarmony-4.0-Release涉及仓库:applications_launcher功能简介在OpenHarmony系统中预安装应用的hap包会随系统编译打包到镜像中,目前有两种编译预安装应用hap包的方式,一种为随系统编译时,编......
  • Java互联网+公立医院绩效考核源码
    一、建设信息化医院绩效考核的意义1.提高考核效率:通过信息化手段,可以将绩效考核数据自动采集、整理、分析和报告,大大提高了考核效率,减少了人工干预和错误率。2.增强考核公正性:信息化考核可以减少人为因素的干扰,使考核更加公正、客观。同时,通过数据共享,可以增强考核结果的透明度和......
  • Linux配置成代理服务器
    简介: 代理服务器(ProxyServer)是一种位于计算机网络中的中间服务器,它充当了客户端和目标服务器之间的中介,用于转发客户端请求并获取目标服务器的响应。代理服务器的主要功能包括以下几点:什么是代理服务器   代理服务器(ProxyServer)是一种位于计算机网络中的中间服务器,它充......