LoginSignup
2
1

More than 5 years have passed since last update.

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

Posted at

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

 ポイントは

  • FormDataはserializer指定してm.request
  • RailsのCSRFTokenを忘れずに

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

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

まずこちらの リッチなドキュメント通りに、
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インスタンスをそのまま

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

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で画像のアップロード、プレビューを実装してみて分かったこと
Mithril.jsをRailsで始める

(おまけ)Mithril をそれなりに触った感想

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

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

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

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