Laravel Vaporでアップロード制限にハマったのでS3直接アップロードに切り替えた話

はじめに

Laravel Vapor(AWS Lambda環境)でファイルアップロード機能を構築する際、
Controllerでファイルを受け取る通常の構成ではすぐにアップロード制限に引っかかってしまいます。

今回は、Vapor環境で直面したアップロード制限の課題と、それを解決するために採用した
「S3直接アップロード(Presigned URL)」の実装方法について解説します。

問題:Vapor(Lambda)のアップロード制限が厳しい

初期の実装では、一般的な「フロントエンド → Laravel (Controller) → S3」というフローを採用していました。

しかしVapor(=AWS Lambda)にはいくつか制約があります。

  • リクエストサイズ制限(API Gateway経由)
  • 実行時間制限
  • メモリ制約

その結果、

  • 少し大きいファイルでアップロード失敗
  • タイムアウト発生
  • 安定しない

といった問題が発生しました。

解決策:S3へ直接アップロードする

ブラウザから直接S3へアップロードする構成に切り替えました。

1. Laravelで presigned URL(署名付きURL)を発行

2. フロントからそのURLへ直接PUT

3. アップロード後にサーバーへ通知

これでサーバーを経由しないアップロードになります。


実装例

① サーバー側:presigned URLを発行

use Illuminate\Support\Facades\Storage;

public function generateUploadUrl()
{
   $path = 'uploads/' . uniqid();
   $url = Storage::disk('s3')->temporaryUrl( $path, now()->addMinutes(5), [ 'ContentType' => 'application/octet-stream', ] );
   return response()->json([ 'url' => $url, 'path' => $path, ]);
}

② フロント側:直接アップロード

async function upload(file) {
 const res = await fetch('/api/upload-url')
 const { url, path } = await res.json()

 await fetch(url, {
  method: 'PUT',
  headers: {
   'Content-Type': file.type,
  },
  body: file,
 })

 // アップロード完了後に通知
 await fetch('/api/upload-complete', {
  method: 'POST',
  body: JSON.stringify({ path }),
  headers: {
   'Content-Type': 'application/json',
  },
 })
}

運用上の注意点と「ハマりどころ」

CORS設定

S3側でCORS設定が必要です。

[
 {
  "AllowedHeaders": ["*"],
  "AllowedMethods": ["PUT"],
  "AllowedOrigins": ["*"],
  "ExposeHeaders": []
 }
]

Content-Typeを合わせる

presigned URL発行時とアップロード時のContent-Typeが違うと失敗します。


完了通知前の離脱対策(ゴミ掃除)

この構成では、アップロードはS3に直接行われるため、

  • アップロードは成功
  • しかしLaravelへの完了通知が送られない

といったケースが発生する可能性があります。

この場合、S3上に未使用ファイルが残り続けます。

そのため実際には`uploads/tmp/` のような一時領域にアップロードし、

S3のLifecycleルールで一定時間後に自動削除するなどの対応が必要です。

まとめ

Vapor環境でファイルアップロードを扱う場合は、Laravel経由ではなく、S3直接アップロードにしておくのが無難です。

ほんだ

ほんだ

企画開発部エンジニア

関連記事