bws-rs:Rust 編寫的 S3 協議網關框架,支持靈活后端接入

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&…