2010-05-09

GAE/J で Amazon Product Advertising API

今日は Google App Engine for Java で Amazon Product Advertising API を使うためのヒントみたいなもの。「Amazon Product Advertising API」は、2009 年 5 月まで「Amazon アソシエイト Web サービス」と呼ばれていたので、そちらの名前で覚えている人も多いかと。このサービスを使うとアプリケーションから簡単に Amazon の商品を検索することが可能に。この API 詳細は で理解してもらうとして、問題はその Signature ―― サービスの名称変更とともに API のリクエスト URL につけることが義務づけられた電子署名がちょっと大変かも。 リクエスト自体は、これまでのものにタイムスタンプを追加して、パラメータの値を RFC3986 のパーセントエンコード(URL エンコードとわずかに違う)、バイト順にパラメータを並べ替え。そのリクエスト全体の文字列をユーザーの秘密キーで HMAC(SHA256 アルゴリズム)を作成し、BASE64 に変換、パーセントエンコード後 Signature パラメータとしてリクエスト末尾につければ、ようやく API へのリクエストの URL の完成という流れ。ああ、ややこしい。

以前からこのサービスを使って、クリボウの Blogger 入門サイドバーとかで実際にキーワードを元に広告を表示しているのだけれど、これは何年か前に Perl で書いたのを を参考に、2009 年に書き直したもの。じゃあ GAE/J では、認証をどうするんだろう?と思ったので調べてみた。前置きが長かったけれど、結論は簡単。Amazon が公開している Java のサンプルコードを利用するだけ。 …だけ、とか書きながらこのままだと動かないのでちょっと修正。


サンプルコードの修正

import org.apache.commons.codec.binary.Base64;
まず、この BASE64 変換を行う org.apache.commons.codec.binary.Base64 のライブラリがないと思うので、以下からゲット。 Binaries 形式のものをダウンロード、解凍してできた commons-codec-*.*.jar をプロジェクトの WEB-INF/lib に入れてビルドパスに加える。
private String endpoint = "ecs.amazonaws.com"; // must be lowercase
private String awsAccessKeyId = "YOUR AWS ACCESS KEY";
private String awsSecretKey = "YOUR AWS SECRET KEY";
次に 33、34、35 の各行。34、35 行は、自身の Amazon Web Service アクセスキーと、 Amazon Web Service 秘密キーを入力。当たり前か。33 行は、そのままだとアメリカの amazon.com の結果を表示してしまうので、
private String endpoint = "ecs.amazonaws.jp"; // must be lowercase
と書き換えておく。

3つ目は、40 行からのコンストラクタで、UnsupportedEncodingException、NoSuchAlgorithmException、InvalidKeyException が出るぞと警告されてしまうので、throws するなり catch するなり。

最後は、なんでかわからないんだけれど、Signature に無駄な %0D%0A という文字列(パーセントエンコード前は「CRLF」改行)が入ってしまうのを阻止する。いや原因がわからず、阻止の仕方もわからないので、
signature = new String(encoder.encode(rawHmac));
らへんのコードを
signature = (new String(encoder.encode(rawHmac))).replaceFirst("[\\r\\n]*$", "");
と、パーセントエンコード寸前に最後の改行文字を削除させる。なんて対症療法!と思うけれど、まあこれで動くので。だれか詳しい人は教えて下さい。


使い方

HashMap<String, String> map = new HashMap<String, String>();
map.put("Service", "AWSECommerceService");
map.put("Version", "2009-01-06");
map.put("Operation", "ItemSearch");
map.put("SearchIndex", "Books");
map.put("ResponseGroup", "Small,Images");
map.put("Keywords", "Google App Engine");
map.put("AssociateTag", "kuribosblogge-22");
map.put("ItemPage", "1");

SignedRequestsHelper srh = new SignedRequestsHelper();
String url = srh.sign(map);
とかいう風に使うと、url にちゃんと AWSAccessKeyId や Timestamp が補われ、パーセントエンコード、ソートもされて、Signature の付加された URL が入るはず。やってみると…。
http://ecs.amazonaws.jp/onca/xml?AWSAccessKeyId=0E87C7QWYAM53DHJ2682&AssociateTag=kuribosblogge-22&ItemPage=1&Keywords=Google%20App%20Engine&Operation=ItemSearch&ResponseGroup=Small%2CImages&SearchIndex=Books&Service=AWSECommerceService&Timestamp=2010-05-09T08%3A30%3A56Z&Version=2009-01-06&Signature=VHsoRNX4slmxgG%2BS7xLKqcSf3Ogf9r7Qa1I8TILySMk%3D
ほらほら。これに URLFetch でアクセスすると、「Google App Engine」にヒットする書籍のデータ(画像も)が XML 形式で返ってくるので、解析してゴニョゴニョと上手く表示してやれば OK。ちなみに Signature の計算が間違っていると、
<?xml version="1.0"?>
<ItemSearchErrorResponse xmlns="http://ecs.amazonaws.com/doc/2009-01-06/"><Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.</Message></Error><RequestID>17711f2a-00c6-415f-9995-6f3f96d0ae57</RequestID></ItemSearchErrorResponse>
みたいに返って来て、データは全く取れないので、チェックしてみるといいと思う。

No comments:

Post a Comment