相干库的安装
使用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哀求,署理服务器与远程服务器毗连乐成后直接转发哀求和相应。
- /// 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 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
- }
-
-
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金 |