GAEでREST的なGETを行う−URLルーティング方法2種

たとえば、検索条件に従って検索を行うhogeというページがあったとすると、検索条件をサーバ側に送信する方法には次のようなものがあるだろう。

方法1

GETメソッドで、URLにパラメータを付与する。

http://サーバアドレス/hoge?param1=xxxx&param2=yyyy&param3=zzzz
方法2

検索条件をformタグとinputタグまたはhiddenタグでくくっておき、POSTメソッドでページごと送り、サーバ側でリクエストボディから引数を取得する。

方法3

ページの遷移先をリソースパスとして指定する

http://サーバアドレス/hoge/xxxx/yyyy/zzzz

このうち、方法2は行儀がわるいやり方なので今回は割愛。
GAEにおける、方法1と方法3のやり方を比較してみる。

基本形:GET+URLパラメータ

まず、方法1のやり方を基本形とする。このようなコードになるだろう。
URL

http://サーバアドレス/hoge?param1=xxxx&param2=yyyy&param3=zzzz

app.yaml

- url: /hoge
  script: main.app

main.py

app = webapp2.WSGIAppliation([
    ("/hoge", "handler.HogeHandler")
    ], debug=True)

handler.py

class HogeHandler(webapp2.RequestHandler):
    def get(self):
        param1 = cgi.escape(self.request.get("param1"))
        param2 = cgi.escape(self.request.get("param2"))
        param3 = cgi.escape(self.request.get("param3"))
        ...

これをふまえ、方法3のルーティングに対応させるにはどうすればよいか。
仮に、何も変更せずに、.../hoge/xxxx/yyyy/zzzzというURLを指定したとすると、.../hoge/xxxx/yyyy/zzzz というURLはパターンマッチしないため、HTTP 404エラーとなる。
さてどうするか。

解答1:正規表現でパターンマッチさせる

hogeの次に指定されるパスが3つと決まっているならば、まずmain.pyをこのように改変する。
main.py

app = webapp2.WSGIAppliation([
    ("/hoge", "handler.HogeHandler"),
    ("/hoge/(.*)/(.*)/(.*)", "handler.HogeHandler"),
    ], debug=True)

ハンドラでは、スラッシュで区切られたデータを次のように受け取ることができる。
handler.py

class HogeHandler(webapp2.RequestHandler):
    def get(self, param1, param2, param3):
        logging.debug(param1)  # xxxx が入ってる
        logging.debug(param2)  # yyyy が入ってる
        logging.debug(param3)  # zzzz が入ってる

ハンドラでの受け取り方はもう1つある。
handler.py

class HogeHandler(webapp2.RequestHandler):
    def get(self, *args, **kwargs):
        logging.debug(arg)  # (xxxx, yyyy, zzzz)
        param1 = args(0)
        param2 = args(1)
        param3 = args(2)

つまり、*argsに配列として/hoge配下の各パスが格納されてくる。

前者は、パスの階層の数が固定となってしまうデメリットがある。
後者は、main.pyの正規表現を、何階層でもOKなように変更してやれば、任意の階層の数に対応することができるメリットがある一方、何番目に何が入っているかをわかっていなければならない(args(n)のnがマジックナンバーになる)というデメリットがある。

解答2:webapp2.Routeを使う

main.py

app = webapp2.WSGIAppliation([
    webapp2.Route("/hoge", handler="handler.HogeHandler"),
    webapp2.Route("/hoge/<param1>/<param2>/<param3>", handler="handler.HogeHandler"),
    ], debug=True)

handler.py

class HogeHandler(webapp2.RequestHandler):
    def get(self, *args, **kwargs):
        logging.debug(arg)  # ()
        logging.debug(kwarg)  # {'param1': 'xxxx', 'param2': 'yyyy', 'param3': 'zzzz'}
        param1 = kwargs['param1']
        param2 = kwargs['param2']
        param3 = kwargs['param3']

kwargsに辞書として格納される。
解答1よりも解答2のやり方のほうが好きだな。

補足:同じURIに対して、GETパラメータも受け取りたい、リソースパスも受け取りたい、のとき

パターン1:

/hoge?param0=1
(GETメソッドで)

パターン2:

/hoge/xxxx/yyyy
(GETメソッドで)

を両立させたかったので、次のようにやってみたが、
main.py

app = webapp2.WSGIAppliation([
    webapp2.Route("/hoge", handler="handler.HogeHandler"), #パターン1をルーティングさせたい
    webapp2.Route("/hoge/<param1>/<param2>/<param3>", handler="handler.HogeHandler"), #パターン2をルーティングさせたい
    ], debug=True)

これだと、パターン1がルーティングしてくれない。正規表現をゴニョゴニョすれば解決するのかもしれないが、次のように変更することでルーティングに成功した。
main.py

app = webapp2.WSGIAppliation([
    ("/hoge", "handler.HogeHandler"), #パターン1のルーティング
    webapp2.Route("/hoge/<param1>/<param2>/<param3>",handler="handler.HogeHandler"), #パターン2のルーティング
    ], debug=True)