海の底のブログ

AWS SDK for JavaScriptでS3でファイル操作

ブラウザ単体でAmazon AWSのS3やDynamoDBやSQSなんかを操作できるAWS SDK for Javascriptが公開されたので使ってみました。

基本はNode.js用SDKと同じ

扱い方は基本的にNode.js用のSDKと同じで対応していないサービスはクラスが実装されていないので扱えないという感じです。

なのでどっちか覚えてればサーバーサイドの実装もブラウザ側での実装も両方割と簡単に対応出来ます。

誰でも読み書きできるコメント欄的な物を作ってみる

本当はGoogle+やFacebook認証を使ったりするんでしょうが今回は認証のないコメント欄的なものをS3を使って作りたいと思います。

IAMコンソールでユーザーを作成する

まず、IAMコンソールでアプリに使うユーザーを作成します。

ここで作成したユーザーのARNAccess Key IDSecret Access Keyは後でアプリから使うのでコピーしておきます。

ユーザーを作成したらS3のバケットを操作できるようにパーミッションを設定します。

設定内容は以下のような感じ。BUCKET_NAMEはアプリで使用するバケット名を記述します。

ここでは指定バケットに対してS3の全てのアクションを許可しています。

ここで例えばReadだけ不許可にすることで外部からファイルを受け取るためのアップローダーを作るということも出来そうです。

{
"Version": "2013-11-24",
"Statement": [
{
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::BUCKET_NAME"
],
"Effect": "Allow"
}
]
}

バケットを作成する

S3のコンソールでバケットを作成します。

ここでもパーミッションを設定しますが基本的にユーザー作成時に指定した内容と同じです。

先ほどコピーしておいたARNをここで記述します。

{
"Version": "2013-11-24",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "<IAMで作ったユーザーのARN>"
},
"Action": "s3:*",
"Resource": "arn:aws:s3:::BUCKET_NAME/*"
}
]
}

パーミッションを設定したら次はCORS(Cross-Origin Resource Sharing)の設定をします。

初期値ではGETしか許可されていないのでPUTも許可するようにします。

また、今回の場合、Access KeyとSecretをソースに埋め込むのでAllowedOriginを指定しないとどこからでも操作できて多分えらい目に遭います。

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>http://www.exmaple.com/</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>*</AllowedHeader>
<AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
</CORSConfiguration>

サンプルコード

とりあえず初期化処理です。 先ほど作成したAccess KeyとSecretを設定し、S3クラスを初期化します。

var messageGet, messagePost, s3;
AWS.config.update({
accessKeyId: '***********************',
secretAccessKey: '**********************************'
});
s3 = new AWS.S3({
region: 'ap-northeast-1',
maxRetries: 15
});

AWSへのリクエストは非同期で行われるので同期処理するためにJsDeferredも初期化。

Deferred.define();

で、実際の初期化処理がこちら。 バケット名とKey(ファイル名みたいなもの)とContentTypeとBody(ファイルの中身)を設定してputObjectを呼び出します。

呼び出し後は非同期で実行され、何らかの理由で失敗したらerrが成功したらresultが指定されてコールバックされます。

messagePost = function() {
var key, params;
key = new Date().getTime().toString();
params = {
Bucket: 'BUCKET_NAME'
Key: key,
ContentType: 'text/plain',
Body: $("#message").val()
};
return s3.putObject(params, function(err, result) {
if (err) {
return console.log(err);
} else {
return console.log(data);
}
});
};

読み込みも簡単です。 バケット名とKeyを指定してgetObject関数を呼び出します。 コールバックは書き込みと同じで失敗すればerrが成功すればdataにファイルの情報がセットされ返ってきます。

ここではBodyの中身を文字列化してJsDeferredに渡しています。

messageGet = function(key) {
var deferred;
deferred = new Deferred();
s3.getObject({
Bucket: 'BUCKET_NAME',
Key: key
}, function(err, data) {
return deferred.call(data.Body.toString());
});
return deferred;
};

起動時処理。 listObjects関数でバケット内のファイルの一覧を取得し順番にmessageGet関数を呼んでファイルの中身を取得してリストに詰めています。

全部取り終わってからコンソールに吐き出すようにJsDeferredを使っています。(実際にはloopじゃなくてparallelのがいいと思う)

$(function() {
return s3.listObjects({
Bucket: 'BUCKET_NAME'
}, function(error, data) {
var result;
if (error) {
return console.log(error);
} else {
result = [];
return Deferred.loop(data.Contents.length, function(i) {
return messageGet(data.Contents[i].Key).next(function(data) {
return result.push(data);
});
}).next(function() {
// 実際には投稿日順で並び替えたりする
return console.log(result);
});
}
});
});

感想のような物

このブログは静的HTMLを吐いてS3に配置していますがこんな感じのちょっとしたツールに簡単にDBやファイル保存の機能を付加できるのは大変夢が広がって面白いです。

ただ、出来る事がすごく増えるわけではないので別に既存のサーバーサイド処理を置き換えるものではなく、別の要件でサーバーを立てるならサーバーサイドでやった方が良いんじゃないかと思います。

逆に全くの新造なら全部これを使ってサーバーを立てないというサービスもあっても良いんじゃ無いかとも思ったのでとりあえずこのブログを実験台に色々試してみたいと思います。(まずはzenbackに頼ってる機能の置き換えかな)