マルチパートアップロード機能を利用するとS3へのファイルアップロードを
並列に行うことや中断・再開といったオペレーションが可能となります。
また通常のアップロードと比較しファイルサイズの上限も緩和されますので
大容量のファイルアップロードには必須の機能になります。
今回はLambdaとブラウザ(RESTAPI)を利用してマルチパートアップロードを行ってみます。
作業の流れは以下になります。
- S3バケットの作成とCORS設定
- マルチパートアップロードのID発行(マルチパートアップロードの開始)
- 署名付きURLの発行 ・・・ファイル分割数分実行
- パートのアップロード ・・・ファイル分割数分実行
- マルチパートアップロードの完了
方式は色々ありますが今回は2,3,5はLambdaで行い、4をブラウザで実装します。
・マルチパートアップロードを使用したオブジェクトのアップロードとコピー
1.S3バケットの作成とCORS設定
マルチアップロードでは「ETag」を利用するため、ExposeHeadersの指定を行います。
アップロード用に任意のバケットを作成し、
バケットのCORS設定を以下のように設定します。
CORS設定はコンソールの
[アクセス許可] > [Cross-Origin Resource Sharing (CORS)]
から設定出来ます。
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"HEAD",
"GET",
"PUT"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": [
"ETag",
"x-amz-meta-custom-header"
]
}
]
2.マルチパートアップロードのID発行
create_multipart_uploadを呼び出し、「アップロードID」を取得します。
パートのアップロード、完了、中止等にはこの「アップロードID」が必要です。
Lambdaで発行しクライアントに返却します。
・boto3リファレンス create_multipart_upload
import boto3
import json
from botocore.client import Config
REGION_NAME = 'ap-northeast-1'
S3_BUCKET_NAME = 'yourBacketName'
S3_OBJECT_NAME = 'fileupload/'
def lambda_handler(event, context):
file_name = event['queryStringParameters']['fileName']
s3 = boto3.client(
service_name='s3',
region_name=REGION_NAME,
config=Config(signature_version='s3v4')
)
# マルチアップロード用IDを生成
response = s3.create_multipart_upload(
Bucket=S3_BUCKET_NAME,
Key=S3_OBJECT_NAME + file_name,
ContentType='multipart/form-data')
upload_id = response['UploadId']
resonse_body = {
'upload_id': upload_id,
}
return {
'statusCode': 200,
'isBase64Encoded': False,
'headers': {
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'OPTIONS,POST,GET,PUT',
},
'body': json.dumps(resonse_body)
}
3.署名付きURLの発行
マルチパートアップロードを行う際、分割したファイル毎にパートNoを付与します。
クライアントからは分割した数だけ本APIを呼び出しを行い、
アップロードデータと一緒にパートNoをクライアントからリクエストで受け取ります。
署名付きURLには以下を含めておく必要があります。
・マルチパートアップロードID
・パートNo
・S3オブジェクトキー(アップロード先)
import boto3
import json
import logging
from botocore.client import Config
REGION_NAME = 'ap-northeast-1'
S3_BUCKET_NAME = 'yourBacketName'
S3_OBJECT_NAME = 'fileupload/'
DURATION_SECONDS = 60*60*24
def lambda_handler(event, context):
# 署名付きURLに含めるファイル名、パートNo、アップロードIDを取得
file_name = event['queryStringParameters']['fileName']
part_number = int(event['queryStringParameters']['partNumber'])
upload_id = event['queryStringParameters']['uploadId']
upload_user = event['queryStringParameters']['uploadUser']
s3 = boto3.client(
service_name='s3',
region_name=REGION_NAME,
config=Config(signature_version='s3v4')
)
# 署名付きURLを生成
preSignedUrl = s3.generate_presigned_url(
ClientMethod = 'upload_part',
Params = {
'Bucket': S3_BUCKET_NAME,
'Key': S3_OBJECT_NAME + file_name,
'UploadId': upload_id,
'PartNumber': part_number,
},
ExpiresIn = DURATION_SECONDS,
HttpMethod = 'PUT')
response={
'presigned_url': preSignedUrl,
}
return {
'statusCode': 200,
'isBase64Encoded': False,
'headers': {
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'OPTIONS,GET',
},
'body': json.dumps(response)
}
4.パートのアップロード
Lambdaから受け取った署名付きURLに対してブラウザからPUTを行います。
ファイル分割処理はややこしいので割愛しますが、アップロード自体は
以下のようにPUTを呼び出すだけです。
presigned_url:Lambdaから受け取った署名付きURL
data:送信データ
PUTメソッドのレスポンスで「ETag」が返却されます。
このEtagはパートNoと紐づくものになります。
アップロード完了を行うために、完了したすべての「ETag」「パートNo」が
必要になるので保持しておきます。
fetch(presigned_url, {
method: 'PUT',
headers,
body: data,
}).then((res) => {
multipartMap.Parts.push({
ETag: res.headers.get('ETag'),
PartNumber: partNumber,
});
送信データの分割、読み込みは以下のように行えます。
function readBlobAsByte(partNumber, blob, offset, size, callback) {
var reader = new FileReader();
reader.onload = function(e) {
let u8array = new Uint8Array(e.target.result);
callback(e.target.readyState, u8array, partNumber);
}
var readsize = size;
if(blob.size < offset + size) {
readsize = blob.size - offset;
}
slice = blob.slice(offset, offset + readsize, blob.type);
reader.readAsArrayBuffer(slice);
}
5.マルチパートアップロードの完了
マルチパートアップロードの完了を通知します。
この完了通知を行うことで、パート単位で送信していたデータが連結されて
正式なS3オブジェクトが生成されます。
この操作を行わない場合、送信した中途半端なデータがS3上に残ってしまいます。
課金対象にもなるので注意してください。
完了にはアップロードID、パート番号とそれに対応する ETag 値のリストが必要です。
import json
import boto3
REGION_NAME = 'ap-northeast-1'
S3_BUCKET_NAME = 'yourBucketName'
S3_OBJECT_NAME = 'fileupload/'
def lambda_handler(event, context):
body_json = json.loads(event['body'])
parts = body_json['Parts']
upload_id = body_json['UploadId']
file_name = body_json['file_Name']
client = boto3.client('s3')
response = client.complete_multipart_upload(
Bucket=S3_BUCKET_NAME,
Key=S3_OBJECT_NAME + file_name,
MultipartUpload={'Parts' : parts},
UploadId=upload_id
)
response = client.get_object(
Bucket=S3_BUCKET_NAME,
Key=S3_OBJECT_NAME + file_name
)
return {
'statusCode': 200,
'headers': {
'Access-Control-Allow-Headers': 'Host,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'OPTIONS,POST,GET,PUT'
},
'body': json.dumps('OK')
}
・boto3リファレンス complete_multipart_upload
以上