件の通りRails製サーバーのフロントにMithrilを持ち込んでファイルアップロードしようとしていたところで少々ハマったためまとめてみました。
# ポイントは
- FormDataはserializer指定してm.request
- RailsのCSRFTokenを忘れずに
でした。というわけで以下にハマりつつ書いた内容を載せておきます。
Mithril でのファイルアップロード
まずこちらの リッチなドキュメント通りに、
FormData クラスと serialize オプションを使って以下のように実装してみました。
<script src="//cdn.jsdelivr.net/mithril/0.2.0/mithril.min.js"></script>
<div id="file-uploader"></div>
FileUploader = {};
FileUploader.vm = {
init: () => {
this.file = m.prop(null);
},
fileChange: (e) => {
this.file(e.target.files[0]);
},
upload: () => {
var formData = new FormData();
formData.append('file', this.file());
return m.request({
method: "POST",
url: '/files/upload', // Rails のエンドポイント
serializer: function() { return formData; }
});
}
};
FileUploader.controller = function() {
FileUploader.vm.init();
};
FileUploader.view = function() {
return m('div', [
m('input[type=file]', {onchange: FileUploader.vm.fileChange}),
m('button', {onclick: FileUploader.vm.upload}, 'upload')
]);
};
m.mount(document.getElementById('file-uploader'), {controller: FileUploader.controller, view: FileUploader.view});
最初のハマりポイントはただのドキュメントの確認漏れなのですが、上記のFormDataインスタンスをそのまま
m.request({
method: 'POST',
url: '/file/upload',
data: formData
});
としてdataに渡していたところリクエストのbodyが空になってしまっていました。
これはMithril requestにserializerを指定しないと、デフォルトのシリアライザの JSON.stringify()
を使ってbodyを構成してしまうため、formDataが空文字に変換されてしまっていたためでした。
というわけで基本的にはドキュメントどおりに作ったら意図したとおりのリクエストとなっていたのですが、実際にサーバーとの結合動作を試してみると、Rails側では InvalidAuthenticityToken
エラーが発生してしまいました。
Rails の CSRFToken
これはRailsには標準でCSRF対策としてCSRFTokenのチェックが入っているためでした。
実際には埋め込んであるCSRF Tokenを、 FormDataに埋め込むか、HTTP Headerに付与する必要があります。
今回のケースではMithrilからのリクエストとしてjsonでPOSTするケースもあったため、
HTTP Headerに付与することにして、以下のようなクラスにまとめました。
class MyRequest {
/** CSRF token の取得 */
static _csrfToken() {
const CSRFtoken = document.querySelector("[name=csrf-token]").getAttribute("content");
if (CSRFToken) return { CSRFToken; }
}
/** CSRF Tokenの HTTP Headerへの付与と各設定の追加*/
static _common_options(method, url) {
return {
method: method,
url: url,
config: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', MyRequest._csrftoken());
}
};
}
/** post での file upload リクエスト*/
static postFile(url, file) {
var options = MyRequest._common_options('POST', url);
var formData = new FormData();
formData.append('file', file);
options.serializer = function () { return formData; };
return m.request(options);
}
}
このクラスを使ってFileUploader.vm.upload
を以下のように置き換えたところ無事アップロードできるようになりました。
upload: () => {
return MyRequest.postFile('/files/upload', this.file());
}
ちなみに上記のMithrilからのリクエストに対応したRails側の(だいぶ雑な)コードはこんな感じです。
class FilesController
def upload
file_content = params['file'].read
puts '以下のファイルがアップロードされました'
puts file_content
puts 'この後ファイルを元に何かする'
end
end
ハマり中参考にさせていただいた記事
Mithril.jsで画像のアップロード、プレビューを実装してみて分かったこと
Mithril.jsをRailsで始める
(おまけ)Mithril をそれなりに触った感想
というか正直なところJavaScript自体が初心者なため他のフレームワークとの違いなどは全然わかっていないレベルなのですが、何と言っても冒頭でも参照した抱負なサンプルを含んだドキュメントの存在が非常にありがたいです。
長らく(といっても2、3年程度ですが)サーバーサイドのステートレス・同期処理オンリーな開発を行ってきた身としてはステートフル・非同期処理なクライアント開発にて、これでもかというほど都度都度ハマりながら、やっとこさPromiseの使い方に慣れてきたというところです。
今はまだテキストデータやファイルをやり取りするレベルのフロントしか作れませんが、よりグリグリうごく物を作れるようになりたいなぁと思うこの頃です。