Showing posts with label API. Show all posts
Showing posts with label API. Show all posts

2011-05-29

GAE/J で com.google.gdata.util.ParseException: Invalid root element とか

Blogger には、個別投稿フィードなるものがあるので、
GoogleService gs = new GoogleService("blogger", "kuribo-japanesebloggers-1");
URL feedUrl = new URL("http://www.blogger.com/feeds/6813881014503035656/posts/summary/552385261504346376");
Feed feed = gs.getFeed(feedUrl, Feed.class);
Entry entry = feed.getEntries().get(0);
GAE/J で GoogleService を使って Blogger の個別投稿フィードを読み込ませようとしたら、

com.google.gdata.util.ParseException: Invalid root element, expected (namespace uri:local name) of (http://www.w3.org/2005/Atom:feed), found (http://www.w3.org/2005/Atom:entry

とかって言われてへこむ。なんだなんだと調べてみると、Blogger の個別投稿フィードは、通常の投稿フィード
<feed>
  <id/>
  <updated/>
  <title/>
  <subtitle/>
  <link/>
  <author/>
  <generator/>
  <entry/>
</feed>
のような構成ではなく、上記 entry 部からいきなり始まる形で、その投稿以外のブログ情報が全く掲載されないようになっているみたい。

ブログ ID +ポスト ID で投稿のタイトルや URL を取得しようと思っていたのが、ちょっぴり挫折。URLFetch して、XML のパーサを使うか、正規表現でゴリゴリするか、迷い中…。

2010-06-13

GAE/J の AuthSub で This website has not registered with Google to establish a secure connection for authorization requests. の警告をなくす方法

GAE アプリから、 AuthSub 認証を使って Blogger などのデータにアクセスする際に出る警告がこちら。
This website has not registered with Google to establish a secure connection for authorization requests. We recommend that you continue the process only if you trust the following destination:
(このウェブサイトは認証リクエストに対して保護された接続を確立するよう Google に登録されていません。次のサイトが信頼できる場合のみ処理を続行してください:)
これを出なくさせたいなと思って調べてみたら、こちらがヒット。 そのままズバリなので、これ以上書く必要もないのだけれど…まあ一応。

ウェブサイトの登録

まず にアクセスして、「Add a New Domain」で自分の GAE アプリのドメインを入力する。「Manage registration」に「Manage YOURAPP.appspot.com」のように、入力したドメインが表示されるので、それをクリック。

Google Accounts Authentication API - Terms and Conditions を確認して、「I agree to the Terms of Service」ボタンを押下。ウェブサイトの管理ページが開くので、「Target URL path prefix」に認証後のトークン送信先 URL を入力し「Save」を押すと、Google へのウェブサイト登録が完了。

(場合によっては、ウェブサイトの「所有権の確認」が要る場合も。メタタグをサイトに追加するか、指示されたファイルをアップロードするかして、ウェブサイトの所有権を確認。)

再び AuthSub 認証を使ってみると警告文が変わっているはず。
This website is registered with Google to make authorization requests, but has not been configured to send requests securely. We recommend that you continue the process only if you trust the following destination:
X.509 自己署名証明書

「Google に登録されてはいるけれど、セキュアじゃないよ。」に警告が変わったので、次はセキュアトークンを送ってもらうための準備。 を参考に、X.509 の Self-signing Certificate を取得。Java の keytool が便利だった。
# Generate the RSA keys and certificate
keytool -genkey -v -alias Example -keystore example
  -keyalg RSA -sigalg SHA1withRSA
  -dname "CN=www.example.com, OU=Engineering, O=My_Company, L=Mountain  View, ST=CA, C=US"
  -storepass changeme -keypass changeme
keytool -export -rfc -keystore example -storepass changeme -alias Example -file mycert.pem
作成された PEM ファイルを、前節の Google アカウント情報「Upload new X.509 cert:」にアップロード。keystore は GAE の war に入れて、アップロード。しなきゃ認証時に java.io.FileNotFoundException が出ちゃう。

GAE 側の設定

Google Account Authentication API からセキュアトークンを受け取ったり、実際にデータをやりとりしたりする GAE のページを SSL に対応させる。

appengine-web.xml の appengine-web-app 要素内に
<ssl-enabled>true</ssl-enabled>
を追加。

お好みで web.xml に
<security-constraint>
 <web-resource-collection>
  <url-pattern>/authsub</url-pattern>
 </web-resource-collection>
 <user-data-constraint>
  <transport-guarantee>CONFIDENTIAL</transport-guarantee>
 </user-data-constraint>
</security-constraint>
をつけ加え(/authsub アクセス時は常に https になる)。

プログラム

Google Accounts Authentication API へのアクセスを要求する場合、
String next = "https://YOURAPP.appspot.com/authsub";
String scope = "http://www.blogger.com/feeds/"; //blogger にアクセスする場合
boolean secure = true;
boolean session = true;
String authSubLogin = AuthSubUtil.getRequestUrl(next, scope, secure, session);
とすると、authSubLogin にログイン用の URL が入るので、それでページにリンクを作成してアクセスしてもらう。

セキュアトークンを受け取って、実際にデータにアクセスするには、
String query = req.getQueryString();
String token = AuthSubUtil.getTokenFromReply(query);
PrivateKey privateKey = AuthSubUtil.getPrivateKeyFromKeystore("example", "changeme", "Example", "changeme");
String sessionToken = AuthSubUtil.exchangeForSessionToken(token, privateKey);

GoogleService myService = new GoogleService("blogger", "APPLICATIONNAME");//blogger にアクセスする場合
myService.setAuthSubToken(sessionToken, privateKey);
という風にするといいみたい(例外処理は省略)。後はもう好きなように
URL feedUrl = new URL("http://www.blogger.com/feeds/default/blogs");
Feed resultFeed = myService.getFeed(feedUrl, Feed.class);
とかすると、ユーザーが管理している Blogger ブログ一覧のフィードが取得できたりするわけ。

あ、セキュアトークン受け取りの URL を https に変えると、Google アカウント情報に指定する「Target URL path prefix」も方も https に変えとかないとね。

これで警告がすべてなくなるはずなんだけれど…、少し前にやったのを思い出しながら書いたので、思い違いや書き忘れがあるかも。後はより詳しい人のツッコミに期待。

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>
みたいに返って来て、データは全く取れないので、チェックしてみるといいと思う。

Zenback - Everyone's Related Posts