【Rust网络编程】开发一个图片代理和统计服务

news/2024/9/28 1:25:31

最近我使用Rust开发了一个代理服务。可以用于代理和统计图片资源的访问

例如:

http://127.0.0.1:8100/image-public/0a1e65f4-7ced-4ef0-ba7d-12ec4d14a0d4.png
->http://xxx.com:45004/image-public/0a1e65f4-7ced-4ef0-ba7d-12ec4d14a0d4.png

项目特点

  • 高性能:使用Rust语言编写
  • 异步处理:基于Tokio运行时,实现高并发的异步I/O操作
  • 精确统计:准确记录目标图片的访问次数

技术栈

  • Rust 编程语言
  • Hyper:用于HTTP服务器和客户端的快速、安全框架。性能好,偏底层,应用广泛,知名的reqwest和axum等都使用了hyper,已成为Rust网络程序生态的重要基石之一
  • Tokio:异步运行时,提供高效的I/O操作

功能介绍

  1. HTTP 代理:

    • 监听本地端口(默认8100),接收 HTTP 请求
    • 访问目标图片(路径以/image-public/开头)将被代理,转发到配置的目标服务器
  2. 图片访问统计:

    • 精确统计目标图片的访问次数
  3. 请求日志:

    • 详细记录每个请求的方法、路径和头部信息
    • 输出响应状态码和图片访问计数
  4. 错误处理:

    • 对于错误图片的请求,返回404 Not Found响应

代码

核心代码如下:

#![deny(warnings)]use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::str::FromStr;use bytes::Bytes;
use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full};
use hyper::client::conn::http1::Builder;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::upgrade::Upgraded;
use hyper::{Method, Request, Response, StatusCode, Uri};use tokio::net::{TcpListener, TcpStream};#[path = "../benches/support/mod.rs"]
mod support;
use support::TokioIo;// 图片下载计数器
static IMAGE_DOWNLOAD_COUNT: AtomicUsize = AtomicUsize::new(0);#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {let addr = SocketAddr::from(([127, 0, 0, 1], 8100));let listener = TcpListener::bind(addr).await?;println!("正在监听 http://{}", addr);loop {let (stream, _) = listener.accept().await?;let io = TokioIo::new(stream);tokio::task::spawn(async move {if let Err(err) = http1::Builder::new().preserve_header_case(true).title_case_headers(true).serve_connection(io, service_fn(proxy)).with_upgrades().await{println!("服务连接失败: {:?}", err);}});}
}async fn proxy(req: Request<hyper::body::Incoming>,
) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> {println!("收到请求: 方法={:?}, 路径={}, 头部={:?}", req.method(), req.uri().path(), req.headers());println!("请求: {:?}", req);if Method::CONNECT == req.method() {if let Some(addr) = host_addr(req.uri()) {tokio::task::spawn(async move {match hyper::upgrade::on(req).await {Ok(upgraded) => {if let Err(e) = tunnel(upgraded, addr).await {eprintln!("服务器 IO 错误: {}", e);};}Err(e) => eprintln!("升级错误: {}", e),}});Ok(Response::new(empty()))} else {eprintln!("CONNECT 主机不是 socket 地址: {:?}", req.uri());let mut resp = Response::new(full("CONNECT 必须连接到 socket 地址"));*resp.status_mut() = StatusCode::BAD_REQUEST;Ok(resp)}} else {// 检查是否是目标图片下载请求let is_target_image = req.uri().path().starts_with("/image-public/");if is_target_image {// 构建新的 URIlet new_uri = format!("http://xxx.com:45004{}", req.uri().path());let new_uri = Uri::from_str(&new_uri).expect("无效的 URI");// 保存原始路径let original_path = req.uri().path().to_string();// 创建新的请求let (parts, body) = req.into_parts();let mut new_req = Request::new(body);*new_req.method_mut() = parts.method;*new_req.uri_mut() = new_uri;*new_req.version_mut() = parts.version;*new_req.headers_mut() = parts.headers;// 连接到实际的服务器let stream = TcpStream::connect(("xxx.com", 45004)).await.unwrap();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!("连接失败: {:?}", err);}});let resp = sender.send_request(new_req).await?;// 如果是目标图片请求,增加计数器if resp.status().is_success() || resp.status() == StatusCode::NOT_MODIFIED {let count = IMAGE_DOWNLOAD_COUNT.fetch_add(1, Ordering::SeqCst);println!("目标图片请求成功。状态码: {}. 总计数: {}", resp.status(), count + 1);} else if resp.status() == StatusCode::NOT_FOUND {println!("目标图片不存在。路径: {}", original_path);let mut not_found_resp = Response::new(full("Image Not Found"));*not_found_resp.status_mut() = StatusCode::NOT_FOUND;return Ok(not_found_resp);}Ok(resp.map(|b| b.boxed()))} else {// 对于非目标图片请求,返回 404 Not Foundlet mut resp = Response::new(full("Not Found"));*resp.status_mut() = StatusCode::NOT_FOUND;Ok(resp)}}
}fn host_addr(uri: &http::Uri) -> Option<String> {uri.authority().and_then(|auth| Some(auth.to_string()))
}fn empty() -> BoxBody<Bytes, hyper::Error> {Empty::<Bytes>::new().map_err(|never| match never {}).boxed()
}fn full<T: Into<Bytes>>(chunk: T) -> BoxBody<Bytes, hyper::Error> {Full::new(chunk.into()).map_err(|never| match never {}).boxed()
}async fn tunnel(upgraded: Upgraded, addr: String) -> std::io::Result<()> {let mut server = TcpStream::connect(addr).await?;let mut upgraded = TokioIo::new(upgraded);let (from_client, from_server) =tokio::io::copy_bidirectional(&mut upgraded, &mut server).await?;println!("客户端写入 {} 字节并接收 {} 字节",from_client, from_server);Ok(())
}

完整代码参考我的仓库:https://github.com/VinciYan/proxy_counter.git

运行效果

收到请求: 方法=GET, 路径=/image-public/0a1e65f4-7ced-4ef0-ba7d-12ec4d14a0d4.png, 头部={"content-type": "application/json", "user-agent": "PostmanRuntime/7.42.0", "accept": "*/*", "postman-token": "9fe5ee1a-ad8e-4e0d-8f65-e82090115795", "host": "127.0.0.1:8100", "accept-encoding": "gzip, deflate, br", "connection": "keep-alive", "content-length": "75"}     
请求: Request { method: GET, uri: /image-public/0a1e65f4-7ced-4ef0-ba7d-12ec4d14a0d4.png, version: HTTP/1.1, headers: {"content-type": "application/json", "user-agent": "PostmanRun
time/7.42.0", "accept": "*/*", "postman-token": "9fe5ee1a-ad8e-4e0d-8f65-e82090115795", "host": "127.0.0.1:8100", "accept-encoding": "gzip, deflate, br", "connection": "keep-alive", "content-length": "75"}, body: Body(Streaming) }
目标图片请求成功。状态码: 200 OK. 总计数: 3

参考

  • https://github.com/VinciYan/proxy_counter.git
  • Getting Started | hyper

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

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

相关文章

k8s 分布式存储平台 -- Longhorn

目录一、什么是 Longhorn二、架构设计1、工作原理2、工作流程3、基于微服务设计的优势三、安装1、安装要求2、使用 Longhorn 命令行工具(验证方式一)3、使用环境检查脚本(验证方式之二)3.1、安装 jq3.2、运行脚本4、安装 open-iscsi4.1、SUSE 和 openSUSE4.2、Debian 和 Ub…

全网最适合入门的面向对象编程教程:53 Python 字符串与序列化-字符串与字符编码

在 Python 中,字符串是文本的表示,默认使用 Unicode 编码,这允许你处理各种字符集,字符编码是将字符转换为字节的规则,常见的编码包括UTF-8、UTF-16和ASCII。全网最适合入门的面向对象编程教程:53 Python 字符串与序列化-字符串与字符编码摘要: 在 Python 中,字符串是文…

【基础岛第3关】浦语提示词工程实践

[to2024-09-25 18:32:11 星期三c] 案例描述 0、前期准备 创建开发机 0.1 环境配置创建虚拟环境并激活创建虚拟环境conda create -n langgpt python=3.10 -y conda activate langgpt 2. 安装必要的库 # 安装一些必要的库 conda install pytorch==2.1.2 torchvision==0.16.2 torc…

9月27日swing知识点

swing是一系列图形用户界面的控件的集合 Swing中GUI类分为三大类: 容器类 JFrame、JPanel、JScrollPane UI组件类 JLabel、JTextField、JTextArea、JButton JCheckBox、JRadioButton、JComboBox 帮助类 Color、Font、Dimension 这三者的依存关系为组件必须依存在顶层容器中,组…

软件工程结对作业

这个作业属于哪个课程 软件工程这个作业要求在哪里 结对作业要求这个作业的目标 设计一个软件的方案原型学号 102202109(我)012202239(朱佳杰)《构建之法》第三章与第八章读后感 第三章:软件工程师的成长 1.1 个人能力的衡量与发展 在《构建之法》第三章中,作者详细阐述了…

油猴脚本使用指南

油猴插件介绍 官方介绍:篡改猴 (Tampermonkey) 是拥有 超过 1000 万用户 的最流行的浏览器扩展之一。 它适用于 Chrome、Microsoft Edge、Safari、Opera Next 和 Firefox。 有些人也会把篡改猴(Tampermonkey)称作油猴(Greasemonkey),尽管后者只是一款仅适用于 Firefox 浏览器…

一些常用的技巧分享

0. 前言 最近很多uu们过来问鼠鼠一些 c语言/python 的问题,遂决定开一个答疑帖,方便大家学习交流。但是呢既然开了帖,也就不只讲有疑问的地方,顺便把常见的一些问题都给大家过一遍。又考虑到很多uu跟鼠鼠一样是电脑小白,也顺带分享一些电子产品、生活小知识。 鼠鼠水平有限…