Google App Engine Python: リソースパスを引数のように webapp で処理する。

WEBサービスAPI のリクエストURL などでは、リソースパス部分を読み取ってサーバー側で処理を分岐していると思われるものがあります。

例えば、TwitterAPI で、ユーザー名 bob の friend のタイムラインを取得するには、次のようなリクエストURLを用います。

http://api.twitter.com/1/statuses/friends_timeline/bob.json

実際の Twitter API のサーバーの構造は知りませんが、普通に考えれば、リソースパス部分(http://api.twitter.com 以降の部分)に相当する静的なディレクトリや静的なファイルがあるとは考えられません。少なくとも bob の friend がツイートするたびに静的な bob.json ファイルを更新するとは思えません。

推測ですが、ステータスデータを保存するデータベースがあって、そこからリクエストに応じて、データを取り出して応答しているのではないでしょうか?
そうだとすれば、ステータス管理サーバーは、bob の friend のタイムラインを JSON 形式で返して欲しいというリクエストをこのリソースパスで受け付けられるようになっていることになります。

つまり、パラメータを使って、リクエストパラメータを次のようなものにしても同じようなことができたはずです。

http://api.twitter.com/1/statuses/?type=friends_timeline&user=bob&output=json


それなら、なぜこのようにしなかったのか?
上記の2つの URL を比べれば、答えるまでもありませんね。
上の方が簡明だからですね。
URL に、いちいちパラメータ名を付加しなくても、何を求めているかはわかります。

同じようなことが Google App Engine で簡単に実現できるのでしょうか?

それができるんです。
何を使うかって?
チュートリアルでも、最初の方に紹介されている webapp を使います。

webapp では、リクエストハンドラのマッピング正規表現を使って振り分けをしていましたね。そこでは、括弧「()」を使った正規表現のグルーピング機能も有効です。そして、その順番でリクエストハンドラ・クラスの get() メソッドにマッチング文字列を引数として渡す仕組みになっているんです。

例えば、リクエストハンドラのマッピング部分を次のように記述してみましょう。

application = webapp.WSGIApplication([('/([^/]*)/?([^/]*)/?(.*)',MainPage])

この正規表現は、リソースパスが次のいずれであってもマッチします。

/
/A
/A/
/A//
//B
//B/
/A/B
/A/B/
///C
/A//C
/A/B/C
/A/B/C?q=D

要するに、括弧で囲まれた3つの部分をそれぞれ抽出する正規表現なのです。
このように3つの部分になる場合には、リクエストハンドラの get メソッドにも対応する3つの引数を指定します。

class MainPage(webapp.RequestHandler):
  def get(self,s1,s2,s3):

以降、この引数を用いて処理を記述できます。

また、リクエストパラメータも同時に利用できます。
q というパラメータが想定されているとすると、次のような記述でその値を取得できます。

class MainPage(webapp.RequestHandler):
  def get(self,s1,s2,s3):
    q=self.request.get('q');

最後に簡単な例ですが、リソースパスから値を取り出す例を紹介します。

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

class MainPage(webapp.RequestHandler):
  def get(self,s1,s2,s3):
    q=self.request.get('q');
    path =s1 + '/' + s2+ '/' + s3
    if q:
       path += '?q='+q
    self.response.out.write(path)

application = webapp.WSGIApplication(
                                 [('/([^/]*)/?([^/]*)/?(.*)', MainPage)],
                                  debug=True)
def main():
  run_wsgi_app(application)

if __name__ == "__main__":
  main()      

リソースパスが、なぜファイルパスではなく、「リソースパス」と呼ばれるのかも、何となく分かりますね。