利用Rust hyper库实现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证书导入电脑受信任根证书目录下

动态为相关网站颁发证书

///创建一个证书颁发模块 crt.mod

use std::{fs::File, io::Read};

use openssl::asn1::Asn1Integer;
use openssl::bn::BigNum;
use openssl::rand;

use openssl::pkey::{Private};
use openssl::rsa::Rsa;

use openssl::{
    pkey::PKey, x509::extension::SubjectAlternativeName, x509::X509,
};

// 以下函数用于加载根证书和私钥
fn load_root_cert_and_key(
    root_cert_path: &str,
    root_key_path: &str,
) -> (X509, PKey<Private>) {
    let ca_file = File::open(root_cert_path).unwrap();
    let bytes = ca_file.bytes().map(|b| b.unwrap()).collect::<Vec<u8>>();
    let ca = X509::from_pem(&bytes).unwrap();

    let pkey_file = File::open(root_key_path).unwrap();
    let bytes = pkey_file.bytes().map(|b| b.unwrap()).collect::<Vec<u8>>();
    let pkey = PKey::private_key_from_pem(&bytes).unwrap();

    // 实现加载根证书和私钥的逻辑
    (ca, pkey)
}


// 客户端动态颁发证书
fn client_cert_signing(
    _root_cert_path: &str,
    _root_key_path: &str,
    host:&str
) -> Result<(X509, PKey<Private>), openssl::error::Error> {
    // 加载根证书和私钥
    let (root_cert, root_key) = load_root_cert_and_key(root_cert_path, root_key_path);
    
    // 生成密钥对
    let rsa = Rsa::generate(2048).unwrap();
    let private_key = PKey::from_rsa(rsa).unwrap();

    let public_key_pem = private_key.public_key_to_pem().unwrap();
    let public_key = PKey::public_key_from_pem(&public_key_pem).unwrap();

    // 设置证书有效期
    // let not_before = openssl::asn1::Asn1Time::days_from_now(0).unwrap();
    //let not_after = openssl::asn1::Asn1Time::days_from_now(365).unwrap();

    // 颁发证书
    let mut cert = X509::builder().unwrap();
    cert.set_version(2).unwrap();

    let mut x509_name = openssl::x509::X509NameBuilder::new().unwrap();
    x509_name.append_entry_by_text("C", "ZH").unwrap();
    x509_name.append_entry_by_text("ST", "SC").unwrap();
    x509_name.append_entry_by_text("L", "YC").unwrap();
    x509_name.append_entry_by_text("O", "YC").unwrap();
    x509_name.append_entry_by_text("OU", "YC").unwrap();
    x509_name
        .append_entry_by_text("CN", host)
        .unwrap();
    let x509_name = x509_name.build();

    cert.set_subject_name(&x509_name).unwrap();
    cert.set_issuer_name(&root_cert.subject_name()).unwrap();
    let mut serial_number = [0; 16];
    rand::rand_bytes(&mut serial_number).unwrap();

    let serial_number = BigNum::from_slice(&serial_number).unwrap();
    let serial_number = Asn1Integer::from_bn(&serial_number).unwrap();
    cert.set_serial_number(&serial_number).unwrap();

    cert.set_not_before(root_cert.not_before()).unwrap();
    cert.set_not_after(root_cert.not_after()).unwrap();
    cert.set_pubkey(&public_key).unwrap();


    let alternative_name = SubjectAlternativeName::new()
            .dns(host)
            .build(&cert.x509v3_context(Some(&root_cert),None)).unwrap();
        cert.append_extension(alternative_name).unwrap();

    cert.sign(&root_key, openssl::hash::MessageDigest::sha256())
        .unwrap();
    
    Ok((cert.build(), private_key))
}


pub fn get_crt_key(host:&str) ->(X509, PKey<Private>){
	//导入自签名CA证书,并将crt格式的CA证书导入浏览器
	//导入CA证书的私钥
    client_cert_signing("./crt/proxylea_cert.crt", 
                        "./crt/proxylea_private.key", host).unwrap()
}


效果如下,可以看见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请求,代理服务器与远程服务器连接成功后直接转发请求和响应。

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(())
}

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

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

2、拦截全部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
}

结果展示

 

全部代码 

整体代码写下来问题不大,主要是前面的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
}


  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
RustHyper是一个基于tokio的异步HTTP,它提供了一系列的API来处理HTTP请求和响应。同时,Hyper也支持WebSocket的实现。 要使用Hyper实现WebSocket,可以使用其提供的WebSocket模块。下面是一个简单的例子: ```rust use hyper::{upgrade::Upgraded, service::service_fn_ok, Body, Request, Response, Server}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpListener; use tokio::sync::broadcast; use tokio::sync::Mutex; use tungstenite::{Message, Result, WebSocket}; async fn handle_websocket(upgraded: Upgraded, tx: broadcast::Sender<String>, mut rx: broadcast::Receiver<String>) -> Result<()> { let (ws_tx, mut ws_rx) = WebSocket::from_raw_socket(upgraded, tungstenite::protocol::Role::Server, None).await?; let mut tx = tx.subscribe(); loop { tokio::select! { Some(msg) = ws_rx.next() => { let msg = msg?; if msg.is_text() { let text_msg = msg.to_text().unwrap().to_owned(); tx.send(text_msg).unwrap(); } } Ok(msg) = rx.recv() => { ws_tx.send(Message::Text(msg)).await?; } } } } async fn handle_request(req: Request<Body>, tx: broadcast::Sender<String>, rx: broadcast::Receiver<String>) -> Result<Response<Body>> { if req.uri().path() == "/websocket" { let res = Response::builder() .status(101) .header("Sec-WebSocket-Accept", "...") .body(Body::empty()) .unwrap(); let upgraded = req.into_body().on_upgrade().await?; handle_websocket(upgraded, tx, rx).await?; Ok(res) } else { let body = format!("Hello, {}!", req.uri().path()); let res = Response::new(Body::from(body)); Ok(res) } } #[tokio::main] async fn main() -> Result<()> { let listener = TcpListener::bind("127.0.0.1:8080").await?; let (tx, _) = broadcast::channel(10); let rx = tx.subscribe(); let tx = Mutex::new(tx); let server = Server::builder(listener.incoming()) .serve(move || { let tx = tx.clone(); let rx = rx.clone(); service_fn_ok(move |req| handle_request(req, tx.lock().unwrap().clone(), rx.clone())) }); server.await?; Ok(()) } ``` 上面的代码实现了一个简单的WebSocket服务器,同时也支持HTTP请求。当客户端发送一个WebSocket请求时,服务器会将连接升级为WebSocket连接,并通过广播的方式将收到的消息发送给所有客户端。当客户端发送一个HTTP请求时,服务器会返回一个简单的“Hello”消息。 对于HTTP请求,我们可以使用Hyper提供的Request和Response类型来处理。对于WebSocket连接的处理,我们需要使用tungstenite提供的WebSocket类型来处理。在这个例子中,我们使用了tokio的广播通道来实现消息的发送和接收。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值