Introduction
Cloudflare R2はAmazon S3互換のオブジェクトストレージです。
エグレス料金がかからず、非常にリーズナブルです。
今回はCloudflare WorkersからR2にアクセスしてみます。
Environment
今回試した環境は以下のとおりです。
- MacBook Pro (13-inch, M1, 2020)
- OS : MacOS 12.4
- Node : v18.2.0
- wrangler : 2.8.1
Cloudflareのアカウントと
wranglerのインストールは設定済みとします。
Setup
まずはR2のセットアップです。
CloudflareのCLIツールであるwranglerを使います。
もしなければnpm or yarnでインストール。
% npm install -g wrangler
% wrangler --version
⛅️ wrangler 2.8.1
-------------------
このあとCLIからいろいろ操作するので、
ログインします。
% wrangler login
wranglerでR2にバケットを作成します。
% wrangler r2 bucket create <YOUR BUCKET NAME>
⛅️ wrangler 2.8.1
-------------------
Creating bucket <YOUR BUCKET NAME>.
ちなみに、バケット名に「_」使おうとしたらエラーになったので注意。
これでCloudflareのダッシュボードでもバケットが確認できるし、
↓のコマンドで一覧を取得することもできます。
% wrangler r2 bucket list
[
・・・
{
"name": <YOUR BUCKET NAME>,
"creation_date": "2023-01-24T04:12:06.019Z"
},
・・・
]
Create Workers with TypeScript
ではTypeScriptでCloudflare WorkersからR2にアクセスしてみます。
wrangler initでWokersの雛形を作成します。
% mkdir ts-r2 && cd ts-r2
% wrangler init --yes
wrangler.tomlにR2のバケット定義を記述します。
[[r2_buckets]]
binding = 'MY_BUCKET'
bucket_name = '<YOUR BUCKET NAME>'
preview_bucket_name = '<YOUR BUCKET NAME>' #開発用
index.tsを、ここを参考に実装します。
//index.ts
interface Env {
MY_BUCKET: R2Bucket
}
export default {
async fetch(request:Request, env:Env) {
const url = new URL(request.url);
const key = url.pathname.slice(1);
switch (request.method) {
case 'PUT':
await env.MY_BUCKET.put(key, request.body);
return new Response(`Put ${key} successfully!`);
case 'GET':
const object = await env.MY_BUCKET.get(key);
if (object === null) {
return new Response('Object Not Found', { status: 404 });
}
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set('etag', object.httpEtag);
return new Response(object.body, {
headers,
});
default:
return new Response('Method Not Allowed', {
status: 405,
headers: {
Allow: 'PUT, GET, DELETE',
},
});
}
},
};
npm startで起動します。
% npm start
> ts-r2@0.0.0 start
> wrangler dev
⛅️ wrangler 2.8.1
-------------------
Your worker has access to the following bindings:
- R2 Buckets:
- MY_BUCKET: <YOUR BUCKET NAME>
⬣ Listening at http://0.0.0.0:8787
- http://127.0.0.1:8787
- http://192.168.11.4:8787
- http://192.168.105.1:8787
Total Upload: 1.02 KiB / gzip: 0.46 KiB
╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ [b] open a browser, [d] open Devtools, [l] turn on local mode, clear console, [x] to exit │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯
curlで適当なpngファイルを指定してアップロードします。
% curl -XPUT --data-binary "@/path/your/image/something.png" http://127.0.0.1:8787/mykey
この時点でダッシュボードをみると、バケットにファイルが登録されてます。
アップロードが成功したら、ブラウザで
http://127.0.0.1:8787/mykey
にアクセスしてみます。
ブラウザでR2からファイルを取得して表示できれば成功です。
Create Workers with Rust
では次に、Rustを使ってWorkersを実装してみましょう。
npm initでRust用Workersの雛形を生成します。
% npm init cloudflare <Project Name> worker-rust
% cd <Project Name>
さきほどと同じく、wrangler.tomlにR2のバケット定義を記述し、
ほかの部分も下記のように少し書き換えます。
[vars]
WORKERS_RS_VERSION = "*"
[build]
command = "cargo install -q worker-build && worker-build --release"
現在では、workersのcrateはGithubから直接もってこないと
R2が使えません。
なので、↓のようにGithubにあるworkers-rsのmainブランチを指定します。
・・・
[dependencies]
worker = { git = "https://github.com/cloudflare/workers-rs.git", branch = "main"}
・・・
lib.rsを下記のように修正。
Rust版ではR2からオブジェクトを取得する処理だけ実装します。
※修正した部分だけ抜粋
//lib.rs
mod r2;
pub struct SharedData {
}
#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
・・・
let data = SharedData {};
let router = Router::with_data(data);
router
.get_async("/r2/get/:key", r2::get)
.run(req, env)
.await
}
そしてr2.rsの実装です。
/r2/get/:keyにGETでアクセスすると、パスにはいっているキー名で
R2からオブジェクト(ここではpng決め打ち)を取得してレスポンスとして返します。
use worker::*;
use crate::SharedData;
pub async fn get(_req: Request, ctx: RouteContext<SharedData>) -> Result<Response> {
//bindしたバケット名を指定
let bucket = ctx.bucket("MY_BUCKET")?;
//パスからキー名を取得
let key = ctx.param("key").unwrap();
let item = bucket.get(key).execute().await?.unwrap();
let item_body = item.body().unwrap();
let bytes = item_body.bytes().await.unwrap();
let response = Response::from_bytes(bytes)?;
let mut headers = Headers::new();
headers.set("content-type","image/png")?;
Ok(response.with_headers(headers))
}
wrangler devコマンドで起動して、動作確認してみます。
% wrangler dev
⛅️ wrangler 2.8.1
-------------------
Running custom build: cargo install -q worker-build && worker-build --release
[INFO]: ? Checking for the Wasm target...
[INFO]: ? Compiling to Wasm...
[INFO]: ⬇️ Installing wasm-bindgen...
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: ✨ Done in 1.15s
⚡ Done in 10ms
・・・
╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ [b] open a browser, [d] open Devtools, [l] turn on local mode, clear console, [x] to exit │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯
ブラウザで下記URLにアクセスしてみます。
http://127.0.0.1:8787/r2/get/<R2のファイル名>
R2に登録した画像が表示されれば、Rust版でも動作確認OKです。