MithrilからRailsサーバーへのファイルアップロードではまった部分

件の通りRails製サーバーのフロントにMithrilを持ち込んでファイルアップロードしようとしていたところで少々ハマったためまとめてみました。

#　ポイントは
* FormDataはserializer指定してm.request
* RailsのCSRFTokenを忘れずに 

でした。というわけで以下にハマりつつ書いた内容を載せておきます。

# Mithril でのファイルアップロード

まずこちらの [リッチなドキュメント](http://mithril-ja.js.org/mithril.request.html#file-uploads-with-formdata)通りに、
FormData クラスと serialize オプションを使って以下のように実装してみました。

```index.html
<script src="//cdn.jsdelivr.net/mithril/0.2.0/mithril.min.js"></script>
<div id="file-uploader"></div>
```

```app.js
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インスタンスをそのまま

```js
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に付与することにして、以下のようなクラスにまとめました。

```js

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`を以下のように置き換えたところ無事アップロードできるようになりました。

```app.js
upload: () => {
   return MyRequest.postFile('/files/upload', this.file());
}
```

ちなみに上記のMithrilからのリクエストに対応したRails側の(だいぶ雑な）コードはこんな感じです。

```files_controller.rb
class FilesController
  def upload
    file_content = params['file'].read
    puts '以下のファイルがアップロードされました'
    puts file_content
    puts 'この後ファイルを元に何かする'
  end
end
```

# ハマり中参考にさせていただいた記事
[Mithril.jsで画像のアップロード、プレビューを実装してみて分かったこと](https://qiita.com/joe-re/items/483ddcbe3d04a0a1cebf)
[Mithril.jsをRailsで始める](https://qiita.com/tnantoka/items/4a6eca3d71d0b5d4067e)

# （おまけ）Mithril をそれなりに触った感想

というか正直なところJavaScript自体が初心者なため他のフレームワークとの違いなどは全然わかっていないレベルなのですが、何と言っても冒頭でも参照した抱負なサンプルを含んだドキュメントの存在が非常にありがたいです。

長らく（といっても２、３年程度ですが）サーバーサイドのステートレス・同期処理オンリーな開発を行ってきた身としてはステートフル・非同期処理なクライアント開発にて、これでもかというほど都度都度ハマりながら、やっとこさPromiseの使い方に慣れてきたというところです。

今はまだテキストデータやファイルをやり取りするレベルのフロントしか作れませんが、よりグリグリうごく物を作れるようになりたいなぁと思うこの頃です。