5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-10-21

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の対応をしてくれないだろうか…

5
2
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
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?