S3へ直接ファイルをアップロードする。Node.js版

# はじめに

これがやりたかった。ただし、node.jsで。
http://qiita.com/yuku_t/items/40b7daf018d3dab48974

PHP版で解りやすいソースを貼ってくれているのを発見。これを元にアクションする事にした。
http://qiita.com/supertaihei02/items/a4f663d837e51f2f72b0

# 簡単な置き換えと思ったら知識の足らなかった事

基本は置き換えただけなので、たいした事は無いと思ったが、S3のバケットのCrossOriginは改めて調べないといけなかったので記しておく。
http://stackoverflow.com/questions/4717006/amazon-s3-and-cross-origin-resource-sharing-cors
を経由して
http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html
に辿り着いた。どこから設定するのかよくわからなかったので、ちょっと探す。

s3のバケットのリストのプロパティ、右下から設定に入れる。
![S3 Management ConsoleList.png](https://qiita-image-store.s3.amazonaws.com/0/3882/a35df081-a835-9202-1ca3-127406f45452.png)

こんな感じでどんなアクセスを許すのかを調整する。ここではlocalhost:3000からはイロイロ許可した。
![S3 Management Console.png](https://qiita-image-store.s3.amazonaws.com/0/3882/08790bba-05dd-3164-1892-b69afaa83247.png)

# コード

node.js、expressでmoment.jsを導入している。routesが下記。
app.get('/upload', routes.upload);
をしている。

```js
var moment = require('moment');
var crypto = require('crypto');

function createS3bucket(params) {

    function base64_encode(val) {
        var b = new Buffer(val);
        return b.toString('base64');
    }


    var accesskey = '[accesskey]' // TODO
    var secret = '[secret]'; // TODO
    var bucket = '[bucket]'; // TODO

    var key = '[path/to/file]'; // TODO

    var aminlater = moment(new Date().getTime() + 1000 * 60); //a minit later

    var expiration = aminlater.utc().format('YYYY-MM-DDTHH:mm:ss[Z]');
    var acl = 'public-read'; // set acl (private | public-read | public-read-write | authenticated-read | bucket-owner-read | bucket-owner-full-control)
    var status = '200'; // return this status code when upload success.

    var ctype = params['ctype'];
    var clength = params['clength'];

    var policy = {
        "expiration": expiration,
        "conditions": [
            {"bucket": bucket},
            {"key": key},
            {"acl": acl},
            {"success_action_status": status},
            {"Content-Type": ctype},
            ["content-length-range", clength, clength]
        ]
    };

    //console.log('policy', policy);

    var encodedPolicy = base64_encode(JSON.stringify(policy));
    var sh = crypto.createHmac('sha1', secret);
    sh.update(encodedPolicy);
    var signature = sh.digest('base64');

    var ret = {
        'url': 'http://' + bucket + '.s3.amazonaws.com/',
        'form': {
            'AWSAccessKeyId': accesskey,
            'signature': signature,
            'policy': encodedPolicy,
            'key': key,
            'acl': acl,
            'success_action_status': status,
            'Content-Type': ctype
        }
    };

    //sconsole.log('ret', ret);

    return ret;
}

exports.upload = function (req, res) {
    var ret = createS3bucket(req.query);
    res.json(ret);
};

```

ビューは下記。基本的に
http://qiita.com/supertaihei02/items/a4f663d837e51f2f72b0
のままです。エラー確認がしやすい用のログを入れた程度。

```html
<!doctype html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <script src="//code.jquery.com/jquery-1.10.1.min.js"></script>
    <script>
        $().ready(function () {
            $('#s3form input[type="file"]').on('change', function (e) {
                // ファイルの情報をチェックしてパラメータ生成.必要であれば許可ファイル以外をはじく.
                var file = e.target.files[0];
                var data = {ctype: file.type, clength: file.size};

                // サーバからpolicyとsignatureを取得.
                var $form = $('#s3form');
                $.ajax({
                    url: $form.attr('action'),
                    type: $form.attr('method'),
                    dataType: 'json',
                    data: data
                }).done(function (data) {
                            // サーバが返した情報をそのまま使ってFormDataを作る.
                            var name, fd = new FormData();
                            for (name in data.form) if (data.form.hasOwnProperty(name)) {
                                fd.append(name, data.form[name]);
                            }
                            fd.append('file', file); // ファイル添付.

                            // 送信
                            var xhr = new XMLHttpRequest();
                            xhr.open('POST', data.url, true);
                            xhr.onreadystatechange = function (aEvt) {
                                if (xhr.readyState == 4) {
                                    if (xhr.status == 200)
                                        console.log(xhr.responseText);
                                    else{
                                        console.log('error');
                                        console.log(xhr.responseText);
                                    }
                                }
                            };
                            xhr.send(fd);

                        })
            });
        });
    </script>
</head>
<body>
<form id="s3form" action="upload" method="get" enctype="multipart/form-data">
    <input type="file" name="file"/>
</form>
</body>
</html>
```

# おしまいに

コードを公開してくれていた方々のおかげで素晴らしく時間短縮ができたので、感謝しつつ自分の成果も上げさせて戴いた。丸パクリごめんなさい。