透過AWS STS、S3服務實做大檔上傳
關於大檔上傳
有實做過大檔上傳的人應該都知道
大檔處理非常麻煩
後端要處理檔案切片
還要煩惱效能問題
佈署也是一個麻煩
還要一個專門處理大檔的服務(例如Tus Server)
因此這篇主要是為了免去架設、處理大檔服務的問題
後端跟AWS請求一個臨時可存取S3的憑證
然後把這個憑證直透過api傳給前端並讓前端(JS)往S3丟檔案
另外因為AWS的JavaScript SDK有支援Multipart Upload
要實做續傳也很方便
當然
要看專案的規劃上(例如成本)是否要允許client端有這麼大的權限可以上檔案
畢竟丟大檔到S3也是一種成本
例如只限縮CMS的管理員才能取得暫時憑證可上傳大檔到S3
本篇實做架構
PHP(Laravel)
AWS STS
AWS Security Token Service
這個服務可以產生臨時的憑證
給予特定使用者有短暫存取AWS資源的權限
AWS IAM相關設定
1. 建立IAM使用者(STS-Provider)
後續將拿這個IAM給Laravel建立暫時憑證給前端
2. AWS建立擁有S3存取政策的角色(S3AccessRole)
一樣依照需求設定限縮權限即可
如果覺得AWS的提供的預設政策不符合需求
也可以自己依照專案需求建立
例如S3完整存取權
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
]
}
或是只限縮指定Bucket可存取
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:ListStorageLensConfigurations",
"s3:ListAccessPointsForObjectLambda",
"s3:GetAccessPoint",
"s3:PutAccountPublicAccessBlock",
"s3:GetAccountPublicAccessBlock",
"s3:ListAllMyBuckets",
"s3:ListAccessPoints",
"s3:ListJobs",
"s3:PutStorageLensConfiguration",
"s3:CreateJob"
],
"Resource": "*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::bucket-name/*",
"arn:aws:s3:::bucket-name"
]
}
]
}
3. 設定S3AccessRole與前面建立的STS-Provider建立信任關係(trust relationship)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::xxxxxx:user/xxxx", // 這是應用程式使用的IAM(STS-Provider) ARN
"Service": "s3.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
建立暫時性憑證給前端
PHP取得暫時憑證給前端
<?php
$client = new StsClient([
'version' => '2011-06-15',
'region' => env('AWS_DEFAULT_REGION'),
'credentials' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
]
]);
$result = $client->assumeRole([
'RoleArn' => 'arn:aws:iam::xxxxx:role/videoUploadToS3Role', // 可存取S3的Role ARN
'RoleSessionName' => 'videoUploadToS3Role', // 可存取S3的Role
'DurationSeconds' => 86400, // 設定24hr到期
]);
result大概長這個樣子
使用Credentials內的AccessKeyId + SecretAccessKey + SessionToken
即可使用Javascript SDK做直接傳到S3上
設定Maximum session duration(最大工作階段持續時間)
可依照專案的需求
設定maxSessionDuration
上述php SDK在請求暫時憑證的參數DurationSeconds必須小於這個maxSessionDuration
S3 Bucket啟用CORS
S3 bucket的permssion CORS啟用以下設定
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"POST",
"PUT",
"DELETE"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]
前端實做大檔續傳
續傳在AWS文件名詞為Multipart upload
1. 發起續傳
使用"createMultipartUpload"發起續傳
請求成功後AWS會回一個UploadID
2. 透過確認是否有已經上傳過得檔案片段
使用"listParts"取得已經上傳過的片段
這先片段主要有兩個欄位
- PartNumber:片段索引
- ETag:AWS為該檔案片段提供的唯一標籤
如果有的話要把這些已上傳的片段存在client端
提供後續使用
3. 檔案做切片並依序上傳
使用JavaScript的slice對檔案做切片
透過"uploadPart"功能將該切片依序上傳
並且把每個片段上傳成功response的PartNubmer、ETag合併到前面已上傳的片段存好
續傳實做的部份
如果有取得已上傳的片段
當for loop檔案片段遇到該已上傳片段的索引
可以直接跳過不發上傳API
達到續傳的機制
另外要注意的是"uploadPart" API的每個檔案片段
至少要5MB以上,最多可以有10000個片段
也就是說
如果每個片段5MB,最大可上傳約48.8G的檔案(5*10000/1024)
可自行依照需求調整每個片段的大小
4. 合併所有檔案片段
當所有檔片段都上傳完成後整個上傳流程並未結束
這時候進s3 bucket看會發現還找不到上傳的檔案
因為上傳完成後還要多一個合併的動作"completeMultipartUpload"
全部上傳完後
要把前面存好的已上傳片段(PartNumber+ETag的陣列)
透過"completeMultipartUpload" API
讓AWS做合併
完成後
才會在S3看到完整的檔案
參考資料
參考影片