Bloggerのエントリーを Google App Engine から利用( AuthSubを使用 )
Google App Engine から Blogger にアクセスしてエントリー等の情報を取得する方法。 App Engine の開発言語は Python を使います。
昨年末 Google App Engine Hack-a-thon に参加してこの問題を解こうと思ったのだが、 その場では完全には解けなくて、あっという一年近く経ってしまいました。 最近思い立ってやってみたのですが、なんと簡単に解決できました。
この問題を解くために必要となるリンク
- http://code.google.com/intl/ja/apis/blogger/ blogger API の説明ホーム
- http://code.google.com/p/gdata-python-client/ gdata-2.0.5.tar.gz をここからダウンロード
- http://code.google.com/intl/ja/apis/blogger/docs/1.0/developers_guide_python.html Pythonでbloggerにアクセスする方法
やりたいことと AuthSub の仕組み
ここでやろうとしていることは、 GAEから...
- Blogger のエントリー/エントリー一覧を取得したり
- GAE側で作成したエントリーデータを Blogger にポストしたり
することです。
Blogger APIはもともとそういう目的で存在しているわけですが、 Google App Engine から Blogger API を使って Googleアカウントで Blogger で作成している ブログのデータにアクセスするには、認証が必要になります。
認証方法には ClientLogin という方法とAuthSubという方法があります。 ClientLoginは実装するのは簡単なのですが、認証情報(ユーザ名・パスワード)をGAEのデータストア上に保存(またはコードに直接書く) しておく必要があるため、自分だけが利用するGAEアプリを書く場合は問題ないのですが、 広く一般公開して不特定多数の人が利用するGAEアプリには適しません。 (自分のグーグルアカウントの認証情報を別の第三者サービスに渡したくないですよね、普通)
ということで、AuthSubという仕組みを使います。 この認証方法では、GAEアプリ側で、利用者の認証情報を管理しないで済みます。
AuthSub の簡単な仕組みの説明
詳しくはググってください。ここでは私が理解している範囲で簡単に説明します。
- グーグルから AuthSub token を取得する
- 取得した AuthSub token を使って Session token を取得する
いったん GAE側で Session token を得れば、このトークンを使って Blogger(などのサービスに)アクセスできる。
最初のプロセスでは、自分のGAEアプリページからいったんグーグルの認証ページにジャンプします。 そこで利用者は自分のGoogleアカウントの認証情報を入れます。この段階で認証が成功すれば、 グーグルは AuthSub token を自分のGAEアプリに教えてくれます。
次のプロセスでは、このAuthSub token を使って、Session token と呼ばれるセッションが続く間ずっと使えるトークンに 変換(アップグレード)します。 といっても、この作業は gdata-python-client が面倒を見てくれます。 具体的には単に UpgradeToSessionToken() メソッドを呼ぶだけで済み、開発者は特にそれ以上意識する必要はありません。
それでは実装します。
事前準備、 gdata-2.0.5.tar.gz のダウンロードとセットアップ
GAEの基本は割愛します。 詳しくはGAEのスタートガイドをご覧ください。
gdata-python-client にアクセスして、gdata-2.0.5.tar.gz をダウンロード、展開します。 展開後、gdata-2.0.5/src 以下にある atom/, gdata/ ディレクトリを 全部を自分の開発している main.py と同じディレクトリにコピーしておきます。
コード main.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext.webapp import template
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.api import memcache
import cgi
import logging
from gdata import service
import gdata
import atom
import gdata.alt.appengine
MEM_KEY = 'blogger_service_instance'
def GetAuthSubUrl():
next = 'http://localhost:8080/Main'
#next = 'http://yourappname.appspot.com/Main'
scope = 'http://www.blogger.com/feeds'
secure = False
session = True
blogger_service = service.GDataService()
return blogger_service.GenerateAuthSubURL(next, scope, secure, session);
def GetBlogId(feed):
#
# Bloggerで複数のブログを作成している場合は注意します
#
blog_id = feed.entry[0].GetSelfLink().href.split("/")[-1]
return blog_id
#
# グーグルによる認証後、このページに戻ります
# URLの後ろには token= パラメータにより AuthSub token が渡されます
#
class MainPage(webapp.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/html'
self.response.headers['pragma'] = 'no-cache'
self.response.headers['Cache-Control'] = 'no-cache, must-revalidate'
#
# session token にアップグレード済みに blogger_serviceインスタンスが既にある場合は認証作業をスキップ
#
blogger_service = memcache.get(MEM_KEY)
if blogger_service is None :
#
# まだ session toke を得ていない場合はAuthSub token を取得して session toke にアップグレードします
#
authsub_token = self.request.get('token')
logging.info("auth_sub_token=%s" % authsub_token)
blogger_service = service.GDataService()
gdata.alt.appengine.run_on_appengine(blogger_service)
obj = gdata.auth.AuthSubToken()
obj.set_token_string( authsub_token )
blogger_service.UpgradeToSessionToken( obj )
#
# アップグレード済みの blooger_service インスタンスを保存
#
memcache.add(MEM_KEY,blogger_service,3600)
#
# Bloggerにアクセスしてブログのタイトルとエントリーを取得
#
query = service.Query()
query.feed = 'http://www.blogger.com/feeds/default/blogs'
feed = blogger_service.Get(query.ToUri())
self.response.out.write('<html>')
self.response.out.write(feed.title.text)
self.response.out.write( '<ul>' )
blog_id = GetBlogId( feed )
post_feed = blogger_service.GetFeed('http://www.blogger.com/feeds/' + blog_id + '/posts/default')
for entry in post_feed.entry :
self.response.out.write( '<li>' )
self.response.out.write( entry.title.text )
self.response.out.write( '</ul>' )
self.response.out.write('</html>')
#
# http://localhost:8080/ で実行される
# AuthSub token を得るため用のグーグルページへ移動させるためのリンクを表示するページ
#
class StartPage(webapp.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/html'
#user = users.get_current_user()
#if user==None:
# self.redirect(users.create_login_url(self.request.uri))
# return
authSubUrl = GetAuthSubUrl();
self.response.out.write('<html>')
self.response.out.write('<a href="'+str(authSubUrl)+'">Login to your Google account</a>' )
self.response.out.write('</html>')
application = webapp.WSGIApplication( [ ('/', StartPage), ('/Main', MainPage), ], debug=True)
def main():
run_wsgi_app(application)
if __name__ == "__main__":
main()
注意点(はまりどころ)
gdata.alt.appengine.run_on_appengine() が必要...たぶん
GAEではなく普通に実行する場合は、gdata.alt.appengine.runonappengine()を呼ぶ必要はないはずです。 GAEの場合は呼ぶ必要がある。ただし、これ一年前に調べたときそうだったから、という理由で。 この行がなくても現在は問題ないかも(未確認)。
blogger_service = service.GDataService()
gdata.alt.appengine.run_on_appengine(blogger_service)
NonAuthSubToken エラーが出た
gdata/service.py を見ればわかることですが、 UpgradeToSessionToken()に渡す token は文字列ではNGです。 tokenを文字列として渡してしまうと、NonAuthSubToken 例外が出て止まります。
以下のように、authsub_token(文字列) を gdata.auth.AuthSubToken() インスタンスに変換してから UpgradeToSessionToken() をコールします。
obj = gdata.auth.AuthSubToken()
obj.set_token_string( authsub_token )
blogger_service.UpgradeToSessionToken( obj )
app.yaml
application: yourappname
version: 1
runtime: python
api_version: 1
handlers:
- url: /.*
script: main.py
