2009年07月04日

Product Advertising APIの対応

Amazon アソシエイト Web サービスがProduct Advertising APIになって、2009年8月15日から新しいリクエストじゃないと受け付けてくれないってことでちょっとやった。

Developer Guide - リクエストの署名認証について(日本語参考訳)
を見てやったわけだが、


改良点は

旧版のパラメータにVersionとTimestamp(2009-01-01T12:00:00Zのような形式でGMT。+09:00が通用するかはわからない。)を追加する。
パラメータの値にはA-Z, a-z, 09-, ハイフン(-)、アンダーバー(_)、ピリオド(.)およびチルダ(~)以外の文字を含めてはいけなく、URLエンコードしなくてはいけない。
パラメータはキーの文字列順にソートする。
「GET」「webservices.amazon.com」「/onca/xml」「パラメータ」を改行(改行コードは\n)で連結した文字列をSecret Access KeyをキーとしHMAC-SHA256ハッシュアルゴリズムで計算する。
HMAC-SHA256ハッシュをBase64エンコードしたものを「Signature=結果」でパラメータに追加する。

って感じなよう。

コード書いてみたが、とりあえずほぼ完成したと思う。
#!/usr/bin/perl

use Digest::SHA;

$SecretAccessKey='1234567890';

$Server='webservices.amazon.com';

$Service='AWSECommerceService';

$AWSAccessKeyId='AWSAccessKeyId=00000000000000000000';

$Operation='Operation=ItemLookup';

$ItemId='ItemId=0679722769';

$ResponseGroup='ResponseGroup=ItemAttributes,Offers,Images,Reviews';

$Version='2009-03-31';
$Version='2009-01-06';


&AmazonGet($AWSAccessKeyId,$Operation,$ItemId,$ResponseGroup);

###
sub AmazonGet{
my(@list,$gmt,@t,$req,$str,$sig,$url,$xml);
foreach(@_){
@t=split(/=/,$_,2);
$t[1]=~s/([^A-Za-z0-9\-\_\.\~])/'%'.uc(unpack('H2',$1))/eg;
push(@list,$t[0].'='.$t[1]);
}
@t=gmtime;
$t[5]+=1900;
$t[4]++;
for(0..4){
$t[$_]='0'.$t[$_] if(length($t[$_])==1);
}
$gmt="$t[5]-$t[4]-$t[3]T$t[2]:$t[1]:$t[0]Z";
$gmt="2009-01-01T12:00:00Z";
$gmt=~s/([^A-Za-z0-9\-\_\.\~])/'%'.uc(unpack('H2',$1))/eg;
push(@list,"Timestamp=$gmt");
push(@list,"Service=$Service");
push(@list,"Version=$Version");
@list=sort(@list);
$req=join('&',@list);
$str="GET\n$Server\n/onca/xml\n$req";
$sig=Digest::SHA::hmac_sha256_base64($str,$SecretAccessKey);
while(length($sig)%4){
$sig.='=';
}
$sig=~s/([\+\=])/'%'.uc(unpack('H2',$1))/eg;
$url='http://'.$Server.'/onca/xml?'.$req.'&Signature='.$sig;
print "$url\n";
}
###


Developer Guideと同じ結果が帰ってくるようなコードを書いただけだが、
サブルーチンAmazonGetにパラメータを渡すとxmlかえす感じにするつもり。

AmazonGetは、

まず、パラメータのリストを受け取り、「=」で区切って右側をURLエンコードしてる。
エンコードしちゃいけない文字を除外し、
Developer Guideではエンコードされた文字列は大文字になっているんでucで大文字に変換してる。

次、gmtimeでタイムスタンプ作る。
(サンプルと同じ結果だしたいんで$gmtには固定で値入れてる。)
「:」はURLエンコードする。

Timestamp、Service、Versionを追加。

パラメータのソート。

HMAC-SHA256ハッシュアルゴリズムの計算。
「Digest::SHA」ってモジュールの「hmac_sha256_base64」ってメソッド使うとBase64エンコードしたものがとりだせる。
ここでちょいとハマったのが、
最初やったとき、Developer Guideでは
8の計算結果が「Nace+U3Az4OhN7tISqgs1vdLBHBEijWcBeCqL5xN9xg=」
なんだが、
手元では「EDWJ1+VXQhAtPDKQ0f+wpaFQcBVDJyTIpDP7BZgxMiA」ってでた。
しかも、
10のリクエストURLでは「Signatu re=pwqYQRc3RepIrf7m%2BVMRy%2FjFXx%2FZBSPsaSFFexIUoSI%3D」ってなってる。
ちょっとググってたらRubyのサンプルコードが見つかったんで、それと見比べてみたら、
Developer Guideでは「Version=2009-03-31」なんだが、正しい結果がでているサンプルコードでは「Version=2009-01-06」となっていることに気づいた。
「Version=2009-01-06」だとDeveloper Guideと同じ結果になった。
こんなこともあろうかとProduct Advertising APIについてのメールが来てからすぐに改造しないでいままでまってた。
すぐに問題点見つかったが、サンプルがなかったらこれはハマったと思う。
うまくいったと思ったが、よく見ると
Developer Guideでは「Nace+U3Az4OhN7tISqgs1vdLBHBEijWcBeCqL5xN9xg=」
なのに、手元では「Nace+U3Az4OhN7tISqgs1vdLBHBEijWcBeCqL5xN9xg」
だった。末尾に「=」がねえ!
Base64末尾の「=」と思うんで、hmac_sha256_base64ではBase64の末尾の「=」がついてこないんだと思う。
Base64は4の倍数文字で、足りない場合は「=」で埋まるはずなんで、4の倍数文字じゃない時は「=」追加するようにした。


こんなかんじかな。

あと、webservices.amazon.comはアマゾン日本はwebservices.amazon.co.jpだと思う。
posted by ST at 07:25| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

この記事へのトラックバックURL
http://blog.seesaa.jp/tb/122760813

この記事へのトラックバック