Cloudflare R2 で ActiveStorage のダイレクトアップロード機能を利用するには

takeyuweb2023/01/25に公開

以前、Cloudflare R2をActiveStorageで使う方法を紹介しました。
https://zenn.dev/takeyuwebinc/articles/e4d47ff381b530

オープンβ当時は署名付きURLに対応していなかったため、ダイレクトアップロードは利用できませんでした。

2022年9月の一般公開で署名付きURLに対応したため、ActiveStorageのダイレクトアップロード機能が使えるようになりました。

バケット全体へのアクセスを許可することなく、ユーザーがファイルをアップロードまたは共有できるように、署名付きURLを作成します。
https://blog.cloudflare.com/ja-jp/r2-ga-ja-jp/

本記事では実際に利用するための設定方法について紹介します。

Cloudflare R2をActiveStorageのバックエンドとして利用する

バケットの作成とActiveStorageの設定

https://zenn.dev/takeyuwebinc/articles/e4d47ff381b530#利用方法

ダイレクトアップロードの設定

@rails/activestorage のインストール

@rails/activestorageを利用します。

私のサンプルでは importmap-rails を使っているので

$ bundle exec bin/importmap pin @rails/activestorage
app/javascripts/application.js
// 追記
import * as ActiveStorage from "@rails/activestorage"
ActiveStorage.start()

フォームテンプレートの修正

direct_upload: true を指定します。

<%= form.file_field :image, direct_upload: true %>

R2 の CORS 設定

ブラウザからR2バケットへのPUTを許可するために、R2バケットのCORS設定が必要です。

現在のところR2の管理画面では設定できないようなので、AWS S3クライアントを使います。設定内容はRailsガイドの内容を参考にしました。

$ aws configure --profile <適当なプロファイル名>
AWS Access Key ID [None]: <R2の管理画面で作成したAPI TokenのAccess Key ID>
AWS Secret Access Key [None]: <R2の管理画面で作成したAPI TokenのSecret Access Key>
Default region name [None]:
Default output format [None]: json

$ aws s3api put-bucket-cors \
--bucket <対象のバケット名> \
--cors-configuration '{"CORSRules" : [{"AllowedHeaders":["*"],"AllowedMethods":["PUT"],"AllowedOrigins":["*"],"ExposeHeaders":["Origin", "Content-Type", "Content-MD5", "Content-Disposition"],"MaxAgeSeconds":3600}]}' \
--profile <適当なプロファイル名> \
--endpoint-url https://<R2の管理画面で確認できるAccount ID>.r2.cloudflarestorage.com

$ aws s3api get-bucket-cors \
--bucket <対象のバケット名> \
--profile <適当なプロファイル名> \
--endpoint-url https://<R2の管理画面で確認できるAccount ID>.r2.cloudflarestorage.com
{
    "CORSRules": [
        {
            "AllowedHeaders": [
                "*"
            ],
            "AllowedMethods": [
                "PUT"
            ],
            "AllowedOrigins": [
                "*"
            ],
            "MaxAgeSeconds": 3600
        }
    ]
}

R2ではAllowedHeadersのワイルドカードが指定できないという話を見かけたのですが、少なくとも2023年1月25日現在は利用できています。

サンプルコード

https://github.com/takeyuweb/rails-activestorage-r2

ダイレクトアップロードの模様

ダイレクトアップロードの際のRailsのログ

Started POST "/posts" for 172.20.0.1 at 2023-01-25 04:28:59 +0000
Cannot render console from 172.20.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
Processing by PostsController#create as TURBO_STREAM
  Parameters: {"authenticity_token"=>"[FILTERED]", "post"=>{"name"=>"direct", "image"=>"eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBFZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--7e3fa302263afa5130b7be1eb620a7941a2dce8b"}, "commit"=>"Create Post"}
  ActiveStorage::Blob Load (0.1ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ?  [["id", 13], ["LIMIT", 1]]
  ↳ app/controllers/posts_controller.rb:24:in `create'
  TRANSACTION (0.0ms)  begin transaction
  ↳ app/controllers/posts_controller.rb:27:in `block in create'
  Post Create (0.2ms)  INSERT INTO "posts" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "direct"], ["created_at", "2023-01-25 04:29:00.108684"], ["updated_at", "2023-01-25 04:29:00.108684"]]
  ↳ app/controllers/posts_controller.rb:27:in `block in create'
  ActiveStorage::Blob Load (0.0ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" INNER JOIN "active_storage_attachments" ON "active_storage_blobs"."id" = "active_storage_attachments"."blob_id" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 6], ["record_type", "Post"], ["name", "image"], ["LIMIT", 1]]
  ↳ app/controllers/posts_controller.rb:27:in `block in create'
  ActiveStorage::Attachment Load (0.0ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 6], ["record_type", "Post"], ["name", "image"], ["LIMIT", 1]]
  ↳ app/controllers/posts_controller.rb:27:in `block in create'
  ActiveStorage::Blob Update (0.1ms)  UPDATE "active_storage_blobs" SET "metadata" = ? WHERE "active_storage_blobs"."id" = ?  [["metadata", "{\"identified\":true}"], ["id", 13]]
  ↳ app/controllers/posts_controller.rb:27:in `block in create'
  ActiveStorage::Attachment Create (0.1ms)  INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES (?, ?, ?, ?, ?)  [["name", "image"], ["record_type", "Post"], ["record_id", 6], ["blob_id", 13], ["created_at", "2023-01-25 04:29:00.115555"]]
  ↳ app/controllers/posts_controller.rb:27:in `block in create'
  Post Update (0.0ms)  UPDATE "posts" SET "updated_at" = ? WHERE "posts"."id" = ?  [["updated_at", "2023-01-25 04:29:00.116323"], ["id", 6]]
  ↳ app/controllers/posts_controller.rb:27:in `block in create'
  TRANSACTION (3.4ms)  commit transaction
  ↳ app/controllers/posts_controller.rb:27:in `block in create'
[ActiveJob] Enqueued ActiveStorage::AnalyzeJob (Job ID: cfdb04b6-d03e-4ffd-a57e-874f682086d9) to Async(default) with arguments: #<GlobalID:0x00007f1cf32a9a38 @uri=#<URI::GID gid://my-app/ActiveStorage::Blob/13>>
Redirected to http://localhost:3000/posts/6
Completed 302 Found in 189ms (ActiveRecord: 4.0ms | Allocations: 12309)

まとめ

R2バックエンドでもダイレクトアップロードが利用できるようになっていました。ダイレクトアップロードを利用することで、Railsサーバーを介さずにブラウザから直接アップロードできるので、サーバー負荷の軽減などに役立ちますね。

タケユー・ウェブ株式会社

Ruby on Rails や AWS が得意なWebサービス受託開発会社です。 中小規模のWebサービスの新規開発の他、他の個人開発者などから引き継いで保守運用を行ったりしています。 新規開発、お手伝いや顧問、レガシーなRailsプロジェクトの保守など、ニーズにあわせて対応できます。ご相談ください。

Discussion

ログインするとコメントできます