【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】Paranoiaを使用した論理削除(ソフトデリート)
# はじめに Paranoiaは、Railsアプリケーションで論理削除(ソフトデリート)を実現するためのGemです。 論理削除は、データベースのレコードを物理的に削除するのではなく、削除フラグを設定することで「削除済み」とみなす方法です。こ [...]
2024年7月20日 21:33
【Rails】activerecord-multi-tenantを使用したマルチテナントアプリケーションの作成
# はじめに マルチテナントアプリケーションでは、複数の顧客(テナント)が同じアプリケーションを利用するため、データの分離が必要です。 activerecord-multi-tenantは、このようなマルチテナント環境をサポートするための便 [...]
2024年7月18日 16:50
【Rails】RubyとRailsにおけるattr_reader, attr_writer, attr_accessorの概念と使用方法
# はじめに RubyとRailsの開発において、`attr_reader`,`attr_writer`,`attr_accessor`は非常に便利なメソッドです。これらは、クラス内でインスタンス変数に対するゲッターおよびセッターメソッドを簡単に [...]
2024年7月17日 18:11
【Rails】RubyとRailsにおけるyieldの概念と使用方法
# はじめに RubyとRailsにおける`yield`は、メソッドやテンプレートの中で動的にコードブロックを実行する能力を提供し、これによってコードの再利用性と拡張性が大幅に向上します。本記事では、RubyとRailsにおける`yield`の概 [...]
2024年7月17日 13:15
【Rails】AASMを使用してオブジェクトの状態遷移を効率的に管理
# はじめに Railsアプリケーションにおいて、オブジェクトの状態管理は重要な課題の一つです。AASM (Acts As State Machine) gemは、複雑な状態遷移を効率的に管理します。本記事では、AASMの基本的な使い方を解説して [...]
2024年7月16日 18:00
【Rails】RSpec + Swagger + rswagでアプリケーションのAPIをテストおよびドキュメント化する方法
# はじめに Railsアプリケーションの開発において、APIのテストとドキュメント化は重要な要素です。 RSpecはテストフレームワークとして広く利用されており、SwaggerはAPIの設計とドキュメント化を支援します。これらを統合するr [...]
2024年7月16日 14:27
【Rails】mailcatcherを使用して開発環境でメール送信をテストする方法
# はじめに mailcatcherは、開発環境でのメール送信をキャプチャするためのツールです。ローカルで送信されたメールをブラウザ上で簡単に確認できるようにします。mailcatcherをRailsアプリケーションで使用する方法について説明しま [...]
2024年7月15日 16:37
【Rails】impressionistを使用してページビューやクリック数を追跡する方法
# はじめに impressionist Gemを使用してRailsアプリケーションでページビューやクリック数を追跡する方法について説明します。 # 実装方法 ## impressionist Gemのインストール まず、impre [...]
2024年7月15日 14:18