Dart で AWS S3 にファイルアップロードを行う

April 17, 2023

dart

aws

S3 に Access Key ID と Secret Access Key でファイルアップロードする

Dart で AWS S3 にファイルアップロードを行う方法をまとめる。Amplify や Cognito を使った手順はネット上に多数あるが Access Key ID と Secret Access Key を使って直接アップロードする方法はあまりなかったので公開する(Dart 向けに公式の AWS SDK は無いようなので、公式パッケージ持ってきてはい終わり、とはならなかった)。

まず、AWS 上で事前準備を行う。ここは実装に使う言語に依存しない部分。具体的には以下の通り。

IAM でユーザーに与える権限は以下の通り。Web UI 上でポリシー作成時に書き込み可を選択して作成。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": [
        "s3:CreateAccessPoint",
        "s3:SubmitMultiRegionAccessPointRoutes",
        "s3:PutAnalyticsConfiguration",
        "s3:PutAccelerateConfiguration",
        "s3:PutAccessPointConfigurationForObjectLambda",
        "s3:DeleteObjectVersion",
        "s3:RestoreObject",
        "s3:DeleteAccessPoint",
        "s3:CreateBucket",
        "s3:DeleteAccessPointForObjectLambda",
        "s3:ReplicateObject",
        "s3:PutEncryptionConfiguration",
        "s3:DeleteBucketWebsite",
        "s3:AbortMultipartUpload",
        "s3:PutLifecycleConfiguration",
        "s3:UpdateJobPriority",
        "s3:DeleteObject",
        "s3:CreateMultiRegionAccessPoint",
        "s3:DeleteBucket",
        "s3:PutBucketVersioning",
        "s3:PutIntelligentTieringConfiguration",
        "s3:PutMetricsConfiguration",
        "s3:PutBucketOwnershipControls",
        "s3:PutReplicationConfiguration",
        "s3:DeleteMultiRegionAccessPoint",
        "s3:PutObjectLegalHold",
        "s3:InitiateReplication",
        "s3:UpdateJobStatus",
        "s3:PutBucketCORS",
        "s3:PutInventoryConfiguration",
        "s3:PutObject",
        "s3:PutBucketNotification",
        "s3:DeleteStorageLensConfiguration",
        "s3:PutBucketWebsite",
        "s3:PutBucketRequestPayment",
        "s3:PutObjectRetention",
        "s3:PutBucketLogging",
        "s3:CreateAccessPointForObjectLambda",
        "s3:PutBucketObjectLockConfiguration",
        "s3:ReplicateDelete"
      ],
      "Resource": "arn:aws:s3:::{your bucket name}/*"
    },
    {
      "Sid": "VisualEditor1",
      "Effect": "Allow",
      "Action": ["s3:PutStorageLensConfiguration", "s3:CreateJob"],
      "Resource": "*"
    }
  ]
}

Dart のコードは aws_signature_v4/example を参考に実装。S3 へのファイルアップロード部分だけ参考にする。

必要な Dart パッケージを取得するために pubspec.yaml に以下を追記し、pub get する。

...
dependencies:
  aws_common: '>=0.4.0 <0.5.0'
  aws_signature_v4: '>=0.3.1+2 <0.4.0'
  path: ^1.8.3
...

aws_common や aws_signature_v4 はバージョンを上げすぎると動作しなくなるため注意。代わりに minio を使った方法が Flutter から S3 を操作する ① -設定編- で紹介されており参考になる。

ファイルアップロードの Dart 実装は以下になる。uploadFile 関数を外部のファイルから呼び出す形で記述している。

import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:aws_common/aws_common.dart';
import 'package:aws_signature_v4/aws_signature_v4.dart';

const signer = AWSSigV4Signer(
  credentialsProvider: AWSCredentialsProvider(AWSCredentials(
      "YOUR AWS ACCESS KEY ID", "YOUR AWS SECRET KEY")),
);

// Set up S3 values
final region = 'ap-northeast-1';
final bucket = 'YOUR BUCKET NAME';
final host = '$bucket.s3.$region.amazonaws.com';
final scope = AWSCredentialScope(
  region: region,
  service: AWSService.s3,
);
final serviceConfiguration = S3ServiceConfiguration();

Future<void> uploadFile(String filename) async {
  final file = File(filename).openRead();
  final path = '/${p.basename(filename)}';
  final request = AWSStreamedHttpRequest.put(
    Uri.https(host, path),
    body: file,
    headers: {
      AWSHeaders.host: host,
      AWSHeaders.contentType: 'text/plain',
    },
  );

  stdout.writeln('Uploading file $filename to $path...');
  final signedRequest = await signer.sign(
    request,
    credentialScope: scope,
    serviceConfiguration: serviceConfiguration,
  );
  final uploadResponse = await signedRequest.send().response;
  final uploadStatus = uploadResponse.statusCode;
  stdout.writeln('Upload File Response: $uploadStatus');
  if (uploadStatus != 200) {
    exitWithError('Could not upload file');
  }
  stdout.writeln('File uploaded successfully!');
}

/// Exits the script with an [error].
Never exitWithError(String error) {
  stderr.writeln(error);
  exit(1);
}

以上。