やりたかった事
- ajaxでファイルアップロード(この時点でサーバにリソース食わせたくないのでチェックのみ)
- PHPでファイルチェック + S3のポリシー作成をして返却
- XMLHttpRequestでファイルデータをS3に直接送信
- ウマー
必要なので調査したけどぴったりなものがなかったのでサーバサイドがRuby版の以下を参考にPHPでスリムに作り直した。
Qiitaの画像アップロード機能も簡単に実装できる。そう、S3ならね。
少々詰まった部分もあったけど動作したので備忘録的に。
ソース
まずはサーバサイドから。
getPolicy.php
<?php
$accesskey = 'Your AWS Access Key' // TODO
$secret = 'Your AWS Access Secret'; // TODO
$bucket = 'Your Bucket Name'; // TODO
$key = 'path/to/updatefilename'; // TODO
$expiration = gmdate("Y-m-d\TH:i:s\Z",strtotime("1 minute")); // Policy Limit
$acl = 'public-read'; // set acl (private | public-read | public-read-write | authenticated-read | bucket-owner-read | bucket-owner-full-control)
$status = '200'; // return this status code when upload success.
$ctype = $_GET['ctype'];
$clength = $_GET['clength'];
$policy = <<<EOS
{
"expiration": "$expiration",
"conditions": [
{"bucket": "$bucket"},
{"key": "$key"},
{"acl": "$acl"},
{"success_action_status": "$status"},
{"Content-Type": "$ctype"},
["content-length-range", $clength, $clength]
]
}
EOS;
//var_dump($policy);
$signature = hash_hmac("sha1", base64_encode($policy), $secret, true);
exit(json_encode(array(
'url' => 'http://'.$bucket.'.s3.amazonaws.com/',
'form' => array(
'AWSAccessKeyId' => $accesskey,
'signature' => base64_encode($signature),
'policy' => base64_encode($policy),
'key' => $key,
'acl' => $acl,
'success_action_status' => $status,
'Content-Type' => $ctype
)
), true));
?>
TODOの部分は書き換えが必要ですよ。
次にクライアント側。
index.html
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://ogp.me/ns/fb#">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.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.send(fd);
})
});
});
</script>
</head>
<body>
<form id="s3form" action="getPolicy.php" method="get" enctype="multipart/form-data">
<input type="file" name="file"/>
</form>
</body>
</html>
困った事
送信成功後もステータスコード200でFirebugsがエラー吐く
文字が1文字も出力されない事が問題かと思って(Flashがそうだったような気がして)
success_action_redirect
というパラメータでリダイレクト先を指定すると転送されるのでそちらから結果を返してみたけど結果変わらず。。
少し調べてみるとクロスドメイン的な問題だろうと思いスルーする事に。
※FireBugsには以下の様に出力されているのだけど同じリクエスト投げると何も返らない・・・謎
XML パースエラー: 要素が見つかりません。 URL: moz-nullprincipal:{8712ffa2-7301-d54a-aa47-f22e156c6d6d} 行番号: 1, 列番号: 1:
400 Bad Requestが返ってくる
これはLive Http headersで解析したらいくつか原因があった
・作成したPolicyの内容とフォームから送信しているパラメータが違っている => 上記ソースの通りcontent-length-rangeのみはスルーされているよう
・実験しすぎてPolicy有効期限の1分を超えていた
・Policyがjson形式になっていない => 通常のjson(コロン区切りの{}閉じ)と入れ後形式(カンマ区切りの[]閉じ)の間違い
なぞなぞでした。
以上。