ども、@kimihom です。
今回はシンプルに S3 にファイルを上げる方法を案内しよう。
Rails 側でファイルアップロードを受ける課題
おそらく多くの Rails デベロッパーは、 画像のサイズ縮小や変換をするプログラムを実行したいがために、一度 Rails 側でファイルを受け取る実装をしたことがあることだろう。carrierwave などはまさにそのための Gem だ。
Webサイトからファイルアップロード => Rails アプリ => 画像等の変換 => S3
しかし時代は流れ、S3 にアップロードされたら、それをトリガーとして AWS Lambda 側でプログラムを実行させることが簡単にできるようになった。AWS Lambda 側でアップロードされた画像を取得・加工し、バックグラウンドジョブ的な形で変換されたファイルを S3 へ置きにいくことができる。その方が、明らかに Rails サーバー的には嬉しい。なぜなら、carrierwave などを使った画像の加工や生成には非常に多くのメモリや処理実行時間がかかり、サーバー負荷の代表的な要因であるからである。
Rails アプリ側では、そもそもアップロードされたファイルを受け取らない。ダイレクトアップロードを実装するべき価値はここにある。
Webサイトからファイルアップロード => S3 => AWS Lambda の実行
ということで、本記事では最もシンプルな Rails を使った S3 ファイルダイレクトアップロードの方法を解説する。
Presigned URL
今回の記事でキーワードとなるのが、S3 の Presigned という概念だ。
AWS 側で短期間有効な URL を生成し、その URL に対して ファイル付きの Ajax POST をすることでアップロードができる。この Presigned URL を生成するのに、Rails サーバー側で AWS SDK を利用する必要がある。以下に Presigned URL を生成するサンプルコードを示す。固定文字でのサンプルコードなので、適宜 動的なプログラムに改善して欲しい。
def presigned_post bucket = Aws::S3::Resource.new.bucket("my-bucket") post = bucket.presigned_post( key: "images/sample.jpg", acl: 'public-read', content_type: "image/jpg" ) render json: { status: "ok", url: post.url, fields: post.fields } end
まずダイレクトアップロード前に、この presigned_post
アクションを Ajax 経由で呼び出そう。うまく実行されれば url
と filelds
がそれぞれレスポンスとして返ってくる。この情報を用いて、フロントエンドでダイレクトアップロードを実行する。
var formdata = new FormData(); for (field in data.fields) { formdata.append(field, data.fields[field]); } formdata.append("file", file); $.ajax({ url: data.url, data: formdata, processData: false, contentType: false, method: "POST", }).done(function() { console.log("uploaded"); }).fail(error);
presigned_post
アクションで返ってきたデータに加えて、実際にアップロードしたいファイルを formdata
の file
として追加している。
なんということでしょう、これだけで S3 にダイレクトアップロードを実装できる!しかもフロントエンドで AWS-SDK の JavaScript を入れる必要もなく、単なる Ajax 呼び出しだけで完結してしまう。とても便利なものである。
S3 からファイルのダウンロード
パブリックとして公開していない S3 からファイルをダウンロードするのも同様に簡単に実装できるので紹介しておく。この場合、一時的な presigned_url
を Rails 側で生成するだけでよくなる。
def presigned_get signer = Aws::S3::Presigner.new url = signer.presigned_url(:get_object, bucket: "my-bucket", key: "images/sample.jpg") render json: {url: url} end
返ってきた URL に対して アクセスが可能になる。
$.ajax({ url: data.url, processData: false, contentType: false }).done(function(data) { console.log(data); }).fail(error);
CORS の設定をお忘れなく
S3 側で アクセス権限 > CORS の設定をしよう。
<?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>https://www.yourdomain.com</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <AllowedHeader>*</AllowedHeader> </CORSRule> </CORSConfiguration>
終わりに
今回は S3 ダイレクトアップロード と おまけのダウンロードをご紹介した。
Rails ファイルアップロードによる高負荷処理から卒業しよう。これからはそうした処理は AWS Lambda に任せる。役割分担をしっかりとして、より効率的なサーバー運用を実現しよう。