相关库的安装
利用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 localhostlet addr: SocketAddr = "127.0.0.1:7890".parse().unwrap();// Bind to the port and listen for incoming TCP connectionslet 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 bodyif 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 localhostlet addr: SocketAddr = "127.0.0.1:7890".parse().unwrap();// Bind to the port and listen for incoming TCP connectionslet 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 httpstokio::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>)>whereB: 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
}