質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
uWSGI

uWSGIは、PythonでWebサービスを動かすアプリケーションサーバの一つです。WSGI(Web Server Gateway Interface)アプリケーションコンテナの一種で、WSGIに則ったDjangoやFlaskなどで動かすことができます。

Flask

FlaskはPython用のマイクロフレームワークであり、Werkzeug・Jinja 2・good intentionsをベースにしています。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

2703閲覧

wsgi製pythonフレームワークはどのようにアプリケーションを起動させているのか

sequelanonymous

総合スコア123

uWSGI

uWSGIは、PythonでWebサービスを動かすアプリケーションサーバの一つです。WSGI(Web Server Gateway Interface)アプリケーションコンテナの一種で、WSGIに則ったDjangoやFlaskなどで動かすことができます。

Flask

FlaskはPython用のマイクロフレームワークであり、Werkzeug・Jinja 2・good intentionsをベースにしています。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

1クリップ

投稿2021/06/29 08:32

wsgiについて調べていたところ、「wsgiアプリケーションの実態は、callableなオブジェクト」という言葉をよくみました。一見わかりそうで理解できないのですが、結局どのような処理がフレームワーク内部と起動場所となるPCで行われて実行されているのか、がこの投稿の趣旨になります。

例としてFlaskの下記のコードの場合は、flask runコマンドを実行することで内部ではどのような処理がどのような順番で行われて、flask内部のビルドインサーバー上でhello.pyを起動させているのでしょうか?

もう少し質問を具体的にすると、下記です。
Flaskのコードの当箇所の引数のenvironにFLASK_APPなどの環境変数郡がはいるのでしょうか? 一方でstart_responseは、何がはいりますでしょうか?runコマンドを実行した際に発動するビルドインサーバーの起動周りのコードはどこの箇所になりますでしょうか?

さらに、wsgi製のアプリケーションサーバー(例えば、uwsgi, gunicorn..)などで起動したときは、どのような順番でどのような処理が内部ではしってwsgiアプリケーションが起動されるのかが気になっています。

お手数ですが、ご存知のかたいたらご教示頂けますと助かります。

hello.py

python

1from flask import Flask 2app = Flask(__name__) 3 4@app.route('/') 5def hello_world(): 6 return 'Hello, World!'

python

1$ export FLASK_APP=hello.py 2$ flask run 3 * Running on http://127.0.0.1:5000/

*コードは、flaskチュートリアルから引用してきました

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

とりあえず、wsgirefモジュールを使って、簡単なWebアプリを作ってみてはいかがですか。

マニュアルのサンプルコードを引用すると、

python

1from wsgiref.util import setup_testing_defaults 2from wsgiref.simple_server import make_server 3 4# A relatively simple WSGI application. It's going to print out the 5# environment dictionary after being updated by setup_testing_defaults 6def simple_app(environ, start_response): 7 setup_testing_defaults(environ) 8 9 status = '200 OK' 10 headers = [('Content-type', 'text/plain; charset=utf-8')] 11 12 start_response(status, headers) 13 14 ret = [("%s: %s\n" % (key, value)).encode("utf-8") 15 for key, value in environ.items()] 16 return ret 17 18with make_server('', 8000, simple_app) as httpd: 19 print("Serving on port 8000...") 20 httpd.serve_forever()

の、simple_app関数がWSGIアプリケーション、make_server関数以下の処理がWebサーバの役割になります。

WSGIアプリケーションとは、上記のように「クライアントから送られてきた情報が格納されている辞書オブジェクト」と「レスポンスを開始するために呼び出すcallableオブジェクト」の2つの引数を持ち、レスポンス本文を戻り値とするcallableなオブジェクトでしかありません。
WSGIに対応したWebサーバは、クライアントからの接続があるたびに、上記のcallableオブジェクトを適切な引数を持って呼び出し、「レスポンスを開始するために呼び出すcallableオブジェクト」の呼び出しと上記のcallableオブジェクトの戻り値を元に、クライアントにレスポンスを返します。

Flaskのソースコードで言えば、以下の点です。

python

1 def __call__(self, environ: dict, start_response: t.Callable) -> t.Any: 2 """The WSGI server calls the Flask application object as the 3 WSGI application. This calls :meth:`wsgi_app`, which can be 4 wrapped to apply middleware. 5 """ 6 return self.wsgi_app(environ, start_response)

__call__という名のメソッドは特殊メソッドで、そのクラスのインスタンスがcallableオブジェクトとして扱われた際に呼び出されるメソッドです。

python

1class Hoge: 2 def __call__(self): 3 print('Hello.') 4 5a = Hoge() 6a() # Hoge.__call__が呼ばれ、「Hello.」と出力される。

Flask.__call__が定義されているということは、Flaskクラスのインスタンスはcallableなオブジェクトであり、Flaskクラスのインスタンス自体がWSGIアプリケーションとなりうるわけです。
先程のコードも、simple_appの代わりにFlaskクラスのインスタンスを使用することができることになります。

python

1from wsgiref.util import setup_testing_defaults 2from wsgiref.simple_server import make_server 3 4from flask import Flask 5 6app = Flask(__name__) 7 8@app.route('/') 9def index(): 10 return 'Hello, flask!' 11 12 13with make_server('', 8000, app) as httpd: 14 print("Serving on port 8000...") 15 httpd.serve_forever()

コメントを受けて。

WSGIに対応したWebサーバ

具体的になんのことをいっていますでしょうか?uwsgiやビルトインサーバーのことをいっていますでしょうか?であれば、WEBサーバーではなくアプリケーションサーバーのことだと思いますが、あっていますでしょうか?

おおよそその認識で結構です。
そもそも、WEBサーバもアプリケーションサーバも、どこかの企画で決められた名称ではありませんから、意味合いも多少のズレがあると思います。
(自分も、サーバは本職ではないので、多少言葉が間違っている点もあるかもしれませんが)
自分の場合、クライアントからのリクエストやレスポンスまで記述したので「Webサーバ」という言葉を使ったまでです。

こちらも具体的に言うと、start_responseにはいるのは、'name'であっていますでしょうか?

start_responseは、以前の回答でも書いたとおり「レスポンスを開始するために呼び出すcallableオブジェクト」です。
この説明では分かりづらいかもしれませんが、もうちょっと言い換えると「WSGIアプリケーションで、レスポンスを返すために必要な情報を渡して呼び出さなければならないcallableオブジェクト」です。
先に挙げたサンプルコードでも呼び出していますし、flaskなどを使わないシンプルなWSGIアプリケーションのサンプルを探せば、同様に呼び出しているはずです。(それを確認する意味でも、「wsgirefモジュールを使って、簡単なWebアプリを作ってみては」と提案したのですが)

もし可能でしたら、python+flask+nginx+uwsgiの構成でクライアントからリクエストがきたときにどういう処理の順番でflaskが起動するか教えて頂けますと助かります。

くり返し言いますが、「WSGIアプリケーションは、単なるcallableオブジェクトであり、(uwsgiのような)WSGIアプリケーションを起動する側は、そのcallableオブジェクトを呼び出している」だけです。

自分はuwsgiを使ったことはありませんが、例えばここを参考に、

uwsgi --http=0.0.0.0:8080 --wsgi-file=run.py --callable=app

と起動したとします。
起動時に指定しているのは、pythonのスクリプト名とWSGIアプリケーションのcallableオブジェクトとなる名前(flaskのインスタンスがcallableオブジェクトになるのは先に説明したとおり)ですね。

uwsgiは、これらの情報を元に、WSGIアプリケーションを呼び出すだけです。

python

1 2environ = {} 3# environに、WSGIの仕様に従った情報を設定 4def start_response(status, headers): 5 # 渡されたstatusとヘッダ情報を元に、httpのヘッダを送信 6 pass 7 8# こんな単純なimportではないと思いますが、そこは長くなるし、WSGIとは関係のないことなので省略 9import run 10response = run.app(environ, start_response) 11 12# responseを元に、httpのボディを送信

これだけのことを知っていれば十分だと思いますが、environの中身や、start_responseの中の処理を知りたければ、WSGIの仕様をよく理解すべきかと思います。(この辺りかな?)
あと、httpのプロトコルのことも理解していないのであれば、それも理解すべきです。


python

1app.run(environ, start_response(a, b))

と、

python

1app.run(environ, start_response)

とでは、全く違います。

前者は、runメソッドを呼び出す前にstart_response関数を呼び出し、その戻り値をrunメソッドに渡します。

後者は、start_response関数を呼び出していません。
start_response関数そのものを渡し、runメソッドの中でstart_response関数を呼び出せるようにしています。

また、start_responseがcallableなオブジェクトってことであれば、どの箇所をコードからcallableだといっていますでしょうか?つまり、どこに__call__がありますでしょうか?

defキーワードで定義された関数・メソッドは、callableオブジェクトになります。
Pythonの内部で勝手に__call__が定義される、と思ってください。

>>> def a(): ... pass ... >>> a.__call__ <method-wrapper '__call__' of function object at 0x7f64299eab80> >>>

投稿2021/06/29 13:39

編集2021/06/30 23:09
katsuko

総合スコア3538

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sequelanonymous

2021/06/29 17:21

ありがとうございます。いくつかご確認させてください。 > WSGIに対応したWebサーバ 具体的になんのことをいっていますでしょうか?uwsgiやビルトインサーバーのことをいっていますでしょうか?であれば、WEBサーバーではなくアプリケーションサーバーのことだと思いますが、あっていますでしょうか? > クライアントからの接続があるたびに、上記のcallableオブジェクトを適切な引数を持って呼び出し こちらも具体的に言うと、start_responseにはいるのは、'__name__'であっていますでしょうか? もし可能でしたら、python+flask+nginx+uwsgiの構成でクライアントからリクエストがきたときにどういう処理の順番でflaskが起動するか教えて頂けますと助かります。 callableオブジェクトがなにかについてのあたりの話は既にドキュメント読んでるので理解しているつもりです。
katsuko

2021/06/29 23:34

回答を編集しました。(出勤前なので、急いで書いたので、変なこと書いてるかもしれませんが)
sequelanonymous

2021/06/30 13:49

ありがとうございます!ふわっと掴めてきました。 start_response関数には引数があるはずなのにpythonのぷんぽうてきには、下記のように呼びだせないはずなんですが、どうなっていますでしょうか? ''' environ = {} # environに、WSGIの仕様に従った情報を設定 def start_response(status, headers): # 渡されたstatusとヘッダ情報を元に、httpのヘッダを送信 pass # こんな単純なimportではないと思いますが、そこは長くなるし、WSGIとは関係のないことなので省略 import run response = run.app(environ, start_response) # responseを元に、httpのボディを送信 ''' これを ''' environ = {} # environに、WSGIの仕様に従った情報を設定 def start_response(status, headers): # 渡されたstatusとヘッダ情報を元に、httpのヘッダを送信 pass # こんな単純なimportではないと思いますが、そこは長くなるし、WSGIとは関係のないことなので省略 import run response = run.app(environ, start_response(a, b)) # responseを元に、httpのボディを送信 ''' というような使い方であれば理解ができます。 また、start_responseがcallableなオブジェクトってことであれば、どの箇所をコードからcallableだといっていますでしょうか?つまり、どこに'__call__'がありますでしょうか? ご確認頂けるとたすかります。
sequelanonymous

2021/07/02 05:21 編集

追記ありがとうございます! 下記は、確認したことをメモ的に記載。(無視していただいて大丈夫です。) 確認コード① ```python In [1]: def test(a): ...: return a + 1 ...: In [2]: def test2(b): ...: return b+1 ...: In [3]: test2(test) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-3-9f5fb478054f> in <module> ----> 1 test2(test) <ipython-input-2-b6b704d3c4e7> in test2(b) 1 def test2(b): ----> 2 return b+1 3 TypeError: unsupported operand type(s) for +: 'function' and 'int' In [4]: a = 10 In [5]: test2(test) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-5-9f5fb478054f> in <module> ----> 1 test2(test) <ipython-input-2-b6b704d3c4e7> in test2(b) 1 def test2(b): ----> 2 return b+1 3 TypeError: unsupported operand type(s) for +: 'function' and 'int' In [6]: test2(test(a)) Out[6]: 12 ``` 確認コード② ```python In [33]: def start_response(status, headers): ...: # 渡されたstatusとヘッダ情報を元に、httpのヘッダを送信 ...: return status, headers ...: In [34]: s = start_response In [35]: s("s", "h") Out[35]: ('s', 'h') In [36]: def _run(func): ...: print(func) ...: return "Ok" ...: In [37]: _run(start_response) <function start_response at 0x7f9f2ae3fca0> Out[37]: 'Ok' In [38]: result = _run(start_response) <function start_response at 0x7f9f2ae3fca0> In [39]: def _run(func): ...: ...: return func ...: In [40]: result = _run(start_response) In [41]: result("s", "h") Out[41]: ('s', 'h') In [42]: ```
sequelanonymous

2021/07/02 06:58

ちょっとごちゃごちゃになってきたので、下記にもう少し問いをシンプルにして書き直しました。色々書いて頂きましたので、こちら一旦ベストアンサーとさせて閉じます。ありがとうございました。 https://teratail.com/questions/347293?modal=q-comp
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問