crate作成の経緯
今まで利用していた「cloudflare-r2-rs」という Cloudflare R2 の Object を操作する crate が、おそらくこの aws SDKの更新によって正常に動作しなくなってしまいました。なので、この crate に似た crate を自作したというお話です (私の作成した crateの方が機能は少ないです)。(pull request にしなかった理由は、私の Rust に対する理解度が不足しており、cloudflare-r2-rs のコードの一部がわからなかったためです...)
作成したcrate
作成したcrate「cf-r2-sdk」は、以下のリンクから確認できます!
GitHub Repository
問題の原因と思われるものと解決
AWSが最近SDKを更新したことによって、CRC32チェックサムがデフォルトで有効となったのが問題の原因だと思われます (推測です) 。Cloudflareのドキュメントには、解決策の一つにS3Client config
に以下の設定を追加すると解決すると書かれていたので、指示通り追加することで解決しました。
requestChecksumCalculation: "WHEN_REQUIRED",
responseChecksumValidation: "WHEN_REQUIRED",
引用元: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/
具体的には、以下のようなプログラムを書きました。
let config = aws_sdk_s3::config::Builder::new()
.credentials_provider(credentials)
.region(Region::new(self.region.clone()))
.endpoint_url(&self.endpoint)
.set_request_checksum_calculation(Some(RequestChecksumCalculation::WhenRequired))
.set_response_checksum_validation(Some(ResponseChecksumValidation::WhenRequired))
.clone()
.build();
コード参考元: https://github.com/Songmu/r2sync (Owner: Songmu)
作成したcrate「cf-r2-sdk」の使い方
使い方は docs.rs にもある程度書いていますが、記事にも使い方を書いておこうと思います。
Sample Repositoryは、以下のリンクから見ることができます。
- 必要なcrateを追加
cargo add cf-r2-sdk
cargo add dotenvy
cargo add tokio -F full
-
BUCKET_NAME
、ENDPOINT_URL
、ACCESS_KEY_ID
、SECRET_ACCESS_KEY
、(REGION
) を .env ファイルに記述しておきます。 (.env ファイルは、必ず .gitignore ファイルに追記して push しないように注意してください)
BUCKET_NAME=
ACCESS_KEY_ID=
SECRET_ACCESS_KEY=
ENDPOINT_URL=
REGION=auto
ENDPOINT_URL、ACCESS_KEY_ID、SECRET_ACCESS_KEY の取得方法はこちら
-
cf-r2-sdk::builder::Builer
で、aws_sdk_s3::Client
を作成します。
use cf_r2_sdk::builder::Builder;
use dotenvy::dotenv;
use std::env;
#[tokio::main(flavor = "current_thread")]
async fn main() {
// load .env file
dotenv().expect(".env file not found.");
// insert a environment variable
let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
let endpoint_url: String =
env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
let access_key_id: String =
env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
let secret_access_key: String =
env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
let region: String = env::var("REGION").expect("REGION not found in .env file.");
// 1.`cf-r2-sdk::builder::Builer` で、`aws_sdk_s3::Client` を作成する
let object: cf_r2_sdk::operator::Operator = Builder::new()
.set_bucket_name(bucket_name)
.set_access_key_id(access_key_id)
.set_secret_access_key(secret_access_key)
.set_endpoint(endpoint_url)
.set_region(region)
.create_client();
}
-
R2 の Object Strage を操作します。
- バイナリデータのアップロード
例
use cf_r2_sdk::builder::Builder;
use dotenvy::dotenv;
use std::env;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), cf_r2_sdk::error::OperationError> {
// load .env file
dotenv().expect(".env file not found.");
// insert a environment variable
let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
let endpoint_url: String =
env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
let access_key_id: String =
env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
let secret_access_key: String =
env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
let region: String = env::var("REGION").expect("REGION not found in .env file.");
let object: cf_r2_sdk::operator::Operator = Builder::new()
.set_bucket_name(bucket_name)
.set_access_key_id(access_key_id)
.set_secret_access_key(secret_access_key)
.set_endpoint(endpoint_url)
.set_region(region)
.create_client();
// upload binary data
object
.upload_binary("sample.txt", "test/plain", b"Hello, World!")
.await?;
Ok(())
}
- ファイルのアップロード
例
use cf_r2_sdk::builder::Builder;
use dotenvy::dotenv;
use std::env;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), cf_r2_sdk::error::OperationError> {
// load .env file
dotenv().expect(".env file not found.");
// insert a environment variable
let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
let endpoint_url: String =
env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
let access_key_id: String =
env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
let secret_access_key: String =
env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
let region: String = env::var("REGION").expect("REGION not found in .env file.");
let object: cf_r2_sdk::operator::Operator = Builder::new()
.set_bucket_name(bucket_name)
.set_access_key_id(access_key_id)
.set_secret_access_key(secret_access_key)
.set_endpoint(endpoint_url)
.set_region(region)
.create_client();
// upload file
object
.upload_file("sample.jpg", "image/jpeg", "./data/sample.jpg")
.await?;
Ok(())
}
- バイナリデータのダウンロード
例
use cf_r2_sdk::builder::Builder;
use dotenvy::dotenv;
use std::env;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), cf_r2_sdk::error::OperationError> {
// load .env file
dotenv().expect(".env file not found.");
// insert a environment variable
let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
let endpoint_url: String =
env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
let access_key_id: String =
env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
let secret_access_key: String =
env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
let region: String = env::var("REGION").expect("REGION not found in .env file.");
let object: cf_r2_sdk::operator::Operator = Builder::new()
.set_bucket_name(bucket_name)
.set_access_key_id(access_key_id)
.set_secret_access_key(secret_access_key)
.set_endpoint(endpoint_url)
.set_region(region)
.create_client();
object
.upload_binary("sample.txt", "test/plain", b"Hello, World!")
.await?;
// download binary data
let bin: Vec<u8> = object.download("sample.txt").await?;
println!("{:?}", bin);
Ok(())
}
- ファイルの削除
例
use cf_r2_sdk::builder::Builder;
use dotenvy::dotenv;
use std::env;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), cf_r2_sdk::error::OperationError> {
// load .env file
dotenv().expect(".env file not found.");
// insert a environment variable
let bucket_name = env::var("BUCKET_NAME").expect("BUCKET_NAME not found in .env file.");
let endpoint_url: String =
env::var("ENDPOINT_URL").expect("ENDPOINT_URL not found in .env file.");
let access_key_id: String =
env::var("ACCESS_KEY_ID").expect("ACCESS_KEY_ID not found in .env file.");
let secret_access_key: String =
env::var("SECRET_ACCESS_KEY").expect("SECRET_ACCESS_KEY not found in .env file.");
let region: String = env::var("REGION").expect("REGION not found in .env file.");
let object: cf_r2_sdk::operator::Operator = Builder::new()
.set_bucket_name(bucket_name)
.set_access_key_id(access_key_id)
.set_secret_access_key(secret_access_key)
.set_endpoint(endpoint_url)
.set_region(region)
.create_client();
let _ = object
.upload_binary("sample.txt", "test/plain", b"Hello, World!")
.await;
// delete file
object
.delete("sample.txt")
.await?;
Ok(())
}
終わりに
本当は cloudflare-r2-rs に pull request するべきなのでしょうが、私の Rust に対する理解度が不足しており、cloudflare-r2-rs のコードリーディングに失敗したという裏話があります。最終的に、Rust の crate の作成方法を学習できて、とてもいい機会になりました!