Ruby で Google Storage for Developers API を使ってみました
今年の Google I/O で発表された、 Google Storage for Developers (以下GSfD)というサービスをご存知でしょうか。数ギガバイトクラスの巨大なデータを、 RESTful API で使って Google クラウドに保存・アクセスできるという、いわば Google 版の Amazon S3 です。発表直後にダメもとでアカウントを申請した後すっかり忘却の彼方だったのですが、先日になって唐突にインビテーションが届いたので、本日はその使い方をご紹介します。
GSfD には Web インターフェースやコマンドラインツール、 Python のクライアントライブラリが用意されていますが、 Web 開発者としてはやっぱり API 経由での使い方をマスターしたいところ。ここは敢えて Ruby から Web API を直で叩く方法に挑戦してみました。日本語はもちろん英語でも公式サイト以外にはほとんど情報がないので、参考にしていただければと思います。
Google Storage for Developers とは
最初に書いたとおり、 GSfD は Amazon S3 の Google 版とでも言うべきクラウドストレージサービスです。主な特徴を以下のとおりです。
- 保存したデータは米国の複数のデータセンターでレプリケートされる。
- 100Gbyteまでのファイルをアップロード可能。
- プレビュー中は 100Gbyte の容量と 300Gbyte/month の帯域が無料で利用できる。
- 強い一貫性 (Read-your-writes consistency) を保証。つまり更新・削除は即時反映される。
- アクセス権を細かく設定可能。
- SSLサポート。
- 独自ドメインの URL によるアクセスも可能。
現在はユーザーを限定したプレビュー公開なので、利用したい場合は公式サイトの右のカラムにある「Sign up on the Google Storage waitlist.」のリンクからアカウントを申請してください。
アカウントが有効になると、以下のような Web インターフェースでファイルの保存や管理などが可能です。
Google Chrome ならファイルのドラッグ&ドロップでアップロードできて(他のブラウザでは未確認)、ぶっちゃけ Google Docs より使い易いです。 GSUtil というコマンドラインツールも用意されています。容量は 10Gbyte くらいでいいから、普通にストレージサービスとして公開してくれないかな・・・。
話がずれましたが、 GSfD はサイズの大きなファイルをクラウド上に保存する便利な API を提供してくれます。後ほど実際に Ruby スクリプトでアクセスする例を示しますが、まずは API を理解するために必要な知識を以下に解説しておきます。
バケットとオブジェクト
GSfD では、データは「バケット」と「オブジェクト」という 2 つの階層で管理されます。オブジェクトは実際のデータを保持するもので、名前などのメタデータとデータ本体で構成されます。バケットは複数のオブジェクトを格納する入れ物で、すべてのオブジェクトはいずれかひとつのバケットに所属します。バケットもオブジェクトも入れ子にはできないので、 GSfD ではフォルダ階層のようなものは作れません(Web インターフェースではフォルダが作れますが、その仕組みは後述)。
現在のところ、バケットはユーザーごとに最大 1,000 個まで作れます。それぞれのバケットは名前で識別しますが、名前空間が全ユーザー共通なので、既に他のユーザーが使っている名前は付けられません。また、バケット名に使える文字は基本的に英数小文字とハイフンのみです。
オブジェクトは容量が許せばいくつでも作成可能です。それぞれのオブジェクトは名前で識別しますが、名前空間はバケットごとに独立しているので、同じバケット内で重複しなければ自由に付けられます。使える文字も、UTF-8エンコード後で 1,024 バイト以内であれば制限はありません。
オブジェクト名の面白い点は、スラッシュも名前に含められるところです。そしてオブジェクトのリストを取得する際に前方一致検索が可能なので、 "path/to/my/picture.jpg" のようなオブジェクト名を付けることで、フォルダ階層を実現できます。・・・と公式ドキュメントには書いてありますが、ちょっと無理があるんじゃないかな(^^;)。少なくとも、ローカルファイルシステムでのフォルダのような柔軟さは期待しない方が良いかと思います。
アクセスキー
API 経由で GSfD にアクセスするには、 Key Management のページで取得できる API キーが必要です。 GSfD が使える Google アカウントでアクセスすると「Create new access key」というボタンが表示されるので、それをクリックすると API キーが生成されます。
API キーはアクセスキーと秘密鍵 (Secret) の対で構成されます。アクセスキーはリクエストの HTTP ヘッダに直書きするものなのでバレてもかまいませんが、秘密鍵を知られると GSfD の全アクセスを奪われてしまいます。 API キーは同時に 5 つまで生成できるので、目的に応じて使い分けたり、ローテーションしたりするのが良いでしょう。
アクセス権
GSfD では、バケットやオブジェクトのアクセス権を以下のスコープに対して設定できます。
- 特定の Google アカウント
- Google グループ
- Google Apps ドメイン(プレビューでは未実装?)
- すべての(Google アカウントによる)認証ユーザー
- すべてのユーザー
これらのユーザーに対して読み込みや書き込みなどの権限を自由に与えることができます。これにより、 GSfD ではアプリケーションサーバーの仲介なしに、エンドユーザー(Web ブラウザ)が GSfD に対して直接ファイルの保存(オブジェクトへの POST を使う?)やダウンロード(Cookie ベース認証を使う)ができるようです。
ただ、オブジェクトへの POST を見ると API キーで署名しているので、これは書き込み権限とは関係ないのかな?このあたりはまだ試していないので、よくわかりません(^^ヾ
API の使用方法
RESTfull API でアクセスするためのエンドポイントは以下のいずれかになります(SSL アクセスも可能です)。
http://commondatastorage.googleapis.com/バケット名/オブジェクト名 http://バケット名.commondatastorage.googleapis.com/オブジェクト名
バケット名とオブジェクト名は省略可能で、両方を省略するとサービス全体に対する操作(実際にはバジェットリストの取得のみ)、バケット名のみを指定するとそのバケットに対する操作、オブジェクト名まで指定するとそのオブジェクトへの操作になります。
RESTful API なので、エンドポイントへのリクエストは HTTP メソッドによって動作が変わります。それぞれのメソッドの動作は以下になります。
- サービスへの GET
- バケット名およびオブジェクト名を省略して GET すると、バケットのリストが XML 形式で返ります。
- バケットへのGET
- オブジェクトリストが XML 形式で返ります。
- バケットへのPUT
- バケットの作成、もしくはバケットのパーミッションの変更(acl クエリーパラメータを指定したとき)です。
- バケットへのDELETE
- そのバケットを削除します。ただし空でないバケットは削除できません。
- オブジェクトへのGET
- オブジェクトのデータのダウンロード、もしくはパーミッションの変更(acl クエリーパラメータを指定したとき)です。
- オブジェクトへのPOST
- HTML フォームを使ったオブジェクトデータのアップロードです。
- オブジェクトへのPUT
- オブジェクトデータのアップロード(上書き)、もしくはパーミッションの変更(acl クエリーパラメータを指定したとき)です。
- オブジェクトへのDELETE
- オブジェクトを削除します。
リクエストパラメータなどの詳細はリファレンスガイドを参照してください。
Cookie ベース認証
ファイルを Web ブラウザからダウンロードする場合は、 Cookie ベース認証を使います。
http://code.google.com/apis/storage/docs/developer...
GSfD にアップロードしたファイルをユーザーにダウンロードさせたい場合、以下のような URL へのリンクを Web ページに設置しておきます。
https://sandbox.google.com/storage/バケット名/オブジェクト名
そしてユーザーがこれをクリックすると、(認証が済んでいなければ) Google アカウントのログインページにリダイレクトされます。そしてユーザーがログインすると再び上記の URL にリダイレクトされ、ファイルのダウンロードが始まるという仕組みです。もちろん、オブジェクトはそのユーザーに対して READ が許可されていなければなりません。
独自ドメインでの利用
先ほどバケット名には英数小文字とハイフンしか使えないと書きましたが、実はピリオドを含めることもできます。ただし、ピリオドを含むバケット名はドメイン名として認識され、そのドメインの所有者しか使うことができません。
ドメインの所有の確認は、 Google Webmaster Tools を使うようです。 Webmaster Tools にサイトを追加し、サイトの確認を済ませれば、そのドメイン名がバケット名として使えるようになります。
ドメイン名をバケット名として使うと、そのドメインの URL で GSfD のファイルにアクセスできるようになります。私もまだ試していないのですが、どうやら DNS の CNAME レコードを使って c.commondatastorage.googleapis.com にリダイレクトすると、元のドメイン名をバケット名として使うようです。例えば私が file.webos-goodies.jp というバケットを作り、そのドメインのリダイレクト設定をすれば、以下の URL で foo.jpg にアクセスできるようになります。
http://file.webos-goodies.jp/foo.jpg
おそらくこの場合は Cookie ベース認証は使えないはずなので、意味があるのは公開ファイルに対してのみだと思います。それでも、なかなか面白い機能ではないでしょうか。
API でアクセスしてみる
それでは、実際に API でアクセスしてみましょう。 GSfD の API は非常にシンプルな RESTful API で、非常に簡単なリクエストで GSfD 上の情報にアクセスできます。最大のポイントはアクセスを認証する Authorization ヘッダの作り方で、これはリクエストヘッダなどの内容を API キーを使って署名することで生成します。具体的には、以下の情報を一行ずつ順番に記述したテキスト(改行コードは 0x0a)を、 API キーの Secret を使った HMAC-SHA1 で署名します。
- HTTPメソッド
- Content-MD5 ヘッダの値。なければ空文字列。
- Content-Type ヘッダの値。なければ空文字列。
- Data ヘッダ(もしくは x-goog-date ヘッダ)の値。 Date ヘッダは必須。
- x-goog- ではじまるヘッダを、以下のように加工し、ヘッダ名順に位置行ずつ並べたもの。
- ヘッダ名をすべて小文字にする。
- 同じヘッダが複数ある時はひとつに統合し、その値をコンマ区切りで並べる。コンマの前後には空白などは入れないこと。
- 連続する空白と改行はひとつの空白に変換する。
- ヘッダと値を分けるコロンの前後の空白は削除する。
- 文字列 "/バケット名/オブジェクト名" 。クエリーパラメータに acl があるときは、それだけ追加する。
つまり、リクエストヘッダが以下のようになっていた場合、
PUT /europe/france/paris.jpg HTTP/1.1 Host: travel-maps.commondatastorage.googleapis.com Date: Mon, 15 Feb 2010 21:30:39 GMT Content-Length: 4539 Content-Type: image/jpg x-goog-acl: public-read x-goog-meta-reviewer: bob x-goog-meta-reviewer: jane
以下の文字列を署名することになります。
PUT image/jpg Mon, 15 Feb 2010 21:30:39 +000 x-goog-acl:public-read x-goog-meta-reviewer:bob,jane /travel-maps/europe/france/paris.jpg
署名は BASE64 でエンコードし、 Authorization ヘッダに以下の形式で含めます。
Authorization: GOOG1 アクセスキー:署名
ということで、 Ruby ヘッダの署名を行う sign_gsfd 関数を書くと、以下のようになります。
require 'openssl' # secret : API キーの Secret # method : HTTP メソッド # path : 書名に使うパス("/バケット名/オブジェクト名[?acl]" の形式) # original_headers : リクエストヘッダ(重複はコンマ区切りの形にあらかじめ変換しておくこと) def sign_gsfd(secret, method, path, original_headers = {}) # すべてのリクエストヘッダ名を小文字にする headers = {} original_headers.each do |key, value| headers[key.to_s.downcase] = value.to_s end # CanonicalHeaders message = [method.upcase, headers['content-md5'] || '', headers['content-type'] || '', headers['x-goog-date'] || headers['date'] || ''] # CanonicalExtensionHeaders exheaders = [] headers.each do |key, value| key = key.strip.gsub(/\s+/u, ' ') value = value.strip.gsub(/\s+/u, ' ') exheaders << [key, value] if key.index('x-goog-') == 0 end exheaders.sort!{|a, b| a[0] <=> b[0] }.each do |key, value| message << "#{key}:#{value}" end # CanonicalResource message << path # 署名を作成 hmac = OpenSSL::HMAC.new(secret, OpenSSL::Digest::SHA1.new) [hmac.update(message.join("\n")).digest].pack("m").gsub(/\s/u, '') end
Authorization ヘッダの作り方さえわかってしまえば、 GSfD API は簡単に使えます(GData API なんぞよりずっと簡単です!)。それでは、いくつかの操作を実際にやってみましょう。
バケットの作成
GSfD にデータを保存するには、まずバケットを作成しなければなりません。それには、作成したいバケット名を使って PUT リクエストを送信します。
require 'openssl' require 'net/http' require 'time' require 'rexml/document' def sign_gsfd(secret, method, path, original_headers = {}) # 上記の署名処理... end ACCESS_KEY = 'アクセスキー' SECRET = '秘密鍵' HOST = 'commondatastorage.googleapis.com' path = '/webos-goodies-test' headers = { 'Content-Length' => '0', 'Content-Type' => 'application/xml', 'Date' => Time.now.httpdate } headers['Authorization'] = "GOOG1 #{ACCESS_KEY}:#{sign_gsfd(SECRET, 'PUT', path, headers)}" Net::HTTP.start(HOST) do |http| http.request_put(path, '', headers) do |response| if response.body && !response.body.empty? REXML::Document.new(response.body).write($stdout, 2) end end end
これを実行すると、 webos-goodies-test という名前のバケットが作成されます(実際に試すときは、バケット名を変えてください)。エラーが発生したときはその内容が XML で表示されるので、そこから原因を究明できます。
バケットリストの表示
本当にバケットが追加されたかどうかを確認してみましょう。バケットに対して GET リクエストを送るだけです。
path = '/webos-goodies-test' headers = { 'Date' => Time.now.httpdate } headers['Authorization'] = "GOOG1 #{ACCESS_KEY}:#{sign_gsfd(SECRET, 'GET', path, headers)}" Net::HTTP.start(HOST) do |http| http.request_get(path, headers) do |response| if response.body && !response.body.empty? REXML::Document.new(response.body).write($stdout, 2) end end end
成功すると、以下のレスポンスが表示されるはずです。
<?xml version='1.0' encoding='UTF-8'?> <ListBucketResult xmlns='http://doc.s3.amazonaws.com/2006-03-01'> <Name> webos-goodies-test </Name> <Prefix/> <Marker/> <IsTruncated> false </IsTruncated> </ListBucketResult>
正常に追加されているようです。
オブジェクトの保存
それでは、オブジェクトを保存してみます。とりあえず実験なので、「Hello Google Storage!」という短いテキストを保存します。
path = '/webos-goodies-test/test.txt' data = 'Hello Google Storage!' headers = { 'Content-Length' => data.size.to_s, 'Content-Type' => 'text/html', 'Date' => Time.now.httpdate } headers['Authorization'] = "GOOG1 #{ACCESS_KEY}:#{sign_gsfd(SECRET, 'PUT', path, headers)}" Net::HTTP.start(HOST) do |http| http.request_put(path, data, headers) do |response| if response.body && !response.body.empty? REXML::Document.new(response.body).write($stdout, 2) end end end
オブジェクトのダウンロード
保存したオブジェクトは、 GET メソッドでダウンロードできます。強い一貫性があるので、保存してからすぐ読み出しても大丈夫w
path = '/webos-goodies-test/test.txt' headers = { 'Date' => Time.now.httpdate } headers['Authorization'] = "GOOG1 #{ACCESS_KEY}:#{sign_gsfd(SECRET, 'GET', path, headers)}" Net::HTTP.start(HOST) do |http| http.request_get(path, headers) do |response| puts response.body end end
実行すると・・・
Hello Google Storage!
ばっちりアクセスできました!ヽ(゜∀゜)ノ
で、けっきょくどうなの?
というわけで、 Ruby スクリプトから GSfD にアクセスすることができました。 API はとても使い易く、 GSfD と連携するアプリケーションはかなり簡単に書けそうです。まだ試せていませんが、 HTML フォームでのアップロードや Cookie ベース認証によるダウンロードがきちんと動作するなら、これまでになく有用なストレージサービスと言えるでしょう。
しかし、問題は価格です。以下に GSfD と Google App Engine (GAE) 、 Amazon S3 (S3)、 Amazon S3 Reduced Redundancy (S3RR) の価格を比べてみました。
GSfD | GAE | S3 | S3RR | |
---|---|---|---|---|
Storage (/GB/mo) | $0.17 | $0.15 | 〜$0.15 | 〜$0.10 |
Transfer In (/GB) | $0.10 | $0.10 | $0.10 | $0.10 |
Transfer Out (/GB) | $0.15 (アジア地域は $0.30) | $0.12 | 〜$0.15 | 〜$0.15 |
容量課金が割高なのも残念ですが、それ以上にアジア地域からのダウンロードに倍のコストがかかるのはいかがなものか。米国にデータセンターがあるのは S3 も同じはずなんですが、なぜこんなことに・・・。残念ながら現在の価格では、巨大なファイルを扱わない限り GSfD を使う理由は見当たりません。 API がとてもよくできているだけに残念なことです。
ただ、前述のとおり他のサーバーを介さずにエンドユーザーがプライベートファイルをアップロード・ダウンロードできるというメリットはあります。これならばアプリケーションサーバーへのリクエストがかからないので、工夫の余地があるかもしれません。
ということで、現在のところ日本からの利用は不利な点もある GSfD ですが、 10Gbyte 超の巨大ファイルの配信が現実的なコストで可能になったのは画期的と言えるでしょう。将来的に国内もしくは近隣のデータセンターでもサービスが始まり、利用コストが下がるのを期待したいですね。とりあえずプレビュー期間は 100Gbyte が無料で使えるので、バックアップにでも使ってみるかな・・・(笑)。
詳しくはこちらの記事をどうぞ!
この記事にコメントする