Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

@chchu

Rails の ActiveStorage を S3 で使ったらPublicなURLが取れなかった

ActiveStorageの必要性

長年愛用していたRailsのファイルアップロードgemであるpaperclipがDeprecationとなってしまったため、仕方なくActiveStorageを使ってみたところ、PublicなURL(S3への直接のURL)が取得できませんでした。
仕方なく別途S3用のクラウドサービスを追加して対応しました。

環境

  • Rails 5.2.1
  • ActiveStorage 5.2.1

問題点

ActiveStorageではデフォルトでクラウドサービスへのアップロードに対応しており
- AWS S3
- GCP Cloud Storage
- Azure Storage
へのファイルアップロードは設定ファイルを書くだけで対応できます。
ただし、ファイルアップロードの際にアクセス権限を指定することができずS3ではprivateでのファイルアップロードとなります。

下記がgem内のS3サービスのファイルです。
ファイルアップロード処理(put)で渡しているoptionにacl(アクセス権限)が含まれていません。
@upload_options への設定の渡し方を調べればなんとかなるとは思っていましたが、次に調べたURLの取得でファイル自体を変更しないといけないことが判明しました。

activestorage-5.2.1/lib/active_storage/service/s3_service.rb
    def initialize(bucket:, upload: {}, **options)
      @client = Aws::S3::Resource.new(**options)
      @bucket = @client.bucket(bucket)

      @upload_options = upload
    end

    def upload(key, io, checksum: nil)
      instrument :upload, key: key, checksum: checksum do
        begin
          object_for(key).put(upload_options.merge(body: io, content_md5: checksum))
        rescue Aws::S3::Errors::BadDigest
          raise ActiveStorage::IntegrityError
        end
      end
    end

ファイルのURLを取得するときに事前署名URLpresigned_urlとなり、URLを使い回すことや長期間使用することが想定されていません。

activestorage-5.2.1/lib/active_storage/service/s3_service.rb
    def url(key, expires_in:, filename:, disposition:, content_type:)
      instrument :url, key: key do |payload|
        generated_url = object_for(key).presigned_url :get, expires_in: expires_in.to_i,
          response_content_disposition: content_disposition_with(type: disposition, filename: filename),
          response_content_type: content_type

        payload[:url] = generated_url

        generated_url
      end
    end

ちなみに、expires_inは最大1週間指定できるようですが、今回はOGPのog:imageに指定するURLに使用したかったのでPublicなURLじゃないと駄目です。

新S3サービスのファイルを作成

以下に既存の3サービスファイルからの修正点を記載します。

app/lib/active_storage/service/s3_service.rb
    def upload(key, io, checksum: nil)
      instrument :upload, key: key, checksum: checksum do
        begin
          # aclでpublic-readを指定して必ず公開設定に変更.
          object_for(key).put(upload_options.merge(body: io, content_md5: checksum, acl: 'public-read'))
        rescue Aws::S3::Errors::BadDigest
          raise ActiveStorage::IntegrityError
        end
      end
    end

# ... 略

   def url(key, expires_in:, filename:, disposition:, content_type:)
      instrument :url, key: key do |payload|
        # public_url で署名なしのPublic URLを取得.
        generated_url = object_for(key).public_url
        payload[:url] = generated_url
        generated_url
      end
    end

注意点

  • S3へアップロードするファイルすべてがpublic-readになります。privateにアップロードするファイルとの共存はできなくなります。
  • ActiveStorage gemをアップデートする場合は、互換性がなくなる可能性があるため、ベースのS3ファイルとの差分をチェックすること

はやくActiveStorage自体でPublic URLの対応をしてくれないだろうか…

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
2
Help us understand the problem. What are the problem?