0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloudflare R2のObjectを操作するcrate「cf-r2-sdk」を作成した話

Last updated at Posted at 2025-01-18

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は、以下のリンクから見ることができます。

  1. 必要なcrateを追加
cargo add cf-r2-sdk
cargo add dotenvy
cargo add tokio -F full
  1. BUCKET_NAMEENDPOINT_URLACCESS_KEY_IDSECRET_ACCESS_KEY、(REGION) を .env ファイルに記述しておきます。 (.env ファイルは、必ず .gitignore ファイルに追記して push しないように注意してください)
.env
BUCKET_NAME=
ACCESS_KEY_ID=
SECRET_ACCESS_KEY=
ENDPOINT_URL=
REGION=auto
ENDPOINT_URL、ACCESS_KEY_ID、SECRET_ACCESS_KEY の取得方法はこちら
  1. R2 Object Strage > Overview に移動
  2. API > Manage API tokens をクリックします。
    image1.png
  3. Create API Token をクリックします。
    image2.png
  4. Permissionsを Object Read & Write にして、Create API tokenをクリックします。
    image3.png
  5. ACCESS Key ID、Secret Access Key、endpoints を取得したら、Finish で完了です。
    image4.png
  1. 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();
}
  1. 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 の作成方法を学習できて、とてもいい機会になりました!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?