SuperAgentとMulterでファイルアップロード

ファイルアップロードの機能を作るのに、二日ハマりました。前提としてReactで作ったFormから投げ、Express4で書いたサーバで受けるというところでの通信部分だけです。まずガチなところとしてサーバを作ります。

var express = require('express');
var multer  = require('multer');
var server = express();
var upload = multer({ dest: "./uploads/" });
server.post("/confirm", upload.single("contact_attachment"), function(req, res) {
    console.log(req.headers);
    console.log(req.params);
    console.log(req.body);
    console.log(req.file);
    res.sendStatus(200);
});
server.use(express.static('public'));
server.listen(process.env.PORT || 8080);

multerはマルチパートのリクエストボディを処理してくれるExpressのMiddlewareで、npmでサクッとインストール。いろんな書き方がありますが、uploadというインスタンスを作っておいてExpressのルーターチェーンに差し込みます。メソッド引数に並べて渡すだけでチェーンできるのは発見だった。違う書き方としては、以下もあります。

server.use("/confirm", upload.single("contact_attachment"));
server.post("/confirm", function(req, res) {
    console.log(req.body);
    res.sendStatus(200);
});

ルーティングの書き方は同じことでもいろんな書き方できるから、ググってる時に混乱多い。みんなマニアックなテクニック使いまくってること多くて慣れるまでルーティングがどうなってるかなかなか掴めないじゃないか!

一方で、ドハマりしたクライアント。迷走の先に気がついたのは、簡単なものとしてプレーンHTMLさえ書けばOKなんでまずそれ作って、後でもっと難しいことをやる時に、それぞれヘッダやパラメータやボディなどに何を書かれているかを見比べることでした。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
    <form method="post" enctype="multipart/form-data" action="/confirm">
        <div><input type="text" name="contact_name"></div>
        <div><input type="file" name="contact_attachment"></div>
        <div><button type="submit">UPLOAD</button></div>
    </form>
</body>
</html>

タグだけで書いたこれが動かないとしょうがない。だから一番初めにサーバがちゃんと動くことを確認すべきだった(私はやらなかったためサーバもクライアントも変更し続けて迷走が長引いた)。multerもたった数行のコードなのに、一発では動くものを書けなかったからねー。

クライアントはReactの中から、初めはjQueryの$.ajax()でやってたけど、どうにも動かなかった。普通にフォームPOSTするだけならすぐだったけど、ファイル添付されたmultipartができなかった。そんな中、ReactではDOMを直に触るの邪道っぽいこともあって通信にしか使わないので、ダメ元で通信専門のライブラリを持ってきました。SuperAgentです。

github.com

まあ、やることは$.ajax()と大差ない。

var React = require("react");
var SuperAgent = require("superagent");
var ContactForm = React.createClass({
    handleSubmit: function(event) {
        event.preventDefault();
        this.setState({
            valid_button: false
        });
  // var formData = new FormData() <==FormDataを使って迷走
        SuperAgent
            .post("/confirm")
            //.type("multipart/form-data") <==これをやるとboundaryが設定されなくなって迷走
            //.set({contact_name: this.state.contact_name}) <==set使って迷走
            .field("contact_name", this.state.contact_name)
            .field("contact_kana", this.state.contact_kana)
            .field("contact_email", this.state.contact_email)
            .field("contact_company", this.state.contact_company)
            .field("contact_message", this.state.contact_message)
            .attach("contact_attachment", this.state.contact_attachment)
            .end(function(err, res) {
                if(res.statusCode == 200) {
                    this.setState({
                        text_guidance: GUIDANCE_3,
                        style_form: {"display": "none"}
                    });
                } else {
                    this.setState({
                        valid_button: true,
                        text_guidance: GUIDANCE_4
                    });
                }
            }.bind(this));
        return false;
    },
   //以下、renderとか色々ごっそり省略
});

頭で"superagent"をrequireしてから、あとはfieldとattachでコツコツ積み上げます。multipartの場合は他のことをしちゃダメだった。より一般的なsetメソッドでパラメータを積んだりすると自動的にcontent-typeがjsonにされちゃう。また、typeで明示的にcontent-typeをmultipart/form-dataに設定すると続いてほしいboundaryのぐちゃぐちゃ文字列が設定されなくなるので死亡。

ライブラリのソース読んで理屈と癖がわかって、動くようになってみるとSuperAgentとMulterはなかなか良い。捗る。