7
5

More than 1 year has passed since last update.

FlutterでWeb,Android,iOSの3プラットフォームから同じコードでS3に写真をアップロードする

Last updated at Posted at 2020-06-28

##概要
6月にWeb向けのImagePickerが出ていたので、ネイティブアプリとブラウザアプリを同じソースコードでビルドして、どのデバイスでもファイルアップロード出来るのかというのを試しました。
AndroidとiOSに加えてWebでもPickerが同じ処理で使えるようになったのは結構画期的だなと思ってのメモです。内容はクライアント側に重点を置いています。

1. パッケージをインポートする。

pubspec.yamlにimage_pickerとimage_picker_for_web、amazon_cognito_identity_dart_2を追加する。
私が試したバージョンは以下です。

image_picker: ^0.6.7
image_picker_for_web: ^0.1.0+1
amazon_cognito_identity_dart_2: ^0.1.14

2. 画像取得処理を書く。thenを使用してますが、awaitでも問題ありません。

import 'package:image_picker/image_picker.dart';
~
~
var picker = ImagePicker();
picker.getImage(source: ImageSource.gallery)
    .then((PickedFile value) {
      // ここから必要に応じて後続の処理を呼び出す
    });

3. アップロード処理を書く。

こちら( https://pub.dev/packages/amazon_cognito_identity_dart_2#signing-requests )のページのFor S3 Uploadsをベースにしますが、PickedFileはlengthが取れないので少し改造します。PolicyやSigV4はそのままでmain()だけ変更します。
私の場合はuploadImageとして以下のようにしました。

  Future<String> uploadImage(PickedFile file) async {
    final _credentials = CognitoCredentials(ID_POOL_ID, _userPool);
    final _cognitoUser = CognitoUser(_username, _userPool, clientSecret: _clientSecret);
    final authDetails =
    AuthenticationDetails(username: _username, password: _passwd);
    CognitoUserSession _session;
    try {
      _session = await _cognitoUser.authenticateUser(authDetails);
    } catch (e) {
      print(e);
      return null;
    }
    await _credentials.getAwsCredentials(_session.getIdToken().getJwtToken());
    final uri = Uri.parse(_s3Endpoint);
    final req = http.MultipartRequest("POST", uri);
    final multipartFile = http.MultipartFile.fromBytes('file', await file.readAsBytes(),contentType: MediaType('application', 'octet-stream'));
    logger.d('Policy.fromS3PresignedPost');
    String filename = 'test.jpg';
    final policy = Policy.fromS3PresignedPost(
        filename,
        _bucketName,
        150,
        _credentials.accessKeyId,
        10000000,
        _credentials.sessionToken,
        region: REGION);
    final key = SigV4.calculateSigningKey(
        _credentials.secretAccessKey, policy.datetime, REGION, 's3');
    final signature = SigV4.calculateSignature(key, policy.encode());
    req.files.add(multipartFile);
    req.fields['key'] = policy.key;
    req.fields['acl'] = 'public-read';
    req.fields['X-Amz-Credential'] = policy.credential;
    req.fields['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256';
    req.fields['X-Amz-Date'] = policy.datetime;
    req.fields['Policy'] = policy.encode();
    req.fields['X-Amz-Signature'] = signature;
    req.fields['x-amz-security-token'] = _credentials.sessionToken;
    try {
      final res = await req.send();
      logger.d('res:${res.toString()}');
      await for (var value in res.stream.transform(utf8.decoder)) {
        logger.d(value);
      }
      return filename;
    } catch (e, stacktrace) {
      logger.e(e);
      logger.e(stacktrace);
    }
    return null;
  }
}

ID_POOL_IDや_userPoolは各環境に合わせて定義してください。
fromS3PresignedPostへ渡すパラメータmaxFileSizeは一旦10Mとしているので必要に応じて変更してください。
リンク先と主に異なるのは、final file = File(path.join('/path/to/my/folder', 'square-cinnamon.jpg'));PickedFileに置き換わっていて、
streamからMultipartFileを生成する処理

final stream = http.ByteStream(DelegatingStream.typed(file.openRead()));
final multipartFile = http.MultipartFile('file', stream, length,
      filename: path.basename(file.path));


bytesからMultipartFileを生成する処理

final multipartFile = http.MultipartFile.fromBytes('file', await file.readAsBytes(),contentType: MediaType('application', 'octet-stream'));

になっております。目的としてはlengthが取れないからbytesでのリクエストにしました。
クライアント側はこんな感じです。
他に

  • Cognitoのユーザプール作成
  • IDプール作成
  • IAM追加
  • S3のCORS設定
  • iOSのNSPhotoLibraryUsageDescription

が必要です。特にS3のCORS設定はブラウザからのみアップロードできない問題に直面するので、ハマりポイントとして記載します。(その他は調べれば出てくると思うので他の方の記事をご参照ください。)

4. サーバ側番外編 S3でCORSの設定

こちら( https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/cors.html )のCORS設定例を参考にS3のバケット>アクセス権限>CORSの設定で設定してください。AllowedOriginだけ各環境に合わせて変える必要があります。
ローカル環境からの試しであればアスタリスク*で設定すると早いです。

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <CORSRule>
    <AllowedOrigin>https://example.org</AllowedOrigin>
    <AllowedMethod>HEAD</AllowedMethod>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
    <ExposeHeader>ETag</ExposeHeader>
    <ExposeHeader>x-amz-meta-custom-header</ExposeHeader>
  </CORSRule>
</CORSConfiguration>
7
5
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5