10
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.

Vue.js + Vue2-DropzoneでS3の署名付きURLへファイルアップロード

Last updated at Posted at 2019-12-17

この記事はうるる Advent Calendar 2019 18日目の記事です。

はじめに

株式会社うるるにお世話になっている、フリーランスエンジニアの福田と申します。
昨日の記事で、S3の署名付きURLを用いてファイルアップロードを行う方法が紹介されていました。
そこで、「じゃあフロントエンドから送るにはどうすりゃいいの?」という記事を書いてみたいと思います。

要約

  • Vue.jsでS3の署名付きURLを用いてファイルアップロードを行う方法の紹介です
  • Vue2-Dropzoneを使用します
  • CodeSandboxを使って動かしながら読み進められるように書いてます

スタート!

CodeSandbox上でVue2-DropzoneをInstallする

  • 上記のリンクからCodeSandboxへアクセス
  • DependenciesAdd Dependencyボタンをクリック
  • Vue2-dropzoneを検索して追加

これでVue2-Dropzoneを使用できるようになりました。

とりあえず動くようにする

Vue2-dropzoneのサイトのガイドに沿って修正してみます。
App.vueを、こういう風に修正しましょう。

<template>
  <div id="app">
    <vue-dropzone ref="myVueDropzone" id="dropzone" :options="dropzoneOptions"></vue-dropzone>
  </div>
</template>

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

export default {
  name: "App",
  components: {
    vueDropzone: vue2Dropzone
  },
  data: function () {
    return {
      dropzoneOptions: {
          url: 'https://httpbin.org/post',
          thumbnailWidth: 150,
          maxFilesize: 0.5,
          headers: { "My-Awesome-Header": "header value" }
      }
    }
  }
};
</script>

Drop files here to uploadと書かれた四角い領域が表示されましたか?
そこをクリックするか、ファイルをドラッグアンドドロップすればサムネイルが表示されます。
これでUIは完成です。

Fileオブジェクトを送信できるようにする

次はFileオブジェクトを送信できるようにしましょう。
Vue2-DropzoneはFormデータとして画像データを送信しますが、署名付きURLでS3へ送る場合はFileオブジェクトを送信する必要があります。
その方法を説明します。

Vue2-Dropzoneには様々なイベントが存在します。
ファイル送信時に発火するsendingイベントにメソッドイベントハンドラを定義すると、引数としてFileオブジェクトとXHRオブジェクトを受け取ることができます。
そのXHRオブジェクトのBodyにFileオブジェクトをセットすればOKです。
また、署名付きURLへオブジェクトを送信する場合はPUTメソッドで送信する必要があるので、その設定も変えます。

こちらのissuesを参考にしました。
https://github.com/enyo/dropzone/issues/33#issuecomment-150659202

修正箇所を抜粋したものがこちら。

<!-- method に PUT を明示 -->
<!-- @vdropzone-sending を追加 -->
<vue-dropzone
  ref="myVueDropzone"
  id="dropzone"
  method="PUT"
  :options="dropzoneOptions"
  @vdropzone-sending="sending"
></vue-dropzone>
export default {
  
  methods: {
    sending(file, xhr) {
      const _send = xhr.send;
      xhr.send = function() {
        // Fileオブジェクトを送信するようにする
        _send.call(xhr, file);
      };
    }
  }
}

ファイルの送信先を動的に変える

通常、ファイルのアップロード先はoptionsurlで指定したURIになるのですが、これを署名付きURLに変えます。
DropzoneのQueueにあるファイルが処理されるタイミングでprocessingイベントが発火するので、その時にoptionsurlを変えてやります。
以下、修正箇所の抜粋です。

<!-- @vdropzone-processing を追加 -->
<vue-dropzone
  ref="myVueDropzone"
  id="dropzone"
  method="PUT"
  :options="dropzoneOptions"
  @vdropzone-sending="sending"
  @vdropzone-processing="processing"
></vue-dropzone>
export default {
  
  methods: {
    
    processing(file) {
      // 署名付きURLを取得
      // httpbin はHTTPリクエストをテストできるサービス
      const uploadUrl = "https://httpbin.org/put"
      // 実際はこんな感じになると思います
      // const uploadUrl = await axios.get('/api/signed_url')

      // Dropzoneの送信先を変える
      this.$refs.myVueDropzone.dropzone.options.url = uploadUrl
    }
  }
}
全体的にはこのようになっていると思います。
<template>
  <div id="app">
    <vue-dropzone
      ref="myVueDropzone"
      id="dropzone"
      method="PUT"
      :options="dropzoneOptions"
      @vdropzone-sending="sending"
      @vdropzone-processing="processing"
    ></vue-dropzone>
  </div>
</template>

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

export default {
  name: "App",
  components: {
    vueDropzone: vue2Dropzone
  },
  data: function() {
    return {
      dropzoneOptions: {
        url: "https://httpbin.org/post",
        method: "PUT",
        thumbnailWidth: 150,
        maxFilesize: 0.5,
        headers: { "My-Awesome-Header": "header value" }
      }
    };
  },
  methods: {
    sending(file, xhr) {
      const _send = xhr.send;
      xhr.send = function() {
        // Fileオブジェクトを送信するようにする
        _send.call(xhr, file);
      };
    },
    processing(file) {
      // 署名付きURLを取得
      // httpbin はHTTPリクエストをテストできるサービス
      const uploadUrl = "https://httpbin.org/put";
      // 実際はこんな感じになると思います
      // const uploadUrl = await axios.get('/api/signed_url')

      // Dropzoneの送信先を変える
      this.$refs.myVueDropzone.dropzone.options.url = uploadUrl;
    }
  }
};
</script>

アップロードしてみた結果がこちら。
processingメソッド内でセットしたhttps://httpbin.org/putにPUTメソッドで送信されていればOKです!
mojikyo45_640-2.gif

事前に署名付きURLを取得する

ファイル送信前に署名付きURLを取得しておけば、API呼び出しのターンアラウンドタイムを節約できます。
Dropzoneはファイルの追加のタイミングでイベントを発火するので、イベントハンドラを定義して署名付きURLを取得します。
取得したURLはFileオブジェクトに退避しておいて、ファイル送信時に使用します。

<!-- @vdropzone-file-added を追加 -->
<vue-dropzone
  ref="myVueDropzone"
  id="dropzone"
  method="PUT"
  :options="dropzoneOptions"
  @vdropzone-sending="sending"
  @vdropzone-processing="processing"
  @vdropzone-file-added="fileAdded"
></vue-dropzone>
export default {
  
  methods: {
    
    fileAdded(file) {
      // ↓↓↓ここはprocessingから移植↓↓↓
      // Fileオブジェクトに署名付きURLのプロパティを追加
      file.uploadUrl = "https://httpbin.org/put"
      // 実際はこんな感じになると思います
      // file.uploadUrl = await axios.get('/api/signed_url')
      // ↑↑↑ここはprocessingから移植↑↑↑
    },
    processing(file) {
      // Fileオブジェクトに退避しておいた署名付きURLでファイル送信先を上書き
      this.$refs.myVueDropzone.dropzone.options.url = file.uploadUrl
    }
  }
}
全体的にはこのようになっていると思います。
<template>
  <div id="app">
    <vue-dropzone
      ref="myVueDropzone"
      id="dropzone"
      method="PUT"
      :options="dropzoneOptions"
      @vdropzone-sending="sending"
      @vdropzone-processing="processing"
      @vdropzone-file-added="fileAdded"
    ></vue-dropzone>
  </div>
</template>

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

export default {
  name: "App",
  components: {
    vueDropzone: vue2Dropzone
  },
  data: function() {
    return {
      dropzoneOptions: {
        url: "https://httpbin.org/post",
        method: "PUT",
        thumbnailWidth: 150,
        maxFilesize: 0.5,
        headers: { "My-Awesome-Header": "header value" }
      }
    };
  },
  methods: {
    sending(file, xhr) {
      const _send = xhr.send;
      xhr.send = function() {
        // Fileオブジェクトを送信するようにする
        _send.call(xhr, file);
      };
    },
    fileAdded(file) {
      // ↓↓↓ここはprocessingから移植↓↓↓
      // Fileオブジェクトに署名付きURLのプロパティを追加
      file.uploadUrl = "https://httpbin.org/put";
      // 実際はこんな感じになると思います
      // file.uploadUrl = await axios.get('/api/signed_url')
      // ↑↑↑ここはprocessingから移植↑↑↑
    },
    processing(file) {
      // Fileオブジェクトに退避しておいた署名付きURLでファイル送信先を上書き
      this.$refs.myVueDropzone.dropzone.options.url = file.uploadUrl;
    }
  }
};
</script>

先程と同様の結果になっていればOKです!

余談ですが、こんなことも

サムネイルをVue.jsのコンポーネントにする

サムネイル生成時に発火するvdropzone-thumbnailイベントを利用すれば、dataUrlを含んだFileオブジェクトを取得できます。
それをコンポーネントのpropsに渡してサムネイルを描画してやれば実現可能です。
コンポーネントなのでスタイルは自由に決められますし、様々なイベントを設定することもできます。

サムネイルのドラッグアンドドロップ

サムネイルをコンポーネント化することができれば、DnDも実現可能です。
Vue.jsのDnDのライブラリは色々あると思いますが、それらと組み合わせることでリッチな画像アップローダーを簡単に作れます。

この辺の話は別の機会に記事に纏められればと思います。

終わり

Advent Calendar 18日目でした。
明日19日目は tatsukoni さんによる記事を乞うご期待!
https://adventar.org/calendars/4548

10
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
10
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?