はじめに
バックエンド(今回はRuby on Rails)で署名付きURLを生成し、GCSに非同期でファイルをアップロードする方法について説明します。
GCPの準備
アカウントの作成
GCPのアカウントを作成します。
サービスアカウントの作成
サービスアカウントとは、GCPの各種サービスを操作したり外部のアプリケーションからGCPにアクセスするための特別なアカウントです。
今回はRuby on Railsで作成した外部のアプリケーションからGCSにアクセスする必要があるため、そのためのサービスアカウントを作成します。
なお、サービスアカウントに付与するIAMロールは「Cloud Storage」の「Storageオブジェクト管理者」を選択してください。
また、作成したサービスアカウントのキー(JSON形式)をダウンロードしてRailsプロジェクトに移動しておいてください。
バケットの作成
GCSのバケットを作成します。
作成したバケットに、先ほど作成したサービスアカウントのアクセス権を付与します。ロールは「Cloud Storage レガシー」の「Storage レガシー オブジェクト読み取り」を付与してください。
なお、バケットにアップロードしたファイルに誰でもアクセスできるようにしたいという場合、allUsers
やallAuthenticatedUsers
のアクセス権を付与する必要があります。
バケットの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の設定が少し面倒ですが、本記事を参考にして実装していただければと思います。