t-sanoブログ

メモです。マイペースにアウトプットします。

Rails5のAPIモードで画像ファイルをS3にアップロードする。(carrierwave利用)

前回、carrierwaveを利用し、S3に画像ファイルをアップロードする機能を作りました。

ubw.hatenablog.com

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つを思いついた。

  • Redisに格納しちゃえ(APIクライアントとAPIサービスが同じRedisを利用している前提)
  • base64で渡す

画像データをsession(Redis)に持たせてみる

次に、画像データをsession(Redis)に格納してみた。 やっぱりエラー・・・。

TypeError (can't dump File)

Redisに格納する際に、Marshal.dumpをしているようなのだが、

qiita.com

上記のサイトによると、

Marshal.dumpは 名前のついてないオブジェクト システムがオブジェクトを保持するもの などを書きだそうとするとTypeErrorを起こす

らしい。

APIクライアント側Base64エンコードして渡す

下記の流れでやってみる。

  1. APIクライアントでBase64エンコード
  2. 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)

Railsでファイルをアップロードする際のencodingエラー | ぐんまのたなかの備忘録

qiita.com

qiita.com