【Google Cloud Storage】署名付きURLによる画像のアップロード

f:id:stmn_mmoto:20201216080055p:plain

目次

  • はじめに
  • アップロードの流れ
  • Google Cloud Storage の準備
  • 実装
  • おわりに

はじめに

こんにちは、スタメンのミツモトです。

スタメンではTUNAGFANTSというサービスを提供しており、画像の保存先として Amazon Web Services のS3(Simple Storage Service)を採用しています。

Google Cloud Storage(以下、GCS)の場合、どういう流れで画像を保存するか知りたいと思い、自分の学習として GCS を用いた署名付きURLによる画像のアップロードを実装したので紹介させていただきます。

アップロードの流れ

今回はクライアントから直接GCSへ画像をアップロードします。 通信の流れは以下になります。 f:id:stmn_mmoto:20201216080118p:plain

Google Cloud Storage の準備

サービスアカウント

最初に、GCSのバケットにアクセスするためのサービスアカウントを作成します。 Google Cloud Platformにアクセスし、サイドバーから「IAMと管理 > サービスアカウント」を選択してください。

f:id:stmn_mmoto:20201216080336p:plain

「サービスアカウントの作成」をクリックします。

f:id:stmn_mmoto:20201216080353p:plain

サービスアカウント名を入力し、サービスアカウントの権限として「Cloud Storage > Storageオブジェクト管理者」を選択します。

f:id:stmn_mmoto:20201216080405p:plain

アプリケーションで作成したサービスアカウントを利用するためキーを追加します。サービスアカウントの一覧で対象アカウントの「編集」をクリックしてください。

f:id:stmn_mmoto:20201216080422p:plain

サービスアカウントの詳細から「鍵を追加」をクリックし、JSON形式で鍵を作成してください。ここで作成した鍵をサーバーサイドの実装時に利用します。

f:id:stmn_mmoto:20201216080510p:plain

バケット

続いてバケットの作成をします。 サイドバーから「Storage > ブラウザ」を選択してください。

f:id:stmn_mmoto:20201216080945p:plain

バケット名の入力、データの保存場所やストレージクラス等を選択し、バケットを作成します。

f:id:stmn_mmoto:20201216081050p:plain

以上で Google Cloud Storage の準備は完了です。

実装

サーバーサイド

署名付きURLを発行するためのモジュールを作成します。 先程作成したサービスアカウントを用いてクレデンシャルを生成し、それを用いてバケットに対する署名付きURLを発行します。

content_typeを指定しないとクライアントからのアップロードが上手くいかないため、署名付きURLを発行する時点でcontent_typeを指定しておきます。

require 'google/cloud/storage'

module Utils::Gcp::Storage
  GCP_SA_CREDENTIALS = { private_key: 'サービスアカウントのprivate_key', client_email: 'サービスアカウントのclient_email' }
  GCS_PROJECT_ID = 'GCPのプロジェクトID'
  GCS_BUCKET_NAME = 'GCPのバケット名'

  class << self
    def pre_signed_url(path, content_type, expires)
      @storage = Google::Cloud::Storage.new( project_id: GCP_PROJECT_ID, credentials: GC_SA_CREDENTIALS)

      expires = expires.to_i
      @storage.signed_url(GCS_BUCKET_NAME, path, method: 'PUT', content_type: content_type, expires: expires)
    end
  end
end

Resourceであるインスタンスからモジュールのpre_signed_urlメソッドを呼び出し、レスポンスとして返却します。

class Resource < ApplicationRecord
  def pre_signed_url(filename【GCS上のファイル名】, content_type)
    Utils::Gcp::Storage.pre_signed_url("resouces/#{id}/images/" + filename, content_type, 5.minutes.from_now)
  end
end
クライアントサイド

最初のシーケンス図通り、以下の順番でリクエストを送ります。(エラーハンドリングは省略しています。)

  1. 署名付きURLの取得
  2. GCSへの画像アップロード
  3. アップロードされた画像の情報をDBへ保存

実装としては以下のようになります。

const uploadImage = async(file, resourceId) => {
  // 署名付きURLの取得
  const res = await fetch("署名付きURL取得エンドポイント" + `?content_type=${file.type}`)
  const resJson = await res.json();

  // 署名付きURLを用いて Google Cloud Storage へアップロード
  await fetch(resJson.preSignedUrl , { method: "PUT", headers: { "Content-Type": file.type }, body: file })

  // アップロードされた画像の情報をDBへ保存
  fetch(
    "画像の情報をDBへ保存するエンドポイント",
    {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded; charset=utf-8" },
      body: `uniq_filename=${resJson.filename}&filename=${file.name}&content_type=${file.type}&byte_size=${file.size}`
    }
  )
}

おわりに

GCS を用いた署名付きURLによる画像のアップロードについて紹介させていただきました。想定していたより簡単に画像のアップロードを実現できたので良かったです。今回はアップロードの流れを書きましたが、機会があれば画像配信についても記事にできたらと思います。

スタメンでは一緒に働くエンジニアを募集しています。 興味がある方は、ぜひ採用サイトからご連絡ください!