👌

Cloudflare Workers からR2を操作する

2023/02/11に公開

この記事ではWorkersからオブジェクトストレージであるR2を操作してみます。
R2の特徴は、エグレス料金(ダウンロードなどの下り通信量)が無料でS3互換APIを備えています。またアクセスをWorkers環境に限定させることでクレデンシャル管理をWorkersスクリプトにオフロードし、複雑な認証機構を作りこめることが特徴です。
料金体系も以下の通り安価に設定されています。

それでは早速触っていきます。Wranglerの環境がまだ未設定の方はこの記事を参考に環境を作ってください。タイトルにはWindowsと入っていますがMacでも基本手順は同じです。

  1. Projectのinit
    以下のコマンドでWorkers プロジェクトを作成します。
    wrangler init firstr2test
    対話プロンプトは以下を参考に入力してください。

実行が完了したら、cd firstr2testでディレクトリを移動したのち、一度wrangler publishで"Hello world"がブラウザから表示されるか確認しておきましょう。

  1. R2バケットの作成
    (R2は従量課金制のため、有料プランに申し込んでおいていただく必要があります。このハンズオンでは料金は発生しません)
    以下のコマンドでバケットを作成します。
    wrangler r2 bucket create firstr2test
    マネージメントコンソールを見ると以下の通りR2バケットができています。

  2. Workersとのバインディング
    現時点では作成したWorkersとR2バケットは個別に存在しており、これを紐付ける作業を行う必要があります。まずwrangler whoamiを実行して、アカウントIDを取得しておきます。
    "Account ID"が表示されるはずですので、コピーしておいてください。

次に適当なエディタでwrangler.tomlを開き、以下の行を追記し保存します。

account_id = "a67e14daa5f8dceeb91fe5449ba496eb"
workers_dev = true

[[r2_buckets]]
binding = 'MY_BUCKET'
bucket_name = 'firstr2test'

次にsrc/index.jsを以下のコードで置換して保存します。

const ALLOW_LIST = ['test.txt'];

const hasValidHeader = (request, env) => {
  return request.headers.get('X-Custom-Auth-Key') === env.AUTH_KEY_SECRET;
};

function authorizeRequest(request, env, key) {
  switch (request.method) {
    case 'PUT':
    case 'DELETE':
      return hasValidHeader(request, env);
    case 'GET':
      return ALLOW_LIST.includes(key);
    default:
      return false;
  }
}


export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const key = url.pathname.slice(1);


   if (!authorizeRequest(request, env, key)) {
      return new Response('Forbidden', { status: 403 });
    }

    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,
        });
      case 'DELETE':
        await env.MY_BUCKET.delete(key);
        return new Response('Deleted!');

      default:
        return new Response('Method Not Allowed', {
          status: 405,
          headers: {
            Allow: 'PUT, GET, DELETE',
          },
        });
    }
  },
};

このコードではリクエスト時にX-Custom-Auth-Keyヘッダで正しいクレデンシャル(AUTH_KEY_SECRET)が渡されたときはPUT,GETは実行が許可されます。
一方GETは誰でも実行可能ですが、test.txtに対するオペレーションのみが許可されています。
wrangler secret put AUTH_KEY_SECRETを実行しクレデンシャルを作成します。文字列であればなんでもいいです。

wrangler publishを実行しWorkersをデプロイします。
デプロイが完了した後、ブラウザでアクセスすると"Forbidden"が表示されるはずです。先ほどtest.txtに対してのみGETを許可したためです。
マネージメントコンソールで"firstr2test"のWorkers画面にアクセスし"Settings"→"Variables"を確認すると以下のようにクレデンシャルがセットされていることがわかります。

これから実行するサンプルでは、この値とcurlでXヘッダ(X-Custom-Auth-Key)にセットした値が合致した場合のもオペレーションが実行され、それ以外は405エラーが戻るようになっています。もちろん追加認証ロジックをコードで作りこんでいただくことが可能です。
画面を下に進むとR2バケットがバインディングされていることがわかります。これによりWorkersのコードから"MY_BUCKET"として認識し操作が可能になります。

  1. テスト読み書き
    それでは早速テストを行ってみます。(Windows/PowerShell環境の人はcurlではなくcurl.exeとしてください。curlで実行されるものはcurlではなく別物です。何を言っているのか(ry))

curl https://<your-workers-url>/test.txt -X PUT --header "X-Custom-Auth-Key: ****" --data-binary 'test'
"Put test.txt successfully!"と表示されれば完了です。"<your-workers-url>"は皆さんの環境固有の値、"****"は先ほど作成したクレデンシャルです。

curl https://<your-workers-url>/test.txtを実行すれば"test"と表示されるはずです。

今度は"test"を"test2"にして実行してみます。
curl https://<your-workers-url>/test2.txt -X PUT --header "X-Custom-Auth-Key: ****" --data-binary 'test2'
curl https://<your-workers-url>/test.txt2
書き込みは成功しますがGETは"Forbidden"で失敗するはずです。これはスクリプト内部でGETメソッドは"text.txt"にのみ許可されているためです。

ではマネージメントコンソールでR2バケットを見ると正しくファイルが2つ作成されています。これはPUTメソッドはファイル名に限定せずクレデンシャルが合致すれば許可されているためです。

  1. Wrangler経由のデータ操作
    それでは最後にWrangler経由のデータ操作を試してみます。ちょうど"package.json"というファイルが実行ディレクトリにあるのでこれをアップロードしてみます。
    wrangler r2 object put firstr2test/package.json --file=package.json
    無事ファイルがアップロードされています。

    Getは以下です。
    wrangler r2 object get firstr2test/package.json --file package.json.dl
    今度はスクリプトを使っていないのでGETがブロックされないことがわかります。
    削除(DELETE)は以下です。
    wrangler r2 object delete firstr2test/package.json

備考:
ここで紹介したKVと異なりR2は強い一貫性が提供されます。オペレーション結果は直ちに各エッジ経由で確認が可能となります。しかしながらWorkers経由の場合、複数のエッジで同じオブジェクトに対する異なるPUTやPOSTオペレーションが発生することが考えられます。この場合、後のオペレーションが最終結果として成立しますのでご注意ください。
この記事ではキャッシュは利用していませんが、商用環境ではオペレーションの高速化(ユーザーの読み込み高速化)のためにオブジェクトをキャッシュするケースが考えられます。この場合、当然一貫性モデルは崩れますのでご注意ください。(Workers経由のアクセスではキャッシュを使用しません)

おつかれさまでした:
いかがでしょうか。R2には今日触ったWorkersAPIのほかにS3互換APIも存在しています。是非試してみてください。

Discussion