使用Rust实现http/https正向代理

相关库的安装

利用vcpkg安装openssl库

vcpkg install openssl:x64-windows

并设置openssl库位置的环境变量

$Env:OPENSSL_DIR="D:/vcpkg/packages/openssl_x64-windows/"

安装openssl软件,因为需要利用openssl生成自签名证书

Cargo依赖

[dependencies]
anyhow = "1.0.81"
bytes = "1.6.0"
futures = "0.3.30"
http-body-util = "0.1.1"
hyper = { version = "1.2.0", features = ["full"] }
hyper-util = { version = "0.1.3", features = ["full"] }
openssl = "0.10.64"
tokio = { version = "1.37.0", features = ["full"] }
tokio-openssl = "0.6.4"
futures-util = "0.3.30"
tokio-util = "0.7.10"

利用OpenSSL生成自签名证书

1、生成RSA私钥文件
openssl genrsa -out proxylea_private.key 2048
2、创建证书签名请求文件
openssl req -new -key proxylea_private.key -out server.csr
3、使用私钥为证书签名(自签名)
openssl x509 -req -days 3650 -in server.csr -signkey  proxylea_private.key -out proxylea_cert.crt
4、将proxylea_cert.crt证书导入电脑受信任根证书目录下

动态为相关网站颁发证书

1、生成RSA私钥文件
openssl genrsa -out proxylea_private.key 2048
2、创建证书签名请求文件
openssl req -new -key proxylea_private.key -out server.csr
3、使用私钥为证书签名(自签名)
openssl x509 -req -days 3650 -in server.csr -signkey  proxylea_private.key -out proxylea_cert.crt
4、将proxylea_cert.crt证书导入电脑受信任根证书目录下

效果如下,可以看见www.baidu.com网站的证书由自签名证书颁发。

利用hyper构建HTTP代理服务器

构建本地代理服务器

#[tokio::main]
pub async fn main() -> Result<()> {
    // This address is localhost
    let addr: SocketAddr = "127.0.0.1:7890".parse().unwrap();
 
    // Bind to the port and listen for incoming TCP connections
    let listener = TcpListener::bind(addr).await?;
    println!("Listening on http://{}", addr);
    loop {
        let (tcp_stream, addr) = listener.accept().await?;
        let msg = format!("{addr} connected");
        dbg!(msg);
        tokio::task::spawn(async move {
            let io = TokioIo::new(tcp_stream);
 
            let conn = http1::Builder::new()
                .serve_connection(io, service_fn(server_upgrade));
 
            // Don't forget to enable upgrades on the connection.
            let mut conn = conn.with_upgrades();
 
            let conn = Pin::new(&mut conn);
            if let Err(err) = conn.await {
                println!("Error serving connection: {:?}", err);
            }
        });
    }
}

将http协议升级到https

1、浏览器在进行https握手时,需要先发送一个CONNECT的http请求给代理服务器,代理服务器返回状态码200的http响应,代表浏览器与代理服务连接建立成功。

2、浏览器与代理服务器开始TLS握手,代理服务器开始连接远程网站服务器。

3、二者都连接成功时就可以传输数据了。

4、对于http请求,代理服务器与远程服务器连接成功后直接转发请求和响应。

/// Intercept local requests
fn intercept_request(mut request: Request<Incoming>) -> (Request<HttpBody>, Option<Response<HttpBody>>) {
    dbg!(request.uri().to_string());
    request.headers_mut().remove("Accept-Encoding");
    let req = request.map(|b| b.boxed());
 
    if let Some(Ok(host)) = req.headers().get(header::HOST).map(|h| h.to_str()) {
        if host.contains("127.0.0.1:7890")
            || host.contains("localhost:7890")
            || host.contains("baidu") {
            let resp = Response::builder()
                .header(header::CONTENT_TYPE, "text/plain")
                .body(full("Proxylea Server Power By Rust & Hyper\n"));
            return (req, Some(resp.unwrap()));
        }
    }
    (req, None)
}
 
/// Intercept remote responses
fn intercept_response(mut response: Response<Incoming>) -> Response<HttpBody> {
    dbg!({ format!("{:?}", response.headers()) });
    response.headers_mut().insert("proxy-server", "Proxylea".parse().unwrap());
    //let (parts,incoming)=resp.into_parts();
    let resp = response.map(|b| b.map_frame(|frame| {
        //You can modify and record the response body
        if let Some(bytes) = frame.data_ref() {
            //todo
        }
        frame
    }).boxed());
    resp
}

拦截本地浏览器请求和远程服务器响应

1、拦截浏览器对baidu网站的请求,并返回一些信息

2、拦截全部http响应,并且打印响应头和添加一些响应头

/// Intercept local requestsfn intercept_request(mut request: Request<Incoming>) -> (Request<HttpBody>, Option<Response<HttpBody>>) {    dbg!(request.uri().to_string());    request.headers_mut().remove("Accept-Encoding");    let req = request.map(|b| b.boxed());     if let Some(Ok(host)) = req.headers().get(header::HOST).map(|h| h.to_str()) {        if host.contains("127.0.0.1:7890")            || host.contains("localhost:7890")            || host.contains("baidu") {            let resp = Response::builder()                .header(header::CONTENT_TYPE, "text/plain")                .body(full("Proxylea Server Power By Rust & Hyper\n"));            return (req, Some(resp.unwrap()));        }    }    (req, None)} /// Intercept remote responsesfn intercept_response(mut response: Response<Incoming>) -> Response<HttpBody> {    dbg!({ format!("{:?}", response.headers()) });    response.headers_mut().insert("proxy-server", "Proxylea".parse().unwrap());    //let (parts,incoming)=resp.into_parts();    let resp = response.map(|b| b.map_frame(|frame| {        //You can modify and record the response body        if let Some(bytes) = frame.data_ref() {            //todo        }        frame    }).boxed());    resp}

结果展示

全部代码

整体代码写下来问题不大,主要是前面的openssl库的编译有些问题。Rust的hyper库类似于Java的Netty库,都属于底层库,但是hyper功能远不如Netty,hyper只是一个http相关的底层库。还有就是相关文档与资源实在是TM太少了,只能去看官方的example。从开发效率上来讲还是Netty更快(不如说是Java开发效率更快),但是学习hyper库有助于学习Rust的异步、特征、泛型,如果看见hyper库里面的pin_xx、poll_xx、各种特征与泛型非常自然的话,那么离熟练使用Rust也就不远了。

use std::error::Error;
use std::net::SocketAddr;
use std::pin::Pin;
use std::sync::Mutex;
 
use anyhow::{anyhow, Result};
use bytes::Bytes;
use futures::FutureExt;
use http_body_util::{BodyExt, Empty, Full};
use http_body_util::combinators::BoxBody;
use hyper::{header, Request, Response, StatusCode};
use hyper::body::{Body, Incoming};
use hyper::client::conn::http1::{Builder, Connection, SendRequest};
use hyper::rt::{Read, Write};
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::upgrade::Upgraded;
use hyper_util::rt::TokioIo;
use openssl::ssl::{Ssl, SslAcceptor, SslConnector, SslMethod, SslVerifyMode};
use tokio::net::{TcpListener, TcpStream};
use tokio_openssl::SslStream;
 
pub mod crt;
 
type HttpBody = BoxBody<Bytes, hyper::Error>;
 
#[tokio::main]
pub async fn main() -> Result<()> {
    // This address is localhost
    let addr: SocketAddr = "127.0.0.1:7890".parse().unwrap();
 
    // Bind to the port and listen for incoming TCP connections
    let listener = TcpListener::bind(addr).await?;
    println!("Listening on http://{}", addr);
    loop {
        let (tcp_stream, addr) = listener.accept().await?;
        let _msg = format!("{addr} connected");
        //dbg!(msg);
        tokio::task::spawn(async move {
            let io = TokioIo::new(tcp_stream);
 
            let conn = http1::Builder::new()
                .serve_connection(io, service_fn(handle));
 
            // Don't forget to enable upgrades on the connection.
            let mut conn = conn.with_upgrades();
 
            let conn = Pin::new(&mut conn);
            if let Err(err) = conn.await {
                println!("Error serving connection: {:?}", err);
            }
        });
    }
}
 
fn get_host_port(host_name: &str) -> (&str, u16) {
    match host_name.find(":") {
        None => {
            (host_name, 80)
        }
        Some(i) => {
            (&host_name[0..i], *&host_name[i + 1..].parse().unwrap_or(80))
        }
    }
}
 
fn not_found_host() -> Response<HttpBody> {
    Response::builder().status(404).body(full("not found host")).unwrap()
}
 
/// Our server HTTP handler to initiate HTTP upgrades.
async fn handle(mut req: Request<Incoming>) -> Result<Response<HttpBody>> {
    if req.method() != hyper::Method::CONNECT {
        let (host, port) = match req.headers().get(header::HOST) {
            None => {
                return Ok(not_found_host());
            }
            Some(h) => { get_host_port(h.to_str()?) }
        };
 
        let stream = TcpStream::connect((host, port)).await?;
        let io = TokioIo::new(stream);
 
        let (mut sender, conn) = Builder::new()
            .preserve_header_case(true)
            .title_case_headers(true)
            .handshake(io)
            .await?;
        tokio::task::spawn(async move {
            if let Err(err) = conn.await {
                println!("Connection failed: {:?}", err);
            }
        });
        let (req, resp) = intercept_request(req);
        return match resp {
            None => {
                let resp = sender.send_request(req).await?;
                Ok(resp.map(|b| b.boxed()))
            }
            Some(resp) => {
                Ok(resp)
            }
        };
    }
 
    let res = Response::new(empty());
    // handle https
    tokio::task::spawn(async move {
        match hyper::upgrade::on(&mut req).await {
            Ok(upgraded) => {
                if let Some(host) = req.uri().host() {
                    if let Err(e) = server_upgraded_https(host, upgraded).await {
                        let error_msg = format!("server io error: {}", e);
                        dbg!(error_msg);
                    };
                }
            }
            Err(e) => eprintln!("upgrade error: {}", e),
        }
    });
 
    Ok(res)
}
 
 
/// https upgraded
async fn server_upgraded_https(host: &str, upgraded: Upgraded) -> Result<()> {
    let upgraded = TokioIo::new(upgraded);
    // we have an upgraded connection that we can read and
    // write on directly.
    //
    let tls_acceptor = get_tls_acceptor(host);
    let ssl = Ssl::new(tls_acceptor.context())?;
    let mut tls_stream = SslStream::new(ssl, upgraded)?;
    if let Err(err) = SslStream::accept(Pin::new(&mut tls_stream)).await {
        return Err(anyhow!("error during tls handshake connection from : {}", err));
    }
    let stream = TokioIo::new(tls_stream);
 
    let (sender, conn) = https_remote_connect(host, 443).await?;
    tokio::spawn(async move {
        if let Err(err) = conn.await {
            let err_msg = format!("Connection failed: {:?}", err);
            dbg!(err_msg);
        }
    });
    let wrap_sender = Mutex::new(sender);
    if let Err(err) = http1::Builder::new()
        .serve_connection(stream, service_fn(|req| {
            let (req, resp) = intercept_request(req);
            async {
                match resp {
                    None => {
                        let remote_resp = wrap_sender.lock().unwrap().send_request(req);
                        match remote_resp.await {
                            Ok(resp) => {
                                Ok::<_, hyper::Error>(intercept_response(resp))
                            }
                            Err(err) => {
                                let resp = Response::builder()
                                    .status(StatusCode::INTERNAL_SERVER_ERROR)
                                    .header(header::CONTENT_TYPE, "text/plain")
                                    .body(full(err.to_string())).unwrap();
                                Ok::<_, hyper::Error>(resp)
                            }
                        }
                    }
                    Some(resp) => {
                        Ok::<_, hyper::Error>(resp)
                    }
                }
            }
        })).await {
        println!("Error serving connection: {:?}", err);
    }
 
    Ok(())
}
 
 
fn empty() -> HttpBody {
    Empty::<Bytes>::new()
        .map_err(|never| match never {})
        .boxed()
}
 
fn full<T: Into<Bytes>>(chunk: T) -> HttpBody {
    Full::new(chunk.into())
        .map_err(|never| match never {})
        .boxed()
}
 
/// Certificate not cached
fn get_tls_acceptor(host: &str) -> SslAcceptor {
    let mut tls_builder = SslAcceptor::mozilla_modern_v5(SslMethod::tls()).unwrap();
 
    let (crt, pri_key) = crt::get_crt_key(host);
 
    tls_builder.set_certificate(&crt).unwrap();
 
    tls_builder.set_private_key(&pri_key).unwrap();
 
    tls_builder.check_private_key().unwrap();
 
    let tls_acceptor = tls_builder.build();
    tls_acceptor
}
 
 
async fn https_remote_connect<B>(host: &str, port: u16) -> Result<(SendRequest<B>, Connection<TokioIo<SslStream<TcpStream>>, B>)>
    where
        B: Body + 'static,
        B::Data: Send,
        B::Error: Into<Box<dyn Error + Send + Sync>>, {
    let addr = format!("{}:{}", host, port);
    let tcp_stream = TcpStream::connect(addr).await?;
    let mut builder = SslConnector::builder(SslMethod::tls_client())?;
    builder.set_verify(SslVerifyMode::NONE);
    let connector = builder.build();
    let ssl = Ssl::new(connector.context())?;
    let mut tls_stream = SslStream::new(ssl, tcp_stream)?;
    if let Err(err) = SslStream::connect(Pin::new(&mut tls_stream)).await {
        return Err(anyhow!("error during tls handshake connection from : {}", err));
    }
    let io = TokioIo::new(tls_stream);
    Ok(hyper::client::conn::http1::handshake(io).await?)
}
 
/// Intercept local requests
fn intercept_request(mut request: Request<Incoming>) -> (Request<HttpBody>, Option<Response<HttpBody>>) {
    dbg!(request.uri().to_string());
    request.headers_mut().remove("Accept-Encoding");
    let req = request.map(|b| b.boxed());
 
    if let Some(Ok(host)) = req.headers().get(header::HOST).map(|h| h.to_str()) {
        if host.contains("127.0.0.1:7890")
            || host.contains("localhost:7890")
            || host.contains("baidu") {
            let resp = Response::builder()
                .header(header::CONTENT_TYPE, "text/plain")
                .body(full("Proxylea Server Power By Rust & Hyper\n"));
            return (req, Some(resp.unwrap()));
        }
    }
    (req, None)
}
 
/// Intercept remote responses
fn intercept_response(mut response: Response<Incoming>) -> Response<HttpBody> {
    dbg!({ format!("{:?}", response.headers()) });
    response.headers_mut().insert("proxy-server", "Proxylea".parse().unwrap());
    //let (parts,incoming)=resp.into_parts();
    let resp = response.map(|b| b.map_frame(|frame| {
        if let Some(bytes) = frame.data_ref() {
            //
        }
        frame
    }).boxed());
    resp
}
 
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/910044.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于MATLAB的运动车辆跟踪检测系统

一、课题介绍 本设计为基于MATLAB的运动车辆跟踪检测系统。带有一个GUI界面&#xff0c;可以读取高速路车流视频&#xff0c;读取视频信息&#xff0c;并且统计每辆车经过左车道还是右车道&#xff0c;车速和平均速度检测&#xff0c;以及某一帧下的车流密度&#xff0c;以及最…

微深节能 环形运动机械定位控制系统 格雷母线

微深节能的环形运动机械定位控制系统中的格雷母线是一种高精度、无磨损的非接触式位置检测系统&#xff0c;特别适用于环形运动机械的定位控制。该系统主要由格雷母线、天线箱、电气柜等关键部件组成&#xff0c;其核心在于格雷母线这一特殊的编码线。 格雷母线的工作原理是通过…

【359】基于springboot的智慧草莓基地管理系统

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本智慧草莓基地管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据…

如何利用 Python 的爬虫技术获取淘宝天猫商品的价格信息?

以下是使用 Python 的爬虫技术获取淘宝天猫商品价格信息的两种常见方法&#xff1a; 方法一&#xff1a;使用 Selenium 一、环境准备&#xff1a; 安装 selenium 库&#xff1a;在命令行中运行 pip install selenium。下载浏览器驱动&#xff1a;如 ChromeDriver&#xff08;确…

定时任务——xxl-job原理与实现

摘要 本文详细介绍了分布式任务调度平台xxl-job的原理与实现。xxl-job以其开发迅速、学习简单、轻量级和易扩展的特性被广泛使用。文章概述了xxl-job的核心特性&#xff0c;包括任务的CRUD操作、动态调度、高可用性、弹性扩容缩容、丰富的触发策略、调度过期策略、阻塞处理策略…

TDengine 签约蘑菇物联,改造通用设备工业互联网平台

在当前工业互联网迅猛发展的背景下&#xff0c;企业面临着日益增长的数据处理需求和智能化转型的挑战。通用工业设备的高能耗问题愈发突出&#xff0c;尤其是由这些设备组成的公辅能源车间&#xff0c;亟需更高效的解决方案来提升设备运行效率&#xff0c;降低能源消耗。为此&a…

【业务】支付总结和GP支付功能测试

背景 我个人支付相关内容测试很少&#xff08;不是你想接什么业务就能接到&#xff0c;都是各方利益博弈以后结果&#xff09;&#xff0c;有些内容也是听听技术会议&#xff0c;看看其他qa的xmind通过只言片语里面做个总结。 支付类型 直连支付 概述&#xff1a;提供支付接…

2024中国国际数字经济博览会:图为科技携明星产品引领数智化潮流

10月24日&#xff0c;全球数智化领域的目光齐聚于中国石家庄正定&#xff0c;一场关于数字经济未来的盛会—2024中国国际数字经济博览会在此拉开帷幕。 云边端算力底座的领航者&#xff0c;图为科技携其明星产品惊艳亮相&#xff0c;期待与您共赴一场数智化的非凡之旅&#xff…

ESP32 gptimer通用定时器初始化报错:assert failed: timer_ll_set_clock_prescale

背景&#xff1a;IDF版本V5.1.2 &#xff0c;配置ESP32 通用定时器&#xff0c;实现100HZ&#xff0c;占空比50% 的PWM波形。 根据乐鑫官方的IDF指导文档设置内部计数器的分辨率&#xff0c;计数器每滴答一次相当于 1 / resolution_hz 秒。 &#xff08;ESP-IDF编程指导文档&a…

【Python】强大的正则表达式工具:re模块详解与应用

强大的正则表达式工具&#xff1a;re模块详解与应用 在编程和数据处理中&#xff0c;字符串的处理是不可避免的一项任务。无论是从文本中提取信息、验证数据格式&#xff0c;还是进行复杂的替换操作&#xff0c;正则表达式&#xff08;Regular Expression&#xff0c;简称Rege…

IP协议知识点总结

IP协议主要分为三个 1. 地址管理 每个网络上的设备, 要能分配一个唯一的地址 2. 路由选择 小A 给小B 发消息, 具体应该走什么路线 3. 地址管理 IP 地址. 本质上是一个 32 位的整数 通常将, 32 位的整数使用点分十进制来表示, 如 192.168.1.1 一共可以表示 42 亿 9 千万个地址…

动态规划 之 路径问题 算法专题

一. 不同路径 不同路径 状态表示 dp[i][j] 表示走到[i][j]位置, 有几种不同的路径状态转移方程 以离[i][j] 最近的位置划分问题 1.从[i - 1][j] 到[i][j], 到[i][j]位置的不同路径数 就是和 到[i - 1][j]位置的不同路径数相同, 即dp[i][j] dp[i - 1][j] 2.从[i][j - 1] 到[i…

浮动路由:实现出口线路的负载均衡冗余备份。

浮动路由 Tip&#xff1a;浮动路由指在多条默认路由基础上加入优先级参数&#xff0c;实现出口线路冗余备份。 ip routing-table //查看路由表命令 路由优先级参数&#xff1a;越小越优 本次实验测试两条默认路由&#xff0c;其中一条默认路由添加优先级参数&#xff0c;设置…

补一下 二维 平面直角坐标系 到三维

上一篇帖子写到 二维的平面直角坐标系&#xff0c;是那样的&#xff0c;这次补充一下三维的。首先需要&#xff0c;安装一个包&#xff0c;如下&#xff1a; 然后&#xff0c;把参数输入&#xff0c;输入这个坐标系的参数&#xff0c;如下&#xff1a; 这样就可以输出如下的三…

CertiK创始人顾荣辉出席新加坡商业与慈善论坛,发表主旨演讲并主持专题讨论

2024年11月5日 —— 美国哥伦比亚大学教授、CertiK联合创始人、MAS国际技术顾问顾荣辉受邀参加2024年度新加坡商业与慈善论坛&#xff08;Business & Philanthropy Leadership Forum Singapore&#xff0c;简称B&P Forum&#xff09;&#xff0c;期间发表主旨演讲并主持…

基于STM32的智能物联网家用机器人设计

引言 本项目基于STM32微控制器设计了一个智能物联网家用机器人&#xff0c;通过集成多个传感器模块、摄像头以及Wi-Fi模块&#xff0c;实现远程控制、家庭监控和环境数据采集等功能。该系统可以监测家中的环境状况&#xff0c;如温湿度、烟雾浓度等&#xff0c;还可以作为安全…

jenkins流水线pipeline

创建项目 1. 新建item 并选择pipeline 1.1 和普通项目配置的区别 普通项目配置目录&#xff1a; pipeline项目目录&#xff1a; pipeline的两种语法 声明式语法 2. 配置 2.1 流水线配置 2.2 选择声明式 声明式需要添加一个名为Jenkinsfile的文件实现流水线 Jenkinsfile的…

【CSS】标准怪异盒模型

概念 CSS 盒模型本质上是一个盒子&#xff0c;盒子包裹着HTML 元素&#xff0c;盒子由四个属性组成&#xff0c;从内到外分别是&#xff1a;content 内容、padding 内填充、border 边框、外边距 margin 盒模型的分类 W3C 盒子模型(标准盒模型) IE 盒子模型(怪异盒模型) 两种…

系统上云-流量分析和链路分析

优质博文&#xff1a;IT-BLOG-CN 一、流量分析 【1】流量组成&#xff1a; 按协议划分&#xff0c;流量链路可分为HTTP、SOTP、QUIC三类。 HTTPSOTPQUIC场景所有HTTP请求&#xff0c;无固定场景国内外APP等海外APP端链路选择DNS/CDN(当前特指Akamai)APP端保底IP列表/动态IP下…

linux操作系统的开机引导

一、linux操作系统的开机引导的过程 1、开机自检 根据bios的设置&#xff0c;对cpu&#xff0c;内存&#xff0c;显卡&#xff0c;键盘等设备进行初步检测&#xff0c;如果以上检测设备工作正常&#xff0c;系统会把控制权移交到硬盘 2、MBR引导/GPR引导 分区之后&#xff…