12
5

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 1 year has passed since last update.

AWS S3へのアップロード方法:ダイレクトアップロードを使って効率的に静的コンテンツをアップロードしよう

Posted at

AWS S3へのアップロード方法

静的なコンテンツをAWS S3へアップロードするのはよくある手法です。
例えば画像や動画をS3へアップロードすることがあると思います。
その中でも、ダイレクトアップロードという方法があります。ダイレクトアップロード機能を使うとサーバーに依存せずにアップロードが出来ます。

従来の方法

S3へのアップロードはサーバー側で処理を行い、DBに関連付けを記録することが一般的でした。この方法ではシンプルにアップロードするファイルをPOSTデータとしてWebサーバーへ送信し、WebサーバーでS3へのアップロードを行い、アップロードされたファイルの関連付けをDBへ記録しています。
従来のS3.jpg

ダイレクトアップロード

最近ではフロントエンドとバックエンドの分離が進んで来ています。このためダイレクトアップロードという方法でアップロードという手法を用いてS3へアップロードする方法が主流になってきています。
この方法では事前にアップロードに必要な情報を取得しておき、アップロード自体はフロントエンドのJavaScriptのAjax処理に任せて、アップロードした後に関連付け情報のみをサーバー側で行うという構成になります。
S3へのアップロードをサーバー側に依存させずに、フロントで直接アップロードできるのがダイレクトアップロードという機能になります。
S3ダイレクトアップロード.jpg

実装

今回はバックエンドにRailsAPIサーバーを建てます。
Railsで標準で使えるActiveStorageを使います。
以下のページを参考に導入してください。

Userモデルに1:Nで紐づく画像がアップロードできることを想定します。

User.rb
class User < ActiveRecord::Base
  # Active Storage
  has_many_attached :images
end

ActiveStorageを使うとUserモデルと紐づく下記のテーブルが作成されます。
S3へアップロードした画像への関連付けはこのテーブルに保存されます。

  • active_storage_variant_records
  • active_storage_blobs
  • active_storage_attachments

1. アップロードに必要な情報を返すAPI

フロントエンドから直接アップロードするためには、事前にS3への署名付きURLの発行が必要になります。
Screenshot 2023-04-29 at 13.18.40.png

署名付きURLの発行には以下のパラメータが必要になります。これらはフロントエンド側の処理で計算を行いパラメータとして含める必要があります。
Screenshot 2023-04-29 at 13.27.24.png

APIサーバー側はレスポンスとしてS3へアプリケーションするのに必要な署名付きURLを返却します。さらにblob_signed_idというアップロード後にDBと関連付けするための情報も必要になります。
Screenshot 2023-04-29 at 13.28.58.png

もう少し詳しくレスポンスの内容を見てみましょう。
blob_signed_idは先述の通り、このあとアップロードが成功した後にDBと紐づけるために必要なIDになります。
direct_upload_urlはS3への署名付きURLになります。フロントエンドでこのURLに向けてAjax通信でファイルを送信する必要があります。
また、アップロードにはdirect_upload_headersで指定したパラメータをHeader情報に含める必要があります。
Screenshot 2023-04-29 at 13.31.40.png

このAPIから取得できた情報で実際にはフロントエンド側でアップロードの処理を行いますが、crulコマンドを使ってアップロードのテストを行う事ができます。

curl -X PUT -T "/Users/XX/Desktop/test.png" \
-H Content-Type:"image/png" \
-H Content-MD5:"n4UCchK/eHAXIsO5QVWVCA==" \
-H Content-Disposition:"inline; filename=\"test.png\"; filename*=UTF-8''test.png" \
"http://XXX/activestorage/blobs/uploaded/users/1/user_images/u6dssto3bvwq72bz3bqcztom0f36?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minio%2F20221009%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221009T122108Z&X-Amz-Expires=300&X-Amz-SignedHeaders=content-length%3Bcontent-md5%3Bcontent-type%3Bhost&X-Amz-Signature=11caec4677467f3c3410c8e5dceb924330ea967eb97dafff040d3f4e2acb3a0d"

(実装例)

app/controllers/api/v1/users_controller.rb
class Api::V1::UsersController < ApplicationController
  before_action :authenticate_api_v1_user!
  
  def direct_uploads
    # Userでスペースを区切る
    prefix = "activestorage/blobs/uploaded/users/#{current_user.id}/user_images/"
    key = File.join(prefix, ActiveStorage::Blob.generate_unique_secure_token)

    blob = ActiveStorage::Blob.create_before_direct_upload!(
      filename: params[:filename],
      byte_size: params[:byte_size],
      checksum: params[:checksum],
      content_type: params[:content_type],
      key:,
    )

    # アップロードとアタッチに必要な情報
    @blob_signed_id = blob.signed_id # アップロード後の紐付け(アタッチ)に使うid
    @direct_upload_url = blob.service_url_for_direct_upload # アップロードURL
    @direct_upload_headers = blob.service_headers_for_direct_upload # アップロードヘッダー
  end
end
  
app/views/api/v1/users/direct_uploads.json.jbuilder
json.status :ok
json.data do
  json.blob_signed_id @blob_signed_id
  json.direct_upload_url @direct_upload_url
  json.direct_upload_headers @direct_upload_headers
end

署名付きURL.jpg

2. アップロードしたファイル情報とDBを紐付ける(アタッチする)API

アタッチAPIと名称していますが、フロントエンドでアップロードしたS3のファイルとDBの関連付けを行うAPIになります。
Screenshot 2023-04-29 at 13.35.23.png

さきほどのアップロードに必要な情報を返すAPIでレスポンスとして取得できたblob_signed_idをパラメータとして送信します。これによりユーザーとアップロードした画像との関連付けをDBへ記録することが出来ます。
Screenshot 2023-04-29 at 13.37.07.png

(実装例)

app/controllers/api/v1/users_controller.rb
class Api::V1::UsersController < ApplicationController
  before_action :authenticate_api_v1_user!
  
  def attach_image
    blob = ActiveStorage::Blob.find_signed!(params[:blob_signed_id])
    current_user.images.attach([blob])
    @message = 'Attached the user image'
  end
end
app/views/api/v1/users/attach_image.json.jbuilder
json.status :ok
json.message @message

アタッチ.jpg

まとめ

フロントエンドはモダンなシステムであればReactやVue.jsなどの構成があると思います。
またWebシステムだけでなく、モバイルアプリケーションのような場合も考えらます。
バックエンドだけでアップロードをすることも可能ですが、APIサーバーのタイムアウトなどを考えると、このようにフロントエンド側でアップロードという時間がかかる処理を行うことはとても合理的です。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?