Refile+S3のpresignにSwiftからダイレクトにアップロードする
画像等のファイルをS3(とかのクラウドストレージ)に置くのが当たり前になってきた昨今、クライアントから画像をアップロードするときにアプリケーションサーバーを経由するのは、そこからさらにS3に挙げるという点ではムダ。
この改善方法として、クライアントサイド(ブラウザやネイティブアプリ)からS3に直接アップロードして、アプリケーションサーバーには「ここに置いといたよ」とだけ伝える方法がある。 このアプローチは、特にherokuのようなアプリケーションサーバーに負荷をかけたくない環境では高い効果を発揮する。
で、このエントリの対象読者は、上の文で何が言いたいか分かってくれる人。
そうじゃない人は、諦めてアプリケーションサーバーに頼ってCarrierWaveかPaperclipの安定版を使う事を強くオススメする。
さて本題。
CarrierWaveの後継と噂される(同じ人が開発している)Rails向けの画像取り扱いgemのRefileというのがある。まだ安定バージョンではないし、Rails5系に組み込もうとするとSinatraとrakeのバージョンがぶつかってしまったり面倒が多いが、設計はCarrierWaveよりもさらに洗練されているっぽい。
GitHub - refile/refile: Ruby file uploads, take 3
Refileにはdirect uploadとPresigned uploadsという機能がある。(大文字小文字はREADMEに倣った)
direct uploadはフォームをsubmitしようとしたときにファイルを1個ずつRails側に送って、それが完了してからフォームをsubmitする方式。
Presigned uploadsは、上記で言ったS3に直接上げる方式。
Refileでは、Javascriptからのアップロードのみ実装されているので、これをSwift側で実装する。
ざっくり言えば refile/refile.js at master · refile/refile · GitHub の移植。
// まずはpresign情報を取得する request("https://example.com/attachments/cache/presign").validate().responseSwiftyJSON { (presignResponse) in // ※エラー処理は自分で書いて下さいね。 switchで .Success / .Failure 分岐するのが個人的には好き。 let url = presignResponse.value!["url"].stringValue let fields = presignResponse.value!["fields"].dictionaryValue let asName = presignResponse.value!["as"].stringValue Alamofire.upload(multipartFormData: { (multipartFormData) in for (name, value) in fields { multipartFormData.append(value.stringValue.data(using: .utf8)!, withName: name) } // 以下のNSDataを用意する部分もお好みで。 UIImagePNGRepresentation を使っても良いし、拡張子などで分岐しても良い。 let image = UIImage(named: "image1.jpg")! let imageData = UIImageJPEGRepresentation(image, 1.0)! multipartFormData.append(imageData, withName: asName) }, to: url, encodingCompletion: { (encodingResult) in // ここにもAlamofireのドキュメントを参照してエラー分岐等を書く必要があると思います。 print("uploaded") }) }
ここで得たデータをPOSTやPUTしてruby側でActiveRecordに渡してやる必要があるけれど、そこはまだやってないので追記するつもり。
ちなみに現状のGemfileの中身はこんな感じ。
tagすら切られてない中途半端なバージョンなので、不用意にバージョンが上がってバグらないように一応refを固定。
gem 'rails', '~> 5.0.1' # Refile (最新版, unstableなので依存解決のために無理して入れてます) gem "refile", require: "refile/rails", github: 'refile/refile', ref: 'd7a42dcd7c' gem "refile-mini_magick" gem "refile-s3" # Refileのために以下が必要っぽい gem "sinatra", github: "sinatra/sinatra", tag: "v2.0.0.beta2"
そういえば、全然関係ないんですが、最近Skyrimにハマってます。めっちゃ面白いですね。