bws-rs:Rust 编写的 S3 协议网关框架,支持灵活后端接入

bws-rs介绍

bws-rs 是一个用 Rust 编写的轻量级 S3 协议服务端网关框架,旨在帮助开发者快速构建兼容 AWS S3 协议 的对象存储服务。该框架支持 S3 V4 签名校验,集成 Axum 作为 Web 框架,所有协议校验逻辑通过实现对应的 trait 并注册为 axum::Extension 实现非侵入式扩展,具有良好的可维护性与可插拔性。

bws-rs 可作为前端网关挂载在你已有的文件系统、对象存储系统甚至缓存引擎之前,为其提供标准化的 S3 协议兼容层,支持与 AWS CLI、MinIO Client 等主流 S3 SDK 的交互。

✅ 已支持的功能
📁 S3 协议支持列表

  • PutObject(上传对象)

  • GetObject(获取对象)

  • HeadObject(获取对象元信息)

  • DeleteObject(删除对象)

  • CreateBucket(创建桶)

  • HeadBucket(桶存在性检查)

  • ListBucket(列举所有桶)

  • DeleteBucket(删除桶)

  • GetBucketLocation(获取桶区域)

  • MultipartUpload(分片上传)

  • Range Get(部分下载)

  • Get/Put Object ACL(访问控制列表)

  • Get/Put Object Metadata(对象元数据)

  • Put Object Tagging(对象标签)

✅ MinIO SDK 兼容性验证
使用 MinIO Go SDK 进行功能验证,支持以下操作:

  • MakeBucket

  • DeleteBucket

  • ListBucket

  • ListObject

  • PutObject

  • DeleteObject

  • BucketExists

在项目中使用bws-rs: cargo add bws-rs

实现bws_rs::service::s3下对应的trait以支持对应的s3 功能

  • HeadHandler: 对应 s3 head object ,head bucket
  • GetObjectHandler: 对应s3 GetObject
  • PutObjectHandler: 对应s3 PutObject
  • DeleteObjectHandler: 对应s3 DeleteObject
  • ListObjectHandler: 对应s3 ListObject
  • CreateBucketHandler: 对应的s3 create bucket
  • ListBucketHandler: 对应s3 list bucket
  • DeleteBucketHandler: 对应s3 delete bucket
  • GetBucketLocationHandler: 对应s3 get bucket location
  • MultiUploadObjectHandler: 对应s3 MultiUpload系列操作

aceeskey 仓库需要实现bws_rs::authorization::AccesskeyStore 来提供对应accesskey的secretkey

使用示范

    use std::sync::Arc;use tokio::io::AsyncReadExt;#[derive(Default)]struct Target {}use crate::service::s3::*;impl CreateBucketHandler for Target {fn handle<'a>(&'a self,_opt: &'a CreateBucketOption,_bucket: &'a str,) -> std::pin::Pin<Box<dyn 'a + Send + std::future::Future<Output = Result<(), String>>>>{Box::pin(async move {log::info!("create bucket {_bucket}");Ok(())})}}impl ListBucketHandler for Target {fn handle<'a>(&'a self,_opt: &'a ListBucketsOption,) -> std::pin::Pin<Box<dyn 'a + Send + std::future::Future<Output = Result<Vec<Bucket>, String>>>,> {Box::pin(async move {let datetime = chrono::Utc::now().to_rfc3339();Ok(vec![Bucket {name: "test1".to_string(),creation_date: datetime,bucket_region: "us-east-1".to_string(),}])})}}impl HeadHandler for Target {fn lookup<'a>(&self,_bucket: &str,_object: &str,) -> std::pin::Pin<Box<dyn 'a+ Send+ Sync+ std::future::Future<Output = Result<Option<HeadObjectResult>, Error>>,>,> {Box::pin(async move {let mut ret: HeadObjectResult = Default::default();ret.checksum_sha256 = Some("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824".to_string(),);ret.content_length = Some(5);ret.etag = Some("5d41402abc4b2a76b9719d911017c592".to_string());ret.last_modified = Some(chrono::Utc::now().format("%a, %d %b %Y %H:%M:%S GMT").to_string(),);Ok(Some(ret))})}}impl PutObjectHandler for Target {fn handle<'a>(&'a self,opt: &PutObjectOption,bucket: &'a str,object: &'a str,body: &'a mut (dyn tokio::io::AsyncRead + Unpin + Send),) -> std::pin::Pin<Box<dyn 'a + Send + std::future::Future<Output = Result<(), String>>>>{Box::pin(async move {log::info!("put bucket {bucket} object {object}");let mut buff = vec![];match body.read_to_end(&mut buff).await {Ok(size) => {log::info!("get {}", unsafe {std::str::from_utf8_unchecked(&buff[..size])});}Err(err) => {log::error!("read error {err}");}}Ok(())})}}impl DeleteBucketHandler for Target {fn handle<'a>(&'a self,_opt: &'a DeleteBucketOption,_bucket: &'a str,) -> std::pin::Pin<Box<dyn 'a + Send + std::future::Future<Output = Result<(), String>>>>{Box::pin(async move {log::info!("delete bucket {_bucket}");Ok(())})}}impl DeleteObjectHandler for Target {fn handle<'a>(&'a self,_opt: &'a DeleteObjectOption,_object: &'a str,) -> std::pin::Pin<Box<dyn 'a + Send + std::future::Future<Output = Result<(), String>>>>{Box::pin(async move {log::info!("delete object {_object}");Ok(())})}}impl crate::authorization::AccesskeyStore for Target {fn get<'a>(&'a self,_accesskey: &'a str,) -> std::pin::Pin<Box<dyn 'a + Send + Sync + std::future::Future<Output = Result<Option<String>, String>>,>,> {Box::pin(async move { Ok(Some(format!("{_accesskey}12345"))) })}}impl crate::service::s3::GetObjectHandler for Target {fn handle<'a>(&'a self,bucket: &str,object: &str,opt: crate::service::s3::GetObjectOption,mut out: tokio::sync::Mutex<std::pin::Pin<std::boxed::Box<(dyn crate::utils::io::PollWrite + Send + Unpin + 'a)>,>,>,) -> std::pin::Pin<Box<dyn 'a + Send + std::future::Future<Output = Result<(), String>>>>{Box::pin(async move {let mut l = out.lock().await;let _ = l.poll_write(b"hello").await.map_err(|err| {log::error!("write error {err}");});Ok(())})}}impl crate::service::s3::GetBucketLocationHandler for Target {}impl MultiUploadObjectHandler for Target {fn handle_create_session<'a>(&'a self,bucket: &'a str,key: &'a str,) -> std::pin::Pin<Box<dyn 'a + Send + std::future::Future<Output = Result<String, ()>>>>{Box::pin(async move { Ok("ffffff".to_string()) })}fn handle_upload_part<'a>(&'a self,bucket: &'a str,key: &'a str,upload_id: &'a str,part_number: u32,body: &'a mut (dyn tokio::io::AsyncRead + Unpin + Send),) -> std::pin::Pin<Box<dyn 'a + Send + std::future::Future<Output = Result<String, ()>>>>{Box::pin(async move {let mut buff = Vec::new();let size = body.read_to_end(&mut buff).await.map_err(|err| log::error!("read body error {err}"))?;println!("upload part upload_id={upload_id} part_number={part_number} bucket={bucket} key={key}\n{}",unsafe { std::str::from_boxed_utf8_unchecked((&buff[..size]).into()) });Ok("5d41402abc4b2a76b9719d911017c592".to_string())})}fn handle_complete<'a>(&'a self,bucket: &'a str,key: &'a str,upload_id: &'a str,//(etag,part number)data: &'a [(&'a str, u32)],opts: MultiUploadObjectCompleteOption,) -> std::pin::Pin<Box<dyn 'a + Send + std::future::Future<Output = Result<String, ()>>>>{Box::pin(async move { Ok("69a329523ce1ec88bf63061863d9cb14".to_string()) })}fn handle_abort<'a>(&'a self,bucket: &'a str,key: &'a str,upload_id: &'a str,) -> std::pin::Pin<Box<dyn 'a + Send + std::future::Future<Output = Result<(), ()>>>>{todo!()}}#[tokio::test]async fn test_server() -> Result<(), Box<dyn std::error::Error>> {let _ = tokio::fs::create_dir_all(".sys_bws").await;env_logger::builder().filter_level(log::LevelFilter::Info).init();let target = Arc::new(Target::default());let r = axum::Router::new().layer(axum::middleware::from_fn(super::handle_fn)).layer(axum::middleware::from_fn(super::handle_authorization_middleware,)).layer(axum::Extension(target.clone() as Arc<dyn PutObjectHandler + Send + Sync>)).layer(axum::Extension(target.clone() as Arc<dyn HeadHandler + Send + Sync>)).layer(axum::Extension(target.clone() as Arc<dyn ListBucketHandler + Send + Sync>)).layer(axum::Extension(target.clone() as Arc<dyn CreateBucketHandler + Send + Sync>)).layer(axum::Extension(target.clone() as Arc<dyn DeleteBucketHandler + Send + Sync>)).layer(axum::Extension(target.clone() as Arc<dyn DeleteObjectHandler + Send + Sync>)).layer(axum::Extension(target.clone() as Arc<dyn crate::authorization::AccesskeyStore + Send + Sync>)).layer(axum::Extension(target.clone() as Arc<dyn GetObjectHandler + Send + Sync>)).layer(axum::Extension(target.clone() as Arc<dyn GetBucketLocationHandler + Send + Sync>)).layer(axum::Extension(target.clone() as Arc<dyn MultiUploadObjectHandler + Send + Sync>));let l = tokio::net::TcpListener::bind("0.0.0.0:9900").await?;axum::serve(l, r).await?;Ok(())}

golang 客户端

package testsimport ("context""io""os""testing""github.com/minio/minio-go/v7""github.com/minio/minio-go/v7/pkg/credentials"
)func TestCreateBucket(t *testing.T) {creds, err := minio.New("127.0.0.1:9900", &minio.Options{Secure: false, Creds: credentials.NewStaticV4("root", "root12345", ""),Region: "us-east-1",})if err != nil {t.Fatal(err)}_, err = creds.BucketExists(context.Background(), "test")if err != nil {t.Fatal(err)}err = creds.MakeBucket(context.Background(), "itest", minio.MakeBucketOptions{})if err != nil {t.Fatal(err)}bkts, err := creds.ListBuckets(context.Background())if err != nil {t.Fatal(err)}t.Log(bkts)err = creds.RemoveBucket(context.Background(), "test")if err != nil {t.Fatal(err)}err = creds.RemoveObject(context.Background(), "test", "test", minio.RemoveObjectOptions{})if err != nil {t.Fatal(err)}err = os.WriteFile("test.txt", []byte("hello"), 0o644)if err != nil {t.Fatal(err)}fd, err := os.OpenFile("test.txt", os.O_RDONLY, 0)if err != nil {t.Fatal(err)}defer fd.Close()_, err = creds.PutObject(context.Background(), "test", "hello/world", fd, 5, minio.PutObjectOptions{})if err != nil {t.Fatal(err)}resp, err := creds.GetObject(context.Background(), "test", "test", minio.GetObjectOptions{})if err != nil {t.Fatal(err)}content, err := io.ReadAll(resp)if err != nil {t.Fatal(err)}if string(content) != "hello" {t.Fatal("expect hello got [" + string(content) + "]")}
}

s3 multipart 验证

package testsimport ("context""crypto/tls""fmt""log""net/http""os""testing""github.com/aws/aws-sdk-go-v2/aws""github.com/aws/aws-sdk-go-v2/config""github.com/aws/aws-sdk-go-v2/credentials""github.com/aws/aws-sdk-go-v2/service/s3""github.com/aws/aws-sdk-go-v2/service/s3/types"
)func TestS3Sdk(t *testing.T) {var (host      = "127.0.0.1"port      = 9900accesskey = "root"secretkey = "root12345"region    = "us-east-1")customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {if service == s3.ServiceID {return aws.Endpoint{URL:           fmt.Sprintf("http://%s:%d", host, port),SigningRegion: "us-east-1",}, nil}return aws.Endpoint{}, &aws.EndpointNotFoundError{}})// 加载 AWS 配置,指定自定义端点解析器cfg, err := config.LoadDefaultConfig(context.TODO(),config.WithEndpointResolverWithOptions(customResolver),config.WithHTTPClient(&http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true},},}),config.WithRegion(region),config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accesskey, secretkey, "")),)if err != nil {log.Fatalf("无法加载 AWS 配置: %v", err)}// 创建 S3 客户端cli := s3.NewFromConfig(cfg, func(o *s3.Options) {o.UsePathStyle = true})var (bucket = "itest"key    = "test.txt")fd, err := os.OpenFile("./test.txt", os.O_RDONLY, 0)if err != nil {t.Fatal(err)}defer fd.Close()out, err := cli.CreateMultipartUpload(context.Background(), &s3.CreateMultipartUploadInput{Bucket: &bucket,Key:    &key,})if err != nil {t.Fatal(err)}var upNo int32 = 1resp, err := cli.UploadPart(context.Background(), &s3.UploadPartInput{Bucket: &bucket, Key: &key, PartNumber: &upNo, UploadId: out.UploadId, Body: fd,})if err != nil {t.Fatal(err)}_, err = cli.CompleteMultipartUpload(context.Background(), &s3.CompleteMultipartUploadInput{Bucket: &bucket, Key: &key, UploadId: out.UploadId, MultipartUpload: &types.CompletedMultipartUpload{Parts: []types.CompletedPart{{ETag: resp.ETag, PartNumber: &upNo,},},},})if err != nil {t.Fatal(err)}
}

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

黑马点评系列问题之p70postman报错“服务器异常”

问题描述&#xff1a;在做这个位置的时候报错报错如下控制台报错如下解决根据控制台的报错来看&#xff0c;是​Redis模板未注入导致的空指针异常经过排查&#xff0c;原因是这里少了个Resource

Docker搭建Elasticsearch和Kibana

1.安装docker&#xff0c;确保正常启动 2.按步骤操作&#xff0c;这里的es是单节点的&#xff0c;如需多节点&#xff0c;需安装docker-compose进行yml文件的编写对容器进行编排 #docker拉镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.11.2 docker pul…

【深度学习笔记 Ⅰ】3 step by step (jupyter)

1. 导包 import numpy as np import h5py import matplotlib.pyplot as plt from testCases_v2 import * from dnn_utils_v2 import sigmoid, sigmoid_backward, relu, relu_backward% matplotlib inline plt.rcParams[figure.figsize] (5.0, 4.0) # set default size of plo…

前端流式渲染流式SSR详解

以下是关于前端流式渲染及流式SSR&#xff08;Server-Side Rendering&#xff09;的详细解析&#xff0c;结合核心原理、技术实现、优化策略及实际应用场景展开说明&#xff1a;⚙️ 一、流式渲染基础原理 核心概念 ◦ 流式渲染&#xff1a;数据通过分块传输&#xff08;Chunke…

Redis通用常见命令(含面试题)

核心命令get 根据key取valueset 把key和vlaue存入进去key和value本事上都是字符串&#xff0c;但在操作的时候可以不用加上引号""Redis作为键值对的结构&#xff0c;key固定就是字符串&#xff0c;value实际上会有多种类型&#xff08;字符串哈希表&#xff0c;列表&…

react/vue vite ts项目中,自动引入路由文件、 import.meta.glob动态引入路由 无需手动引入

utils/autoRouteHelper.ts // src/utils/autoRouteHelper.ts import { lazy } from "react"; import withLoading from "/components/router/withLoading";/** 自动生成某个文件夹下的子路由 */ interface RouteItem {path: string;element?: any;childre…

Linux简单了解历史

一、引言Linux是计算机经久不衰的一个计算机操作系统&#xff0c;在那个unix、苹果macOS、微软Window神仙打架的年代拼出自己的一席之地。最初的Linux完全就是一个unix的一个翻版&#xff0c;并且最开始的版本(0.01)就是一个差不多一万行简单到不能再简单的版本。那现在Linux是…

lua(xlua)基础知识点记录二

1. 关于lua函数传参参数在lua中给function传递参数的时候一般分为两种情况&#xff1a;值传递和引用传递值传递&#xff1a;值传递&#xff1a;数字、字符串、布尔值、nil等基本类型通过值传递。函数内部接收的是外部变量的副本&#xff0c;修改副本不会影响原始变量。 虽然我们…

分治算法---归并

1、排序数组 class Solution {vector<int> tmp; public:vector<int> sortArray(vector<int>& nums) {tmp.resize(nums.size());mergeSort(nums,0,nums.size() - 1);return nums;}void mergeSort(vector<int>& nums, int left , int right){if…

《计算机网络》实验报告三 UDP协议分析

目 录 1、实验目的 2、实验环境 3、实验内容 3.1 DNS查询UDP数据分析 3.2 QQ通信UDP数据分析 4、实验结果与分析 4.1 DNS查询UDP数据分析 4.2 QQ通信UDP数据分析 4.3 根据捕获的数据包&#xff0c;分析UDP的报文结构&#xff0c;将UDP协议中个字段名&#xff0c;字段…

Mysql 学习总结(90)—— Mysql 8.0 25 条性能优化实战指南

1. 内存配置优化 # my.cnf 关键内存参数 innodb_buffer_pool_size = 8G # 建议设置为物理内存的70-80% innodb_log_buffer_size = 64M # 日志缓冲区大小 query_cache_size = 0 # MySQL 8.0已移除,确保关闭 tmp_table_size = 256M # 临时表大小 max_…

嵌入式通信DQ单总线协议及UART(一)

文章目录一、DS18B20--DQ单总线1.1 单总线时序结构分析1.1.1 初始化&#xff1a;1.1.2 发送一位1.1.3 接收一位1.1.5 发送字节1.1.6 操作流程1.1.7 数据帧的理解1.1.8 数据帧的理解二、UART2.1 同步通信和异步通信2.2 双工通信2.3 串行通信常用数据校验方式2.3.1 奇偶检验2.3.2…

2025年SEVC SCI2区,利用增强粒子群算法(MR-MPSO)优化MapReduce效率和降低复杂性,深度解析+性能实测

目录1.摘要2.MapReduce-Modified Particle Swarm Optimization (MR-MPSO)3.结果展示4.参考文献5.算法辅导应用定制读者交流1.摘要 大数据的迅猛增长带来了严峻的数据管理挑战&#xff0c;尤其是在数据分布不均的庞大数据库中。由于这种不匹配&#xff0c;传统软件系统的效率大…

10-day07文本分类

文本分类使用场景文本分类任务 文本分类-机器学习贝叶斯算法应用在NLP中的应用 用贝叶斯公式处理文本分类任务 一个合理假设&#xff1a; 文本属于哪个类别&#xff0c;与文本中包含哪些词相关 任务&#xff1a; 知道文本中有哪些词&#xff0c;预测文本属于某类别的概率 贝叶斯…

Apache SeaTunnel详解与部署(最新版本2.3.11)

目录 一、概述 1.1、软件介绍 1.2、解决问题​ 1.3、软件特性​ 1.4、使用用户 1.5、产品对比 二、架构 2.1、运行流程 2.2、连接器​ 2.3、引擎 2.3.1、设计理念 2.3.2、集群管理​ 2.3.3、核心功能​ 2.3.4、引擎对比 三、软件部署 3.1、Docker部署 3.2、发…

pytorch | minist手写数据集

一、神经网络神经网络&#xff08;Neural Network&#xff09;是一种受生物神经系统&#xff08;尤其是大脑神经元连接方式&#xff09;启发的机器学习模型&#xff0c;是深度学习的核心基础。它通过模拟大量 “人工神经元” 的互联结构&#xff0c;学习数据中的复杂模式和规律…

[C/C++安全编程]_[中级]_[如何避免出现野指针]

场景 在Rust里不会出现野指针的情况&#xff0c;那么在C里能避免吗&#xff1f; 说明 野指针是指指向无效内存地址的指针&#xff0c;访问它会导致未定义行为&#xff0c;可能引发程序崩溃、数据损坏或安全漏洞。它是 C/C 等手动内存管理语言中的常见错误&#xff0c;而 Rust…

机器学习基础:从数据到智能的入门指南

一、何谓机器学习​ 在我们的日常生活中&#xff0c;机器学习的身影无处不在。当你打开购物软件&#xff0c;它总能精准推荐你可能喜欢的商品&#xff1b;当你解锁手机&#xff0c;人脸识别瞬间完成&#xff1b;当你使用语音助手&#xff0c;它能准确理解你的指令。这些背后&a…

steam游戏搬砖项目超完整版实操分享

大家好&#xff0c;我是阿阳&#xff0c;今天再次最详细的给大家综合全面的分析讲解下steam搬砖&#xff0c;可以点击后面跳转往期文章了再次解下阿阳网客&#xff1a;关于steam游戏搬砖项目&#xff0c;我想说&#xff01;最早是21年5月份公开朋友圈&#xff0c;初次接触是在2…

vue2 面试题及详细答案150道(21 - 40)

《前后端面试题》专栏集合了前后端各个知识模块的面试题&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…