GCSのsigned policy documentsでフォルダごとアップロードする
webアプリケーションでファイルアップロード機能をよく提供すると思います。 ほんの数ファイルのアップロードであれば、署名付きURLを使うことが多いですが、フォルダのようにファイル数が読めないような場合は署名付きポリシードキュメントを使うと便利です。 署名付きURLの特徴 ここではフォルダアップロードする際に困る点だけ説明します。 その他の特徴については公式ドキュメント1を参照してください。 署名付きURLは主に次のようなフォーマットになっており、パスにオブジェクト名まで含まれています。 https://storage.googleapis.com/example-bucket/cat.jpeg?X-Goog-Signature=... これはつまり、アップロードできるオブジェクト名が1つに限定されてしまうということです。 複数ファイルをアップロードしたい場合、ファイルごとに発行する必要があり、計算量はO(n)となってしまいます。 署名付きポリシードキュメントの特徴 前述の計算量がO(n)がネックになるケースでは、署名付きポリシードキュメントを使用することで解決できます。 https://cloud.google.com/storage/docs/authentication/signatures?hl=ja#policy-document ポリシードキュメントには次のような特徴があります。 アップロード先のバケットを指定できる ファイル名のプレフィックスを限定できる 特定のフォルダ以下に対しての操作を許可できる それ以外のフォルダにはアップロードできない Content-TypeやContent-Lengthなどの条件を指定できる POSTのみ可能でGETやDELETEは不可 特にフォルダ配下に対してアップロードする権限を付与できるので、フォルダ内のファイルを全てアップロードするようなケースでは、署名の発行の計算量がO(1)となり、大幅に効率化できます。 実装してみる apiにrails、フロントエンドにreactを使用したアプリケーションで、GCSの署名付きポリシードキュメントを発行する例を示します。 まずapiは、google-cloud-storage gemを使用して、署名付きポリシードキュメントを発行し、返します。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 require 'bundler/inline' gemfile do source 'https://rubygems.org' gem "google-cloud-storage" end class Storage # @param prefix [String] The prefix for the objects to be uploaded # @param max_file_size [Integer] The maximum allowed file size for uploads # @return [Hash] A hash containing the URL and form fields for the signed policy document def self.generate_policy_document_for_prefix(prefix:, max_file_size: 100 * 1024 * 1024) post_object = bucket.generate_signed_post_policy_v4( "#{prefix}${filename}", expires: 3600, conditions: [ [ "starts-with", "$key", prefix ], [ "content-length-range", 0, max_file_size ] ] ) { url: post_object.url, fields: post_object.fields } end private def self.bucket @bucket ||= storage_client.bucket({bucket_name}) end def self.storage_client @storage_client ||= begin Google::Cloud::Storage.new( credentials: load_service_account_key ) end end def self.load_service_account_key keyfile_path = Rails.root.join({service_account.json}).to_s JSON.parse(File.read(keyfile_path)) end end 次にフロントエンドで、発行されたポリシードキュメントを使用して、フォルダ内のファイルをアップロードします。 ...