Make Local Happiness

自分の幸せは自分で作る!!!

Python+uWSGI+nginx+flaskを試してみる

PythonAPIを開発できる環境を作りたかったので、

色々試してみました。

いつもはPHPを使っていたので、webサーバはお手軽にApacheを使っていたのですが、

業務ではデータ分析をしているため、

今後はPythonを使う予定なので、さっそくPythonのwebサーバを作っていきたいと思います。

Pythonをwebから使う際には以下のようなサーバがあるのですが、

uWSGIが良いとのブログを発見したのでやってみます。

Python を Web 上で使うには

便利で超強力なWSGIサーバー uWSGI を使ってみよう

こちらの記事を参照したのですが、

難しい内容なので、そこまで理解してませんので、

詳しくは檜山正幸のキマイラ飼育記のブログをみてください。

とりあえず、webサーバはNginxでpythonを動かしているのがuWSGIくらいしかわかってません。

uWSGIを使ってみる

環境はAWS上の環境でOSはubuntuを使ってます。

python のバージョンは2.7.6を使ってます。

$ python --version
Python 2.7.6

uwsgiのインストール

$pip install uwsgi >    ############## end of uWSGI configuration #############    total build time: 129 seconds    *** uWSGI is ready, launch it with /home/ubuntu/.pyenv/versions/2.7.6/bin/uwsgi *
Successfully installed uwsgi
Cleaning up...

特に問題はおきませんでした。

まずは動かしてみる

uwsgi --http :9090 --wsgi-file wsgicaty.py --callable main_app

起動は正常にしたけど、なんかserver Internal errorでる

まあ、いいか

hello.pyを動かしてみる

$mkdir www
$vim www/hello.py
# hello.py
def application(env, start_response): 
   start_response('200 OK', [('Content-Type','text/html')])
    return "Hello World"

$uwsgi --http :9090 --wsgi-file hello.py 

アクセスしてみるとHello Worldが表示されたのでオkです。

今度はNginxとuWSGIを組み合わせて使ってみる

nginxをインストール

$sudo apt-get install nginx

uWSGIをNginxと組み合わせて動かす

こんな感じ

uWSGI(3031) ⇒ Nginx(8888)  ⇒ インターネット

$sudo vim /etc/nginx/nginx.conf 
#少し省略してます。
http{
    server {
      listen       8888;
      server_name  pythonapi.com;
      location / {
        include uwsgi_params; 
        uwsgi_pass 127.0.0.1:3031;
      }
    }
}

uwsgiを起動して、nginxを起動します。

 $uwsgi --http :3031 --wsgi-file hello.py  & 

 $sudo service nginx start

以下のURLでアクセスしてhello worldが表示されればオkです。

http://localhost:8888

Flaskを使ってみる

http://uwsgi-docs.readthedocs.org/en/latest/WSGIquickstart.html

ここのサンプルをそのまま試してみました。

$pip install flask

$vim myflaskapp.py

#!/usr/bin/env python

from flask import Flask 

app = Flask(__name__)

@app.route('/')

def index():

    return "<span style='color:red'>I am app 1</span>"

起動してみる

$uwsgi --http :3032 --wsgi-file myflaskapp.py --callable app --processes 4 --threads 2 

http://localhost:3032にアクセスして、

I am app 1 と赤字で表示されればオk

ps -aux でプロセスを確認すると4つプロセスができていることが確認できます。

設定ファイル(uwsgi.ini)にしてみる

毎回先ほどのオプションをつけるのは大変なので、

設定ファイル化して立ち上げてみます。

uidとgidはnginxを起動するユーザとグループを指定してください。

venvはuWSGIをインストールした場所です。

python-pathはアプリ(.py)をおいている場所です。

$vim /var/www/http/uwsgi.ini

[uwsgi]

socket = /tmp/mysock.sock

master = true

uid = www-data

gid = www-data

http = :3031

venv = /home/ubuntu/.pyenv/versions/2.7.6/

python-path = /home/ubuntu/www/

wsgi = myflaskapp:app

processes = 3

threads = 2

起動

$uwsgi /var/www/http/uwsgi.ini 

http://localhost:3031にアクセスして、

I am app 1 と赤字で表示されればオkです。

uwsgiがサーバー再起動時に自動で走るように、サービス起動スクリプトを作る

先ほどのflaskを使った際にはnginxはいっさい使ってませんでした。

起動スクリプトを書いて楽にしちゃいましょう。

$vim /etc/init.d/uwsgi

#!/bin/bash

# bye-reader start script

# chkconfig: 345 99 1

# description: uwsgi start script

# processname: uwsgi

# pidfile: /tmp/uwsgi.pid

#

 

# Source function library.

#. /etc/init.d/functions

. /lib/lsb/init-functions

 

PROGNAME="uwsgi"

PID="/tmp/$PROGNAME.pid"

LOCK="/tmp/$PROGNAME.lock"

RETVAL=0

 

UWSGI_HOME="/home/ubuntu/.pyenv/versions/2.7.6"

UWSGI_BIN="$UWSGI_HOME/bin/uwsgi"

UWSGI_APP_DIR="/var/www/http"

UWSGI_APP_BIN="$UWSGI_APP_DIR/deploy"

UWSGI_LOG="/var/log/uwsgi/$PROGNAME.log"

UWSGI_APP_INIT="$UWSGI_APP_DIR/uwsgi.ini"

 

PROG=$UWSGI_BIN

SSD="/sbin/start-stop-daemon"

 

[ -f $PROG ] || exit 0

 

start() {

        echo -n "Starting $PROGNAME: $PROG"

        $SSD --start --quiet --background --retry 5 --oknodo --exec $PROG $UWSGI_APP_INIT -- --pidfile $PID

        log_end_msg $?

        [ $RETVAL = 0 ] && touch $LOCK

        return $RETVAL

}

 

stop() {

        echo -n "Stopping $PROGNAME: "

        $SSD --stop --quiet --pidfile $PID \

                        --retry 5 --oknodo --exec $PROG

        log_end_msg $?

        RETVAL=$?

        [ $RETVAL = 0 ] && rm -f $LOCK $PID

}

 

case "$1" in

    start)

        start

        ;;

    stop)

        stop

        ;;

    restart)

        stop

        sleep 1

        start

        ;;

    status)

        status_of_proc -p $PID "$PROG" uwsgi

        ;;

    *)

        echo "Usage: $PROGNAME {start|stop|restart|status}"

        RETVAL=2

        ;;

esac

exit $RETVAL

nginx側の設定をする

前回までの設定と違う点はuwsgiとnginxの紐付けである、uwsgi_passをポートからソケットファイル(※名称は正しくないかもしれないです)に変わっっています。

ただ、今回どうしてもuwsgiをnginxのユーザで起動できなかったため、

nginx.confのuserをubuntuに変更しました。

$vim /etc/nginx/nginx.conf

かなり省略しています。

user ubuntu;

server {

    listen       8888;

    server_name  pythonApi; 

 

    location / {

        include uwsgi_params;

        uwsgi_pass unix:/tmp/mysock.sock;

    }

}

uwsgiを起動してみる&プロセスが立ち上がっていることを確認

$service uwsgi start

$ ps aux | grep uwsgi

ubuntu    9882  0.0  0.9 114116 16624 ?        S    Mar03   0:04 /home/ubuntu/.pyenv/versions/2.7.6/bin/uwsgi /var/www/http/uwsgi.ini --pidfile /tmp/uwsgi.pid

ubuntu    9885  0.0  0.7 187848 12476 ?        Sl   Mar03   0:00 /home/ubuntu/.pyenv/versions/2.7.6/bin/uwsgi

ubuntu    9887  0.0  0.7 187848 12476 ?        Sl   Mar03   0:00 /home/ubuntu/.pyenv/versions/2.7.6/bin/uwsgi

ubuntu    9888  0.0  0.7 187848 13140 ?        Sl   Mar03   0:00 /home/ubuntu/.pyenv/versions/2.7.6/bin/uwsgi

ubuntu    9891  0.0  0.6 114116 11828 ?        S    Mar03   0:00 /home/ubuntu/.pyenv/versions/2.7.6/bin/uwsgi

Nginxを起動してみる&プロセスが立ち上がっていることを確認

$sudo service nginx start

$ ps aux | grep nginx

root      8475  0.0  0.0  67320  1388 ?        Ss   Mar03   0:00 nginx: master process /usr/sbin/nginx

ubuntu    8477  0.0  0.1  67616  2192 ?        S    Mar03   0:00 nginx: worker process

ubuntu    8478  0.0  0.0  67616  1692 ?        S    Mar03   0:04 nginx: worker process

ubuntu    8480  0.0  0.0  67616  1692 ?        S    Mar03   0:04 nginx: worker process

ubuntu    8481  0.0  0.0  67616  1692 ?        S    Mar03   0:04 nginx: worker process

http://localhost:8888にアクセスして、

I am app 1 と赤字で表示されればオkです。

REST APIを試す

そろそろ、サーバ側はわかってきたので、

RESTを使ったサンプルを試してみます。

http://flask-restful.readthedocs.org/en/latest/quickstart.html#full-example

flask.restfulをインストールして試してみる

$pip install flask.restful

$vim api.py 

from flask import Flask

from flask.ext.restful import reqparse, abort, Api, Resource

 

app = Flask(__name__)

api = Api(app)

 

TODOS = {

    'todo1': {'task': 'build an API'},

    'todo2': {'task': '?????'},

    'todo3': {'task': 'profit!'},

}

 

 

def abort_if_todo_doesnt_exist(todo_id):

    if todo_id not in TODOS:

        abort(404, message="Todo {} doesn't exist".format(todo_id))

 

parser = reqparse.RequestParser()

parser.add_argument('task', type=str)

 

 

# Todo

#   show a single todo item and lets you delete them

class Todo(Resource):

    def get(self, todo_id):

        abort_if_todo_doesnt_exist(todo_id)

        return TODOS[todo_id]

 

    def delete(self, todo_id):

        abort_if_todo_doesnt_exist(todo_id)

        del TODOS[todo_id]

        return '', 204

 

    def put(self, todo_id):

        args = parser.parse_args()

        task = {'task': args['task']}

        TODOS[todo_id] = task

        return task, 201

 

 

# TodoList

#   shows a list of all todos, and lets you POST to add new tasks

class TodoList(Resource):

    def get(self):

        return TODOS

 

    def post(self):

        args = parser.parse_args()

        todo_id = 'todo%d' % (len(TODOS) + 1)

        TODOS[todo_id] = {'task': args['task']}

        return TODOS[todo_id], 201

 

##

## Actually setup the Api resource routing here

##

api.add_resource(TodoList, '/todos')

api.add_resource(Todo, '/todos/<string:todo_id>')

 

 

if __name__ == '__main__':

    app.run(debug=True)

起動してみたいので、まずはuwsgi.iniの設定で

wsgiだけ変えます。

$vim uwsgi.ini 

[uwsgi]

socket = /tmp/mysock.sock

master = true

uid = www-data

gid = www-data

http = :3031

venv = /home/ubuntu/.pyenv/versions/2.7.6/

python-path = /var/www/http/

wsgi = api:app

processes = 3

threads = 2

起動してみる

$service uwsgi start

http://localhost:8888/todos

にアクセスして以下のjsonが返ってきたら成功です。

{"todo1": {"task": "build an API"},"todo3": {"task": "profit!"},"todo2": {"task": "?????"}} 

URLで絞り込みもできるみたいです。便利ですね。

http://localhost:8888/todos/todo3

{"task": "profit!"}

d3.jsとflaskを合わせたサンプルを試してみます

https://github.com/cranmer/flask-d3-hello-world

こちらのサンプルになります。

$git clone https://github.com/cranmer/flask-d3-hello-world.git 

$uwsgi --http :8000 --wsgi-file __init__.py --callable app &

http://localhost:8000にアクセスして試してみる

以下の画面が見られると成功です。

http://cdn-ak.f.st-hatena.com/images/fotolife/i/iwate_takayu/20140304/20140304162651.png

今業務で作っている分析ツールAPIと画面を完全に切り離して作っているため、

基本やり取りは全てjsonで画面の処理はjsで全て書いています。

最近はd3側の処理が大きくなりつつあるので、

処理速度が遅くなっているのですが、

もしかして、DOM要素をAPI側からそのまま送った方が処理としては早いのかな??

と思いました。例えばnode.jsなど使って。

参考にしたサイト