2
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ブラウザからS3に直接ファイルをアップロード vue.js+VueDropzone

Posted at

はじめに

前回までで
Node.jsのLambdaでPre-Signed URLを発行する
Pre-Signed URLでS3に直接ファイルをアップロードする
をしたし、ブラウザからでも簡単にいけるだろうと思ってたけど、かなりハマった。
僕が一番ハマったのはsignatureVersion: 'v4'が必要ってところでした。
一応手順をメモしておいたので参考になればと思います。

S3に直接

S3に直接ファイルをアップロードするにはPre-Signed URLを使うと良いです。
ここまでは前回までで実装できています。
ブラウザからとなると、CORSの設定と、クライアントのjsの実装くらいなのでそこまでハマらないはず。って思いますよね?

CORSの設定

ブラウザからのアクセスなのでCORSの設定が必要です。
S3のバケット>アクセス権限>CORSの設定
で以下のようなxmlを記入しましよう。

s3cors.jpg

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

Pre-Signed URLの取得

ここがハマったポイントでした。

Lambda
const AWS = require('aws-sdk');
const s3 = new AWS.S3()
const params = {
  Bucket: '<バケット名>',
  Key: '<ファイルパス>',
  Expires: 100 // 期限(秒)
}

async function getPresignedUrl(){
  return new Promise((resolve,reject)=>{
    s3.getSignedUrl('putObject', params, (err, url) => {
      if (err) {
        reject(err)
      } else {
        resolve(url)
      }
    })
  })
}

exports.handler = async (event) => {
  const url = await getPresignedUrl()
  return {
    url: url
  }
}

上記のコードでurlを取得して、それでブラウザからアップロードすると403のステータスコードでSignatureDoesNotMatchという以下のようなエラーが返ってきます。

<?xml version="1.0" encoding="UTF-8"?>
<Error>
    <Code>SignatureDoesNotMatch</Code>
    <Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
    <AWSAccessKeyId>xxx</AWSAccessKeyId>
    <StringToSign>xxx</StringToSign>
    <SignatureProvided>xxx</SignatureProvided>
    <StringToSignBytes>xxx</StringToSignBytes>
    <RequestId>xxx</RequestId>
    <HostId>xxx</HostId>
</Error>

クライアントのjsの書き方が悪くて、署名がうまくマッチしてないのかなとずっとクライアントの書き方を試行錯誤していたんですが、どうやらそうじゃなかったようですw
signatureVersion: 'v4'という新しい署名の方法にすればよかったみたいです。修正版のコードは以下(設定するだけ)

Lambda修正版
const AWS = require('aws-sdk');
const s3 = new AWS.S3({
  signatureVersion: 'v4',
});
const params = {
  Bucket: '<バケット名>',
  Key: '<ファイルパス>',
  Expires: 100 // 期限(秒)
}

async function getPresignedUrl(){
  return new Promise((resolve,reject)=>{
    s3.getSignedUrl('putObject', params, (err, url) => {
      if (err) {
        reject(err)
      } else {
        resolve(url)
      }
    })
  })
}

exports.handler = async (event) => {
  const url = await getPresignedUrl()
  return {
    url: url
  }
}

このコードを実行すると、前とは少し違う形でurlが返ってきます。

フロント

今回はvue.jsでVueDropzoneというモジュールを使いました。

component
<template>
  <div>
    <vue-dropzone
      ref="myVueDropzone"
      id="dropzone"
      :options="dropzoneOptions"
      @vdropzone-sending="sending"
      method="PUT"
    >
    </vue-dropzone>
    <textarea v-model="signurl" placeholder="Signed URL" class="signedurl"></textarea>
    <button @click="uploadFile">Upload</button>
  </div>
</template>

<script>
import vue2Dropzone from 'vue2-dropzone'
import 'vue2-dropzone/dist/vue2Dropzone.min.css'

export default {
  components: {
    vueDropzone: vue2Dropzone
  },
  data() {
    return {
      signurl: '',
      dropzoneOptions: {
        url: 'http://localhost',
        method: 'PUT',
        autoProcessQueue: false,
        thumbnailWidth: 150,
        maxFilesize: 500
      }
    }
  },
  methods: {
    sending(file, xhr) {
      const _send = xhr.send
      xhr.send = () => {
        _send.call(xhr, file)
      }
    },
    uploadFile() {
      this.$refs.myVueDropzone.dropzone.options.url = this.signurl
      this.$refs.myVueDropzone.processQueue()
    }
  }
}
</script>

<style>
.signedurl {
  margin-top: 30px;
  border: 1px solid #ccc;
  width: 97%;
  height: 200px;
  padding: 10px;
}
</style>

今回はPre-Signed URLをLambdaで発行してそのURLをコピーしてtextareaに貼っ付ける形で実装してます。
本来であればここをAPI化してアップロードする前にPre-Singed URLを取得するようにすれば良いと思います。
dropzoneはデフォルトでファイルを指定した瞬間にアップロードしてしまうので、autoProcessQueue: falseを入れておくとアップロードを止めることが出来ます。
アップロードするにはthis.$refs.myVueDropzone.processQueue()を呼んでやると良いようです。
なのでこれをする前にアップロード先のURLをPre-Singed URLに変更してあげましょう。
あとは、PUTメソッドで送ることと、XHRオブジェクトのBodyにFileオブジェクトをセットすることがポイントです。
これでうまくファイルがS3にアップロードできました。
500MBくらいの動画を試しにアップロードしてみましたが、これもかなり早くアップロードすることができました!

ちなみに

vue-dropzoneにAWS S3にアップロードするような設定があるのでこれをうまく使えばこっちでもいけるかも。
サンプルコードもありますし→UploadToAWSS3.vue
でもメソッドがPOSTになるっぽいし途中でやめましたw

参考

大変参考になりました!ありがとうございました。

2
7
0

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
2
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?