AWS SDK for Javascriptを使ってブラウザーから画像をS3にアップロードする

Table of Contents

Table of Contents

以前画像をS3にアップロードする時に、まずはサーバーに画像を一時的に保存してから、S3にアップロードしましたが、今回はブラウザーでサーバーに画像を保存せずにそのままS3にアップロードしてみました。

目標を達成するため、下記のステップで行いました。

  • AWS SDK for Javascriptの導入
  • 画像ファイルをアップロードするUI作成
  • AWS側の設定
  • S3のCORSの設定

AWS SDK for Javascriptを導入

S3にファイルアップロード機能を実装するhtmlファイルのheadタグにAWS SDK for Javascriptを導入する。

<html>
  
  <head>
    ...

    <script src="//sdk.amazonaws.com/js/aws-sdk-2.556.0.min.js" />
    
    ...
  </head>

</html>

画像ファイルをアップロードするUI作成

inputタグを使って、アップロードのUIを作成する。

また、アップロードしたい画像を画面に表示するようなプレビューUIも作成する。

<input type="file" name="imageFile1" id="imageFile1" accept="image/*"  onchange="handleFiles(event);">
<!-- ファイルサイズの表示 --> 
<div id="msg"></div>
<!-- 画像のプレビュー -->
<img style="max-width:100%" id="img_source">
<!-- 送信ボタン -->
<input type="submit" id="send" />

<script>
  function handleFiles(event) {
    var reader = new FileReader();
    var files = event.target.files;
    var image = document.getElementById("img_source");
    reader.onload = function (event) {   
        image.onload = function (){
        document.getElementById("msg").innerHTML =
            image.width+"x" +image.height;        
        };
        image.onerror = function () { 
            alert('この画像は読み込めません。');  
        };
        image.src = reader.result;       
    };
    reader.onerror = function (){  
        alert('このファイルは読み込めません。');  
    }
    if (files[0]){    
        reader.readAsDataURL(files[0]);
    }
  }
</script>


AWSの設定

AWSの設定について、公式ドキュメントを参照します。

ブラウザスクリプトへの認証されていないアクセスをサポートするため、Amazon Cognito IDプールを作成する。

参考:https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/getting-started-browser.html


S3のCORSの設定

こちらも公式ドキュメントを参照します。

参考:https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/s3-example-photo-album.html

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>Authorization</AllowedHeader>
        <AllowedHeader>Content-*</AllowedHeader>
        <AllowedHeader>Host</AllowedHeader>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

S3にアップロードのJavascript実装

AWS S3の機能を使うため、AWS関連の設定を実装し、送信ボタンを押すとuploadToS3()関数を実行する。

$(function() {
  // Amazon Cognito IDの設定から取得できる情報
  var albumBucketName = "your bucket name";
  var bucketRegion = "ap-northeast-1";
  var IdentityPoolId = "ap-northeast-1:xxxxx";
  
  // AWS設定
  AWS.config.update({
      region: bucketRegion,
      credentials: new AWS.CognitoIdentityCredentials({
          IdentityPoolId: IdentityPoolId
      })
  });
  
  var s3Client = new AWS.S3({
      apiVersion: "2006-03-01",
      params: { Bucket: albumBucketName }
  });
  
  // 送信ボタンを押してS3にアップロードする
  $('#send').click(function(e) {
    uploadToS3(files);
  });
  
  function uploadToS3(file) {
    var timestamp = new Date().getTime();
    var filename = "file_" + timestamp;
    var params = {
        Bucket: albumBucketName,
        Key: filename + `.jpg`, 
        ContentType: file.type, 
        Body: file, 
        ACL: "public-read"
    };
    s3Client.upload(params, function(data, err) {
      if (err) {
        console.log(err);
      } else {
        console.log(data);
      }
    });
  }
})


おまけ:複数ファイルを同期でアップロードする

複数画像を非同期でアップロードしたくないなので、順番で送信する方法を探しました。

まず、下記のように、3枚の画像を選択できるようにします。

<input type="file" name="imageFile1" id="imageFile1" accept="image/*"  onchange="handleFiles(event);">
<input type="file" name="imageFile2" id="imageFile2" accept="image/*"  onchange="handleFiles(event);">
<input type="file" name="imageFile3" id="imageFile3" accept="image/*"  onchange="handleFiles(event);">

同期のため、送信処理の部分では、async,awaitを使います。

また、AWS.S3().upload()のpromiseを用意していますので、それを使うようにします。 const stored = await s3Client.upload(params).promise() S3にアップロードした画像のURLはstoredの中にあります。

$('#send').click(async function(e) {
  var files = [];
  [1,2,3].forEach( function(v) {
    if ($(`#imageFile${v}`)[0].files.length > 0) {
      files.push($(`#imageFile${v}`)[0].files[0]);
    }
  });

  await uploadToS3(files);
});

async function uploadToS3(files) {
  var imageUrls = []; 
  var timestamp = new Date().getTime();
  var filename = "file_" + timestamp;
  try {
    for(var index = 0; index < files.length; index++) {
        item = files[index];
        var params = {
            Bucket: albumBucketName,
            Key: filename + `-${index}.jpg`, 
            ContentType: item.type, 
            Body: item, 
            ACL: "public-read"
        };
        console.log("index:" + index);
        const stored = await s3Client.upload(params).promise()
        console.log(stored);
        imageUrls.push(stored.Location);
    }
  } catch(err) {
      console.log(err)
      alert("写真アップロードができませんでした。\n再度アップロードしてみてください。");
  }
  return imageUrls;
}

参考