RailsAPIでBase64エンコードされた画像ファイルを受け取り、S3(AWS)に保存する
自分が昔に書いた記事の続編です。
クライアント層から送られてきたBase64エンコードされた画像ファイルをRailsで扱う方法の紹介するのがこの記事の目的です。
以前書いた記事にコメントを頂いたので、筆を取りました。(コメントありがとうございます!)
実装の時間を短縮したいなら……
Rubyの有名なアップロード用Gem「CarrierWave」と、そのさいのBase64エンコード・デコードをサポートする「Carrierwave-base64」を利用するのが手っ取り早いと思われます。
自分も利用したことがありますが、ドキュメントに詳細に使い方が書いているので、迷うところはないでしょう、たぶん。
それらのGemを利用し、AWSのS3に画像をアップロードする処理自体は、↓の記事が参考になりそうでした。(自分が書いたわけではありません。)
https://qiita.com/junara/items/1899f23c091bcee3b058#carrierwave%E3%81%AE%E8%A8%AD%E5%AE%9A
しかし、この記事では別のライブラリを使っています
ただ、この記事ではShrineという添付ファイルを操るための軽量なGemと、自前で実装したBase64デコード・エンコードモジュールを使った実装を紹介しています。
その理由は、この記事は、過去の記事の続編で、そして過去の自分がこのGemを使って実装していたためです!
このGemを使った理由は、CarrierWaveを使ったものはよく見かけたので、別のものに挑戦してみたい、みたいなモチベーションだった気がします。(半年以上前なので記憶が曖昧)
多少オレオレ実装の気があります。ただ、Carrierwave-base64といったGemが行っていることの一部は、この記事を読めば理解の助けになるのではないかと思います。
まずは受け取ろう
Avatarクラスというのを使って自分は受け取る処理を書きました。
class Avatar < ApplicationRecord # Shirne用のメソッド include ImageUploader::Attachment.new(:image) # 自前実装のモジュール include ImageEncodable belongs_to :user end
module Api module V1 class AvatarsController < ApplicationController # ... def create # @avatarという、ユーザーの画像を司るクラスのインスタンスを作る @avatar = current_user.avatar # Base64で送られてきた画像をデコードする(モジュールの説明はのちにします) image_file = ImageEncodable.decode_to_imagefile(avatar_params[:image]) # イメージを格納して、あとはShrineに任せる @avatar.image = image_file @avatar.save! render json: @avatar end def show # ImageEncodableモジュールのおかげで、Avatarクラスのインスタンスが、自分をBase64でエンコードすることが出来る。 @avatar = @user.avatar.encode(:icon) render json: @avatar end private def avatar_params params.require(:avatar).permit(:image) end end
Shrineが担保するImageUploader(詳細は公式ドキュメントへGo)
require "image_processing/mini_magick" class ImageUploader < Shrine plugin :processing # allows hooking into promoting plugin :versions # enable Shrine to handle a hash of files plugin :delete_raw # delete processed files after uploading plugin :validation_helpers process(:store) do |io, context| versions = { original: io } # retain original io.download do |original| pipeline = ImageProcessing::MiniMagick.source(original) versions[:icon] = pipeline.resize_to_limit!(200, 200) versions[:medium] = pipeline.resize_to_limit!(800, 800) end Attacher.validate do validate_max_size 5 * 1024 * 1024, message: "5MBを超える画像はアップロードできません。" end versions # return the hash of processed files end end
自前実装のモジュール
module ImageEncodable # S3に格納している画像データをエンコードし、クライアント層に返すためのメソッド def encode(image_size_symbol) encoded_image = Base64.strict_encode64(open(avatar_url(image_size_symbol)).read) prefix = "data:image/png;base64," prefix + encoded_image end # Base64の画像をデコードする def self.decode_to_imagefile(prefix_encoded_image) # Base64の形式を判断し、画像であればそれに合わせた処理 meta_data = prefix_encoded_image.match(/data:(image|application)\/(.{3,});base64,(.*)/) content_type = meta_data[2] encoded_image = meta_data[3] if content_type == "jpeg" || content_type == "png" # Rubyが担保するデコードを行う。StringIOクラスを利用し、ファイルとして扱えるように。 decoded_image = Base64.strict_decode64(encoded_image) image_file = StringIO.new(decoded_image) else # raise error end end private def avatar_url(image_size_symbol) if image.try(:[], image_size_symbol) if Rails.env.development? url = "public" + url else url = image[image_size_symbol].url end url else "public/default.png" end end end
以上です。
昔に書いたコードの、不必要そうな処理を除外してベタ貼りしております。
基本的な処理の流れは、クライアント層から送られてきたBase64データをデコードしてファイルにしてS3に保存、クライアントに返すときはエンコードして返答、という流れです。
しかしですね、今思えば、どう考えても、わざわざ画像をS3に保存するときにデコードする必要がないな、と思います。
そのまま文字列で放り込んどいた方が扱いやすいですよね。
まあ、あくまでこんな実装方法もあるよ、という参考にしていただければ幸いです。