Rails5のAPIモードで画像ファイルをS3にアップロードする。(carrierwave利用)
前回、carrierwaveを利用し、S3に画像ファイルをアップロードする機能を作りました。
Rails5のAPIモードで作っていたので、今回は、APIクライアントとの連携を試してみます。 APIクライアントからS3に画像アップロードができればゴールです。
前回と同じように、実施プロセスをメモしていきます。
早速、試してみる
HTMLフォームにもろもろ入力して、いざ、submit!
・・・
下記のようなエラーが出て、うまくいかなかった・・・。
Encoding::UndefinedConversionError ("\x89" from ASCII-8BIT to UTF-8)
UndefinedConversionError
どうやら、encodingの食い違いが原因らしい。 画像ファイルのencodingがASCII-8BITだが、UTF-8に変換しようとしているため発生するらしい。 ならばと、UTF-8にエンコードして渡してやったが、渡した先でのデータの持ち方がよくわからん状態になっていた。
次の手として、下記2つを思いついた。
画像データをsession(Redis)に持たせてみる
次に、画像データをsession(Redis)に格納してみた。 やっぱりエラー・・・。
TypeError (can't dump File)
Redisに格納する際に、Marshal.dumpをしているようなのだが、
上記のサイトによると、
Marshal.dumpは 名前のついてないオブジェクト システムがオブジェクトを保持するもの などを書きだそうとするとTypeErrorを起こす
らしい。
APIクライアント側Base64でエンコードして渡す
下記の流れでやってみる。
APIクライアントでBase64でエンコード
まず、前提として、 HTTPで画像データをアップロードする際は下記のライブラリを利用する。
ActionDispatch::Http::UploadedFile
このライブラリの使い方がよくわからずハマったのですが、 ライブラリには下記のようなパラメータがあり、
@original_filename @header @tempfile @content_type
@tempfileというパラメータで、画像データをバイナリで保持していた。 なので、それぞれのパラメータをAPIで受け取れればよいのではないかと考えた。 @tempfileだけはバイナリなので、Base64でエンコードして渡すこととした。
def image_params { filename: form_param[:image].original_filename, type: form_param[:image].content_type, tempfile: encode64_tempfile } unless form_param[:image].blank? end def encode64_tempfile tempfile = Base64.strict_encode64(form_param[:image].tempfile.read) URI.escape(tempfile) end
APIサービスでBase64エンコードされた画像データを受け取り、デコード
def decode64_image image = params[:image] return unless image.present? img_params = { filename: image[:filename], type: image[:type], tempfile: decode64_tempfile(image[:tempfile], image[:filename]) } ActionDispatch::Http::UploadedFile.new(img_params) end def decode64_tempfile(file, filename) tempfile = URI.decode(file) tempfile = Base64.decode64(tempfile) file = Tempfile.new(filename) file.binmode file << tempfile file.rewind file end
結果
アップロードできた! 画像をアップロードできるAPIができました。
参考
class Encoding::UndefinedConversionError (Ruby 2.0.0)