【Rails】Ruby on Railsで署名付きURLを生成してGCSにファイルをアップロード【GCP】

はじめに

バックエンド(今回はRuby on Rails)で署名付きURLを生成し、GCSに非同期でファイルをアップロードする方法について説明します。

GCPの準備

アカウントの作成

GCPのアカウントを作成します。

サービスアカウントの作成

サービスアカウントとは、GCPの各種サービスを操作したり外部のアプリケーションからGCPにアクセスするための特別なアカウントです。

今回はRuby on Railsで作成した外部のアプリケーションからGCSにアクセスする必要があるため、そのためのサービスアカウントを作成します。

なお、サービスアカウントに付与するIAMロールは「Cloud Storage」の「Storageオブジェクト管理者」を選択してください。

また、作成したサービスアカウントのキー(JSON形式)をダウンロードしてRailsプロジェクトに移動しておいてください。

バケットの作成

GCSのバケットを作成します。

作成したバケットに、先ほど作成したサービスアカウントのアクセス権を付与します。ロールは「Cloud Storage レガシー」の「Storage レガシー オブジェクト読み取り」を付与してください。

なお、バケットにアップロードしたファイルに誰でもアクセスできるようにしたいという場合、allUsersallAuthenticatedUsersのアクセス権を付与する必要があります。

バケットのCORS設定

CORSは、あるオリジンから別のオリジンへのリソースアクセスを制御するためのセキュリティ機能です。

GCSバケットからリソースを取得しようとしたときに、GCS側でCORS設定が適切に行われていないとリクエストがブロックされます。

バケットのCORS設定を行うにはgsutilツールを使用します。

まず、CORS設定を記述したJSONファイルを作成します。

cors.json

[
  {
    "origin": ["http://127.0.0.1:3000"],
    "method": ["GET", "HEAD", "PUT", "POST", "DELETE"],
    "responseHeader": ["Content-Type", "Authorization"],
    "maxAgeSeconds": 3600
  }
]

作成したJSONファイルを使用してバケットのCORS設定を行います。

% gsutil cors set cors.json gs://your-bucket-name

バケットのCORS設定を確認します。

% gsutil cors get gs://your-bucket-name
[{"maxAgeSeconds": 3600, "method": ["GET", "HEAD", "PUT", "POST", "DELETE"], "origin": ["http://127.0.0.1:3000"], "responseHeader": ["Content-Type", "Authorization"]}]

実装

Gemのインストール

RailsでGCSを扱うためのGemをインストールします。以下を追記してbundle installを実行します。

Gemfile

gem 'google-cloud-storage'

署名付きURLを生成するアクションの作成

フロントエンドからのリクエストで署名付きURLを生成するアクションを作成します。

app/controllers/uploads_controller.rb

require 'google/cloud/storage'

class UploadsController < ApplicationController
  before_action :set_storage, only: :generate_signed_url

  def generate_signed_url
    file_name = params[:filename]

    if file_name.blank?
      render json: { error: 'Filename is required' }, status: :bad_request
      return
    end

    expires_time = 5 * 60
    url = @storage.signed_url('your-bucket-name', file_name, method: 'PUT', expires: expires_time, version: :v4)
    render json: { url: url }
  end

  private
    def set_storage
      @storage = Google::Cloud::Storage.new(
        project_id: 'your-project-id',
        credentials: 'path/to/your/service-account-file.json',
      )
    end
end

ルーティングの追加

config/routes.rb

Rails.application.routes.draw do
  get 'generate_signed_url', to: 'uploads#generate_signed_url'
end

JavaScriptでファイルをアップロード

フォームから入力されたファイルの署名付きURLを生成し、生成した署名付きURLを使用してGCSにファイルをアップロードする処理を実装します。

  <form id="uploadForm">
    <input type="file" id="fileInput" />
    <button type="submit">Upload</button>
  </form>
    document.getElementById('uploadForm').addEventListener('submit', async (e) => {
      e.preventDefault();
      const fileInput = document.getElementById('fileInput');
      const file = fileInput.files[0];

      if (!file) {
        alert('Please select a file');
        return;
      }

      const response = await fetch(`/generate_signed_url?filename=${encodeURIComponent(file.name)}`);
      if (!response.ok) {
        alert('Failed to get signed URL');
        return;
      }

      const { url } = await response.json();

      const uploadResponse = await fetch(url, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/octet-stream',
        },
        body: file,
      });

      if (uploadResponse.ok) {
        alert('File uploaded successfully');
      } else {
        alert('File upload failed');
      }
    });

まとめ

署名付きURLを使用すれば非同期でファイルをアップロードすることができます。また、署名付きURLには有効期限を設定することができるのでセキュリティ面でも安全です。

バケットの権限設定やCORSの設定が少し面倒ですが、本記事を参考にして実装していただければと思います。

関連記事

【Rails】RSpecを使った自動テスト《システムスペック編》
# はじめに Railsでアプリケーションを開発する際、品質を担保するためにテストは欠かせません。その中でも、RSpecは多くの開発者に愛用されているテストフレームワークです。 今回は、RSpecを使ったRailsアプリケーションのテスト方 [...]
2024年7月11日 17:07
【Rails】RSpecを使った自動テスト《リクエストスペック編》
# はじめに Railsでアプリケーションを開発する際、品質を担保するためにテストは欠かせません。その中でも、RSpecは多くの開発者に愛用されているテストフレームワークです。 今回は、RSpecを使ったRailsアプリケーションのテスト方 [...]
2024年7月11日 16:06
【Rails】RSpecを使った自動テスト《コントローラースペック編》
# はじめに Railsでアプリケーションを開発する際、品質を担保するためにテストは欠かせません。その中でも、RSpecは多くの開発者に愛用されているテストフレームワークです。 今回は、RSpecを使ったRailsアプリケーションのテスト方 [...]
2024年7月11日 15:39
【Rails】RSpecを使った自動テスト《モデルスペック編》
# はじめに Railsでアプリケーションを開発する際、品質を担保するためにテストは欠かせません。その中でも、RSpecは多くの開発者に愛用されているテストフレームワークです。 今回は、RSpecを使ったRailsアプリケーションのテスト方 [...]
2024年7月11日 11:50
【Rails】RSpecを使った自動テスト《基本編》
# はじめに Railsでアプリケーションを開発する際、品質を担保するためにテストは欠かせません。その中でも、RSpecは多くの開発者に愛用されているテストフレームワークです。 今回は、RSpecを使ったRailsアプリケーションのテスト方 [...]
2024年7月10日 17:49
【Rails】デザインパターン「Concern」の基本情報と実装方法
# はじめに Ruby on RailsなどのMVCフレームワークで構築したWebシステムにはアンチパターンというものが存在します。システム開発におけるアンチパターンとは、避けるべき悪い設計や実装方法のことを指します。 MVCフレームワーク [...]
2024年7月10日 13:32
【Rails】デザインパターン「Form Object」の基本情報と実装方法
# はじめに Ruby on RailsなどのMVCフレームワークで構築したWebシステムにはアンチパターンというものが存在します。システム開発におけるアンチパターンとは、避けるべき悪い設計や実装方法のことを指します。 MVCフレームワーク [...]
2024年7月6日 22:50
【Rails】デザインパターン「Service Object」の基本情報と実装方法
# はじめに Ruby on RailsなどのMVCフレームワークで構築したWebシステムにはアンチパターンというものが存在します。システム開発におけるアンチパターンとは、避けるべき悪い設計や実装方法のことを指します。 MVCフレームワーク [...]
2024年7月6日 19:59