S3DirectMultipartUpload 
Rails アプリに組み込み、S3 へのダイレクトマルチパートアップロード API を提供する mountable engine。認証・セッション管理はホストアプリケーション(::ApplicationController 由来の before_action 等)に委譲する。
名称と対象範囲
- この gem は、AWS S3 の「Multipart upload」機能に対して、アップロードセッション管理と presigned URL の払い出しを行うためのエンジンです。設計は AWS ドキュメント “Multipart upload overview”(https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html)で説明されている
CreateMultipartUpload→UploadPart→CompleteMultipartUploadのフローに対応しています。 - 「direct」は、実データの転送がホストアプリケーションを経由せず、クライアントから S3 に対して直接 PUT される点(この gem は presigned URL だけを発行する)を指しています
- 実装上、
byte_sizeが 5MB 未満の場合は README 記載の通りマルチパートではなく単一のPutObject用 presigned URL を返しますが、本 gem の主対象は「S3 Multipart upload を利用するようなサイズのオブジェクトを、クライアントから直接 S3 に送る」ケースです
開発方針
- 社内利用ツールを共有するために public repository としていますが、積極的な機能追加やサポートは想定していません。
- Issue/PR は歓迎しますが、対応はベストエフォートです。
- クリティカルな不具合やセキュリティ連絡は
[email protected]までお願いします。
インストール(GitHub 公開リポジトリから利用する場合)
Gemfile に以下を追加し、bundle install を実行する。
gem 's3_direct_multipart_upload', github: 'logmi/s3_direct_multipart_upload'
導入手順
- ルーティングに
mount S3DirectMultipartUpload::Engine, at: '<お好みのパス>'を追加する(<mount_path>/s3_direct_multipart_uploads系のエンドポイントが有効になる)。 bin/rails s3_direct_multipart_upload:install:migrationsでマイグレーションをホストへコピーし、bin/rails db:migrateを実行する。
設定
- バケットやリージョンなどの S3 接続情報は、ホストアプリの
config/storage/{env}.ymlに定義したs3_direct_multipartサービスを参照する。- 例 (production.yml):
s3_direct_multipart: service: S3 bucket: your-bucket region: ap-northeast-1 endpoint: https://s3.ap-northeast-1.amazonaws.com # 任意 force_path_style: false # 必要に応じて - 例 (development/test):
s3_direct_multipart: service: Disk s3_direct_multipartが未定義の場合は起動時に例外となる。
- 例 (production.yml):
- キープレフィックス:
uploads/s3_direct/yyyyMMdd/<session_uuid>形式で自動付与する。
セキュリティ・運用上の注意
- エンジン側では
skip_forgery_protectionを有効化しており、認証/認可やレート制御はホストアプリの::ApplicationController由来の before_action 等に委譲する前提です。CSRF 対策や利用権限チェックが必要な場合は mount 先で必ずガードを追加してください。 UploadSession#expires_atを設定するだけで、期限切れセッションのクリーンアップやabort_multipart_uploadは未実装です。S3 上に未完了アップロードが蓄積しないよう、ホスト側で定期バッチを用意して後始末してください。
ファイルサイズとパート数の扱い
chunk_sizeは 5MB 以上を推奨し、5GB 以下に収めること(S3 制約)。byte_sizeに対して期待パート数が 10,000 を超える場合はアップロードを拒否します。byte_sizeが 5MB 未満の場合はマルチパートを使わず単一パートとして扱い、1 件の presigned URL だけを返します(chunk_sizeはbyte_sizeに自動調整)。
エンドポイント
POST <mount_path>/s3_direct_multipart_uploadsPOST <mount_path>/s3_direct_multipart_uploads/:upload_session_id/partsPOST <mount_path>/s3_direct_multipart_uploads/:upload_session_id/complete
レスポンス仕様は doc/s3_direct_multipart_upload/api.yml を参照。
ドキュメント導線
- 仕様 (SoT):
SPEC.md - 運用:
OPERATIONS.md - 意思決定:
ADR/ - 実装ガイド:
AGENTS.md - サンプルアプリ: https://github.com/logmi/s3_direct_multipart_upload_example (公開デモ用の最小実装)
配布形態
- Rubygems で公開(
gem 's3_direct_multipart_upload')。ソースリポジトリは現在 private なので、利用者は.gemに同梱されたドキュメント(README/SPEC/OPERATIONS/AGENTS と OpenAPI 定義)を参照してください。 - GitHub の issue/PR はベストエフォート対応。問い合わせは README 冒頭のメールアドレスまで。
開発・貢献のはじめかた
- セットアップ:
bundle installを実行(必要ならgem update --systemで Rubygems を最新化) - 開発用設定:
config/storage/development.yml/config/storage/test.ymlにあるs3_direct_multipart(Disk) を利用する前提で動くため追加設定は不要 - テスト:
bundle exec rspecを実行 - マイグレーション: ホスト側では
bin/rails s3_direct_multipart_upload:install:migrations→bin/rails db:migrateを実行
Disk サービス利用時の開発用エンドポイント
service: Disk(stub_responses: true)のまま presigned URL に対して PUT できるよう、開発/テスト環境限定でPUT /s3_direct_multipart_upload/dev/storage/*pathを用意しています。- 環境変数:
S3_DIRECT_DEV_ENDPOINT: presigned URL に埋め込むベース URL。未設定時はhttp://localhost:3000。S3_DIRECT_DEV_SECRET: 署名生成に使うシークレット。未設定時はdev-secret。
- 使い方:
ActiveStorageのs3_direct_multipartをservice: Diskで設定する。UploadSession#presigned_partsが返す URL(クエリにuploadId,partNumber,expires_at,signatureを含む)に対し、クライアントからPUTする。- リクエストボディは
tmp/s3_direct_multipart_upload/<object_key>.parts/<partNumber>に保存され、ETagヘッダーが返る。
- 本番環境ではルート自体が無効になるため、開発専用の安全な代替として利用できます。
- Disk + stub_responses 利用時の upload_id 重複対策: development/test では
config/initializers/s3_direct_dev.rbでcreate_multipart_uploadをユニークなupload_id(SecureRandom.uuid)で stub しています。既存セッションが残っている場合でも一意制約に抵触しなくなります。 - ダウンロード確認: development/test では
UploadSession#complete!時にパートファイルを結合し、tmp/s3_direct_multipart_upload/<object_key>に単一ファイルを生成します。GET /s3_direct_multipart_upload/dev/storage/:upload_session_id/downloadにアクセスすると結合済みファイルを返します(completed セッションのみ許可、欠損時は 422)。
ライセンス
MIT