LoginSignup
12
12

More than 5 years have passed since last update.

Tornado-JSON で手軽に JSON を返す Web API を作ろう

Last updated at Posted at 2016-01-22

前口上

tornado は軽量な Web Framework と非同期通信ライブラリです。WebSocket やロングポーリングのようなコネクションを長く維持する通信方式を採用している場合に真価を発揮しそうです。
今回は、JSON 形式のレスポンスを返すちょっとしたアプリのサーバー実装のために Django のような大きな Web Framework を使いたくなかったので tornado を試してみました。

また、JSONEncorder を使ってレスポンスを返しても良いのですが、Tornado-JSON を使えば、レスポンスの形式を JSend 仕様にすることができたり、JSON Schema によるバリデーションを行うことができます。また、ドキュメンテーションもできるので、JSON over HTTP で実装したい場合は便利そうです。

環境

Web Server: nginx
Language: Python 2.7.10
OS: CentOS release 6.7 (Final)

インスール

まずは、tornadoTornado-JSON を pip でインストールします。

pip install tornado
pip install Tornado-JSON

特に問題なくインストールできました。

demosその1 hellowold

インストールが完了したら、 GitHub から Tornado-JSON を clone し、demo を試してみましょう。

cd Tornado-JSON/demos/helloworld
python helloworld.py

上記を実行すると、tornado アプリケーションが開始されます。下記のように、API の URL と紐づくクラスが見て取れます。

tornado-json-helloworld-compressed.png

では、実際にリクエストしてみましょう。ブラウザからでも良いですが、今回は httpie を使ってターミナルから確認してみます。
ターミナルに表示されている API を上から順に試していきます。

/api/helloworld/?

$ http GET <path/to/host>/api/helloworld/
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 45
Content-Type: application/json; charset=UTF-8
Date: Thu, 21 Jan 2016 20:22:38 GMT
Etag: "2e708f71c76a9c77119c54d91746619abbbb28fc"
Server: nginx/1.0.15
Vary: Accept-Encoding

{
    "data": "Hello world!",
    "status": "success"
}

最も単純な例のようです。

/api/freewilled/?

$ http GET <path/to/host>/api/freewilled/
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 76
Content-Type: application/json; charset=UTF-8
Date: Thu, 21 Jan 2016 20:27:55 GMT
Etag: "81d9135b038a336645a03c17f8c1f58bf890a010"
Server: nginx/1.0.15
Vary: Accept-Encoding

{
    "data": "I don't need no stinkin' schema validation.",
    "status": "success"
}

これは少し分かりにくいのですが、バリデーションをしない例のようです。

/api/postit/?

# httpie では、生のjsonを送信するために数値型は = ではなく := で指定することに注意。= で指定すると数値が文字列型になってしまい、バリデーションでエラーとなる。 
$ http POST <path/to/host>/api/postit/ title='post it' body='hello world' index:=0
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 65
Content-Type: application/json; charset=UTF-8
Date: Thu, 21 Jan 2016 20:30:53 GMT
Server: nginx/1.0.15
Vary: Accept-Encoding

{
    "data": {
        "message": "post it was posted."
    },
    "status": "success"
}

こちらは GET Method ではなく POST Method の例ですね。
title, body, index をパラメータとしている API なので、これらを指定しないと、バリデーションに失敗します。失敗したときは、下記のようになりました。

$ http POST <path/to/host>/api/postit/ title='post it' body='hello world' index=0
HTTP/1.1 400 Bad Request
Connection: keep-alive
Content-Length: 179
Content-Type: application/json; charset=UTF-8
Date: Fri, 22 Jan 2016 16:02:19 GMT
Server: nginx/1.0.15
Vary: Accept-Encoding

{
    "data": "u'0' is not of type 'number'\n\nFailed validating 'type' in schema['properties']['index']:\n    {'type': 'number'}\n\nOn instance['index']:\n    u'0'",
    "status": "fail"
}

/api/greeting/(?P<fname>[a-zA-Z0-9_\-]+)/(?P<lname>[a-zA-Z0-9_\-]+)/?$

$ http GET <path/to/host>/api/greeting/John/Smith
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 55
Content-Type: application/json; charset=UTF-8
Date: Thu, 21 Jan 2016 20:45:36 GMT
Etag: "8e58c6953fdb166e2912eca36881f55885f20073"
Server: nginx/1.0.15
Vary: Accept-Encoding

{
    "data": "Greetings, John Smith!",
    "status": "success"
}

こちらは正規表現を利用した URL の例ですね。
John, Smith がそれぞれ fname, lname という Key でキャプチャされ、Python プログラムから参照することができます。

/api/asynchelloworld/(?P[a-zA-Z0-9_\-]+)/?$

$ http GET <path/to/host>/api/asynchelloworld/Smith
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 78
Content-Type: application/json; charset=UTF-8
Date: Thu, 21 Jan 2016 20:48:19 GMT
Etag: "4f1db48667213b44adbacc2a85599bca86c72f19"
Server: nginx/1.0.15
Vary: Accept-Encoding

{
    "data": "Hello (asynchronous) world! My name is Smith.",
    "status": "success"
}

こんにちは、非同期の世界。というわけで、非同期ライブラリを使用した例のようですね。

以上の API 実装は GitHub の demos/helloworld/helloworld.pydemos/helloworld/helloworld/api.py にあります。これらを参考にすれば所望の処理が実装できそうです。

demosその2 rest_api

こちらは URLconf を示す demo です。

helloworld の実装を見ると気づくのですが、URLconf (Django でいうところの urlpatterns) が見当たりません。
どういうことなのでしょうか。

実は、APIHandler__urls__, __url_names__ という名前のリスト型クラス変数があり、これをオーバーライドすると、URLs を自動生成してくれます。 生成された URLs は tornado_json.routes.get_routes 関数で取得します。 これが Tornado-JSON の URLconf 作法のようです。

詳しくは、demos/rest_api/cars/api/__init__.py に記載されています。

実際に何か作って公開

ということはありません。(力尽きました。。気が向いたら後日やるかも?

試してみた感想

罠は色々あるかもしれませんが、軽量な点が嬉しいです。
一番の魅力だと思われる非同期処理とノンブロッキングIOは詳しく調べられていませんが、特に WebSocket と組み合わせたときにどうなるか興味深いです。

Tornado-JSON に関しては、昨今の WEB 開発の要件が、JSON over HTTP に移っていることを考えると、魅力的です。ただ、開発があまり活発ではないのか、Contributors も 4人と非常に少ないことに多少不安感があります。一方、tornado は比較的活発なので少しは安心感が。

参考

tornado に関しては、2012年頃に(恐らく唯一の)書籍も出版されているようです。
https://github.com/Introduction-to-Tornado/Introduction-to-Tornado

12
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
12