When it’s ready.

出来るまで出来ない

RESTで日本語 @ GoogleAppEngine

発表直後のGAEエントリーのピークは治まり、最近は、だんだGAEやり続けている人がポツポツと見え始めてきた気がする。

このままじゃ置いてきぼりになってしまうので、最近見つけたGAEネタをエントリーしてみる。

REST って必須だよね

デリシャスとかで、自分のブクマをタグで見たい時には

http://del.icio.us/ユーザー名/タグ名

的に書く。コレって意外と便利だし、タグが日本語だったとしても、safariだとエンコードされないからまんま見えて安心な感じ。ロケーションバーで日本語が見えるかどうかは重要じゃないが、RESTで日本語は使えないより使えた方がいいよね。

そんな、REST APIをGAEで作っているときに日本語が上手に取れない事が分かったので、それをどう解決するのかを書く。

まずはじめに
http://localhost:8080/get/日本語/
とリクエストされたら、”日本語”だけを取り出してみる。
>main.py

# coding:utf-8
#! /usr/bin/env python

import wsgiref.handlers
import urllib

class Main(webapp.RequestHandler):
  def get(self):
     self.response.out.write("特に意味のないTopページです")

class GetURI(webapp.RequestHandler):
  def get(self, uri_code):
    uri =  urllib.unquote(urllib.unquote(uri_code))
    self.response.out.write(uri.decode('utf-8'))

def main():
  app = webapp.WSGIApplication([
                                ('/tag/(.*)', GetURI),
                                ('/', Main),
                                ], debug=True)
  wsgiref.handlers.CGIHandler().run(app)
if __name__ == '__main__':
  main()

main()の中のwebappの設定で、urlの所に(.*)と書けばそのクラスの引数になるポイ。

(.*)で取得出来た文言は、urllibのunquoteメソッドでアンクォートされるはずなんだけど、一度じゃだめで、二度unquoteする必要があった。とっても気持ち悪いけど、とりあえづ、こうしたら動いた。あとは、そのままout.writeすると、そのまま使える。

URIから取り出した文字列でフィルター出来ない、罠

URIから日本語取り出せても、そのまま表示に使っておしまいということはなく、たいていの場合は、その文言で検索やらフィルタすると思うが、なぜかここでもそのままではフィルタリング出来なかった。その解決方は以下の通り

(ハマった、簡単に説明しようと思ったけど、データの投稿部分がないと検証出来ない。とりあえづ、/post/hoge とすると hoge が登録されるとした。)
>main.py

# coding:utf-8
#! /usr/bin/env python

import wsgiref.handlers
from google.appengine.ext import db
from google.appengine.ext import webapp
import urllib

# モデル strというカラムだけ持つ
class Data(db.Model):
  str = db.StringProperty(required = True)
  create_at = db.DateTimeProperty(auto_now_add = True)

class Main(webapp.RequestHandler):
  def get(self):
    self.response.out.write("特に意味のないTopページです")

# 渡されたURIを元にDataStoreにフィルターをかけるクラス
class GetURI(webapp.RequestHandler):
  def get(self, uri_code):
  # unquoteを2回かけて日本語取り出す
    uri =  urllib.unquote(urllib.unquote(uri_code))
    val = {'Data':[i for i in Data.all().filter('str = ', uri.decode('utf-8'))]}
    self.response.out.write(val)


# 渡されたURI文字列をDataStoreに登録するクラス
class PostURI(webapp.RequestHandler):
  def get(self, uri_code):
    uri =  urllib.unquote(urllib.unquote(uri_code))
    b = Data(str = uri.decode('utf-8'))
    b.put()
    self.response.out.write("%sを書き込みました。"%(b.str.encode('utf-8')))

def main():
  app = webapp.WSGIApplication([
                                ('/get/(.*)', GetURI),
                                ('/post/(.*)', PostURI),
                                ('/', Main),
                                ], debug=True)
  wsgiref.handlers.CGIHandler().run(app)
if __name__ == '__main__':
  main()

ケッツマづいたところ

  1. (.*)で引き取った文字列は urllib.unquote を2回かけないと日本語にならない
  2. Model.all() に .filter()かける時の引数は、カラム名と“=(イコール)”の間にスペースが必要 例) .filter('hoge =', foo)
  3. 日本語でフィルターかける時には、.decode('utf-8')をつける

なんか、ぜんっぜん間違ったやり方ポイケド、とりあえづ動いているのでよしとする。
もっと正しくて簡単なやり方あったら教えてください。

おまけ

コピペで動かせるように app.yamlを張っておく
>app.yaml

application: JapaneseRESTSample
version: 1
api_version: 1
runtime: python

handlers:
- url: .*
  script: main.py

上記、main.pyとapp.yamlを同じフォルダーに保存(utf8,lf)して、そのディレクトリー内で

$ dev_appserver.py  . 

とやってもらえれば、動くと思う。
まだ、GoogleAppEngineを入手されてない方は、http://code.google.com/appengine/downloads.html
ここに行けば、Win OSX LinuxSDKが落とせますです。

おまけ2

getメソッドで、self.request._environするといろいろとれる。
始めは、(.*) 知らなくて、_environ から無理矢理splitしてた。
ブラウザとかOSとかも取れるからなにかに使えるかも・・
今の環境だとこうなった

{
'SERVER_SOFTWARE': 'Development/1.0', 
'SCRIPT_NAME': '', 
'REQUEST_METHOD': 'GET', 
'HTTP_KEEP_ALIVE': '300', 
'SERVER_PROTOCOL': 'HTTP/1.0', 
'QUERY_STRING': '', 
'CONTENT_LENGTH': '', 
'HTTP_ACCEPT_CHARSET': 
'Shift_JIS,utf-8;q=0.7,*;q=0.7', 
'HTTP_USER_AGENT': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X; ja-JP-mac; rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14', 
'TZ': 'UTC', 
'SERVER_NAME': 'localhost', 
'REMOTE_ADDR': '127.0.0.1', 
'wsgi.url_scheme': 'http', 
'PATH_TRANSLATED': '********************************************',
'SERVER_PORT': '8080', 
'AUTH_DOMAIN': 'gmail.com', 
'wsgi.input': , 
'HTTP_HOST': 'localhost:8080', 
'wsgi.multithread': False, 
'HTTP_CONNECTION': 'keep-alive', 
'USER_EMAIL': '', 
'HTTP_ACCEPT': 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5', 'wsgi.version': (1, 0), 'GATEWAY_INTERFACE': 'CGI/1.1', 'wsgi.run_once': True, 'wsgi.errors': ', 
mode 'w' at 0x170b0>, 
'wsgi.multiprocess': True, 
'HTTP_ACCEPT_LANGUAGE': 'ja,en-us;q=0.7,en;q=0.3', 
'CONTENT_TYPE': 'application/x-www-form-urlencoded', 
'wsgi.file_wrapper': , 
'HTTP_ACCEPT_ENCODING': 'gzip,deflate', 
'PATH_INFO': '***********************'
}