base64エンコード形式の文字列データをページに埋め込む

概要

S3のプライベートバケットに保管されている画像を表示したい。
S3からオブジェクトを取得し、それをbase64エンコードしたものをData URLを使用してブラウザに埋め込み、画像を表示することを行なったので それをメモとして残します。

Data URL とは

Data URL は data: スキームが先頭についたURLで、データをインラインで文書に埋め込むことができます。
スキーム(data:)、MIMEタイプ、base64トークン、データ自体の4つの部品で構成されます。

data:[<mediatype>][;base64],<data>

データが文字の場合は、そのまま指定することができます。
文字以外であれば、base64 を指定し、 base64エンコードしたバイナリーデータを指定します。
Data URIスキームに対応したブラウザが、base64エンコードされたデータをデコードし、それを展開してくれるそう。

例としてPNGの画像をbase64エンコードしたData URIは以下のようになります。

image_tag "data:image/png;base64, #{image_data}"

image_dataの部分は、base64エンコードされたデータそのものが入ります。

base64エンコードとは

base64は、データを64種類の印字可能な英数字のみを用いて、マルチバイト文字やバイナリデータを扱うためのエンコード方式です。
具体的には、A、…、Z、a、…、z、0、…、9、+、/ でデータが変換されています。

base64エンコード方式は、ASCIIテキストしか扱えないメディア上で保存や送信を行う際に、データを変換するために使用されてます。
base64が定められた経緯として、かつて電子メールを送信する際にSMTPプロトコルではASCIIで表現される英数字しか送信することができなかったが、画像などのテキスト以外のデータを扱うためにASCIIへの変換方法が定められました。

Data URL で画像を表示することについての注意点

  • ブラウザによってURLの文字数に制限が設けられています。
    データ URL - URI | MDN
  • base64エンコードして埋め込むと、当然ですが imgタグでsrcに画像パスを指定しするより、ソースコード自体も長くなります。そのため埋め込む画像の分だけページサイズも大きくなります。データ転送量が増加するため、画像を埋め込んだファイルのダウンロードに時間がかかる可能性があります。

実際のコード

s3 = Aws::S3::Resource.new(client: s3_client)
object = s3.bucket(Settings.s3.bucket_name).object(prefix: "uploads/image")
image_data = Base64.encode64(object.get.body.read)

object.get.body.readの部分について、 getメソッドでオブジェクトを取得し、bodyメソッドでオブジェクトの中身を参照します。以下のようにバイト型データを扱うストリームとなっています。

pry > object.get.body.class
=> StringIO

文字列として扱うためにはストリームから read して文字列型に変換する必要があります。

pry > object.get.body.read.class
=> String

ビュー側での記述です。

image_tag "data:image/jpeg;base64, #{image_data}"

ちなみに、object.get.body.read で文字列にした後そのままdataURLに埋め込むことができるのかと思い、 base64エンコードせずにビュー側に返すと Invalid byte sequence in UTF-8とエラーになってしまいました。
おそらく、dataURLが解釈できない文字列が含まれているため、別途追加処理(エスケープ?)が必要かもしれないと思います。

参考にしたURL

データ URL - URI | MDN
Base64 - Wikipedia
Base64についてまとめてみた - iimon TECH BLOG