この記事はうるる Advent Calendar 2019 18日目の記事です。
はじめに
株式会社うるるにお世話になっている、フリーランスエンジニアの福田と申します。
昨日の記事で、S3の署名付きURLを用いてファイルアップロードを行う方法が紹介されていました。
そこで、「じゃあフロントエンドから送るにはどうすりゃいいの?」という記事を書いてみたいと思います。
要約
- Vue.jsでS3の署名付きURLを用いてファイルアップロードを行う方法の紹介です
- Vue2-Dropzoneを使用します
- CodeSandboxを使って動かしながら読み進められるように書いてます
スタート!
CodeSandbox上でVue2-DropzoneをInstallする
- 上記のリンクからCodeSandboxへアクセス
- DependenciesのAdd 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);
};
}
}
}
ファイルの送信先を動的に変える
通常、ファイルのアップロード先はoptions
のurl
で指定したURIになるのですが、これを署名付きURLに変えます。
DropzoneのQueueにあるファイルが処理されるタイミングでprocessing
イベントが発火するので、その時にoptions
のurl
を変えてやります。
以下、修正箇所の抜粋です。
<!-- @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です!
事前に署名付き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