Python Bottle で API のデモを作る。foreverで管理する。Nginx を前に置く。Python 3.4 を使っている。
基本方針
- forever で管理するってことは uWSGI 使わない。これは bottle+uWSGI+python3+nginxでアプリを動かす の uWSGI 周りの設定を省けるというメリットがあるが、Deployment — Bottle 0.13-dev documentation に書いてあるようにデフォルトの wsgiref サーバはボトルネックになりうる。でも、それも同ページに書いてあるようにサーバ部分を切り替えればいいだけ。
- APIのところは普通にデータを json で返すけれど、それ以外の部分は基本的に static_file でホストする。構造がややこしくなりがちなのでテンプレートは極力使わない。参考: PythonのBottleフレームワークで静的ファイルのリンク生成 - Symfoware
- 将来的にはデモと説明は Open API Initiative による RESTful APIの記述標準化 - Drafts のような形で標準化され簡単に書けるようになるはずだから、その部分とは切り離しておきたい。
nginx の設定
ポートはもちろん、スクリプトのrun(host='localhost', port=3030)
の部分と対応させる
location /app/hogehoge/ { proxy_pass http://127.0.0.1:3030/; }
メインのスクリプト
適宜抽象化してるがだいたいこんな感じ。demo のところに html と js でデモを作成している。
from bottle import response, route, run, static_file import json import logging logging.basicConfig(filename='debug.log',level=logging.DEBUG) @route('/') def readme(): response.content_type = 'text/markdown' return static_file("README.md", root='./') @route('/api/<data>') def api(data): response.content_type = 'application/json' # 本当はここにいろいろ処理が入る return json.dumps([data]) @route('/demo/<filepath:path>') def server_static(filepath): return static_file(filepath, root='./demo') if __name__ == '__main__': # コマンドから"python3 test.py"で起動した場合 run(host='localhost', port=3030)
forever の使い方
forever start -c python test.py
みたいな形で使う。そもそも python test.py
でエラーを吐く場合は立ち上がらない(STOPPEDになる)ので、注意。その場合、アクセスは Nginx から 502 Bad Gateway を返されるかな。
forever list
で状態を把握し、適宜forever stop [id]
で落としたり、forever restart [id]
でファイルを読み込み直したりするとよい。
参考:
Python 部分をデバッグする
-o
オプションで stdout が出力できるとあるが、うまくいかなかった。logging モジュール使ってファイルに出力するのがいいのじゃないか。
参考:
- node.js - Where does forever store console.log output? - Stack Overflow
- Logging HOWTO — Python 3.5.1 ドキュメント
APIへの入力フォーム
Ajax の基本的なテクニックを使う。
$(document).ready(function(){ $('#form').submit(function(event) { event.preventDefault(); $.ajax({ url: '../api/'+$("#input").val().split(" ").join("+"), dataType: 'json', success : function(data) { //取得した data に対する処理 $("#result").text(JSON.stringify(data)) }, //二重送信防止 beforeSend: function(xhr, settings) { $("#submit").attr('disabled', true); }, complete: function(xhr, textStatus) { $("#submit").attr('disabled', false); }, }) }); });
参考:
- jQueryでフォームをAjax送信する際の基本パターンのチュートリアル。二重送信の防御とか。 | Ginpen.com どうせ上書きするし、ややこしいので form の action や method 属性は書かない。
APIから出力する
Bottle で json を出力するのは
import json @route('/api/<data>') def api(data): response.content_type = 'application/json' return json.dumps([data])
みたいにすればいい。
参考:
- BottleのRequest/Responseオブジェクトをマスター - Qiita 情報が古い。Python 3 では simplejson は json モジュールでいいし、わざわざ HTTPResponse モジュール使わなくても Bottle だけで完結させられる
- Bottle のレスポンスを JSON でシリアライズする | CUBE SUGAR STORAGE
APIからの出力を受け取って表示する
id が result の div に流し込むなら
$("#result").text(JSON.stringify(data))
みたいな感じ。上記 APIへの入力フォーム の success 部に書くだけ。
その他
- Bottle でリダイレクトはエラー扱いなので気軽に使えない。参考: PythonのWebフレームワークBottle使ってみる — どこか遠くでのんびり怠惰に暮らしたい
- Bottle の GitHub ソース。十分にコードが安定しているうえにメンテされているフレームワークとしては理想的な状態。
- Bottle の routing でスラッシュを付け忘れてアクセスするとちゃんと動かないので注意(当たり前)
- 上はGETしかしてないけれど、PUT や DELETE もつかった RESTful な API を作りたければ Creating a RESTful Python API with Bottle - Fabrizio (Fritz) Stelluto を参考に。
- テンプレート使うなら PythonのWebフレームワーク Bottleをロリポップサーバ(ロリポプラン)で動かしてみる - Qiita とかも参考になる。やっぱ 250円のプランではほっとんど何もできないけど、bottleなら。なるほど。
- グローバル変数は
if __name__ == '__main__':
の下じゃなくてスクリプトのトップに置く。グローバル変数使うときの考慮事項はこちら→python - Accessing a global list - Code Review Stack Exchange - Bottleは1ファイルなのでファイルだけ入れてもいいんだけど、一応pipでインストールしている。pipはvenv環境で使っている。venvは
apt-get install python3.4-dev
みたいに特定のバージョンのdev入れておけば、そのバージョンのpythonからpython3.4 -m venv [venvdir]
すればいい。
nginxの設定はシンプルに
location ~ ^/app/hogehoge/(.*)$ { proxy_pass http://127.0.0.1:3030/$1; }
ではなく、
location /app/hogehoge/ { proxy_pass http://127.0.0.1:3030/; }
のようにシンプルにしないとなんかのモジュールが動かなかった。