Python(2)WSGI:辞書environでウェブページからの情報を受け取る

ラベル:

前の関連記事:Python(1)WSGI:PythonからWSGIアプリでウェブブラウザに結果を出力する


ウェブアプリなんて作る予定はないのですが、ウェブブラウザへの出力方法を前回学習したのでついでに今度はウェブブラウザに表示したページから情報を得る方法を学習します。これらのことはフレームワークを利用すればもっと簡単に安全にできるようですけど。

辞書environに受け取る情報が入っている


CGIの基礎知識がない上にGoogle検索してようやく例をみつけてもPython2.xで書かれているのかPython3.3.3ではそのままなかなか動かず苦労しました。

WSGI でなんかつくってみる #python_adv — Python Web Framework Advent Calendar 2012 1.0.0 documentation

このページが比較的わかりやすかったです。
def calc(value):  # 引数を2倍にして返す関数。
    return value*2
def app(environ, start_response):  # WSGIアプリ。
    import cgi
    fs = cgi.FieldStorage(environ=environ, fp=environ['wsgi.input'])
    value = fs.getfirst('value', '2')  # 最初に出会ったvalueの値を取得。2番目の引数はデフォルト値。
    val = int(value)  # valueを整数型に変換。
    html = '{0}を2倍にした計算結果={1}<br><br><a href="http://localhost:8000/?value={1}">この計算結果を2倍する。</a>'.format(value, calc(val))  # 出力するhtmlを作成。
    start_response('200 OK', [('Content-Type', 'text/html; charset=utf-8')])  # start_responseを設定。
    return [html.encode()]  # htmlをバイト列に変換して返す。
def run():
    port = 8000  # サーバが受け付けるポート番号を設定。
    from wsgiref.simple_server import make_server
    httpd = make_server("", port, app)  # appへの接続を受け付けるWSGIサーバを生成。第一引数はホストlocalhostがデフォルト。
    url = "http://localhost:{}".format(port)  # 出力先のurlを取得。
    import webbrowser
    webbrowser.open_new_tab(url)  # デフォルトのブラウザでurlを開く。
    httpd.serve_forever()  # WSGIサーバは手動で止めないといけません。
if __name__ == '__main__':
    run()
これを実行するとデフォルトブラウザにhtmlが出力されます。


「この結果を2倍する。」のリンクはhttp://localhost:8000/?value=4のURLになっておりこれをクリックするとvalueの値がPythonプロセスに返されてそれを元にウェブページが更新されます。


クリックするたびに1行目のcalc()の結果が反映されます。

それに伴ってURLも変化していきます。
    httpd = make_server("", port, app)  # appへの接続を受け付けるWSGIサーバを生成。第一引数はホストlocalhostがデフォルト。
    httpd.serve_forever()  # WSGIサーバを手動で止めないといけません。
14行目でWSGIアプリ(ここではapp())への接続が確立され18行目でそれを永続させています。

これによってそのポートへアクセスするたびに3行目のapp()が呼び出されることになります。
start_response callable は、HTTP レスポンスを開始するために使用される。
The start_response() Callable
WSGIのアプリの第二引数はウェブブラウザに渡すデータをのためにあるようです。
    start_response('200 OK', [('Content-Type', 'text/html; charset=utf-8')])  # start_responseを設定。
9行目でウェブブラウザに渡すデータを設定しています。

ということでウェブブラウザから渡される情報は第一引数のenvironに入っているようです。

environは辞書になっています(environ Variables)。
    print("\n".join([str(i) for i in environ.items()]))
4行目にこれを入れてenvironの全項目をみてみると76項目もありました。

予想していたよりもたくさんあったのでびっくりしました。

計算結果が4のときと8のときを比べていると8のときは'HTTP_REFERER'の項目が増えています。

ほかは'QUERY_STRING'の項目がNoneから 'value=4'に変更になっているだけであとは全部変更がないようです。
QUERY_STRING
The portion of the request URL that follows the "?", if any. May be empty or absent.
もしあるならば "?" に続くリクエスト URL の一部。空であるか、また は存在しないかもしれない。
PEP 3333: Python Web Server Gateway Interface v1.0.1 — knzm.readthedocs.org 2012-12-31 documentation
計算結果が8のときのURLはhttp://localhost:8000/?value=4なのでQUERY_STRINGには'value=4'が入るわけです。
    fs = cgi.FieldStorage(environ=environ, fp=environ['wsgi.input'])
5行目のFieldStorageとはなんでしょう?

Python 標準ライブラリ — Python 3.3.3 ドキュメントを検索してみると2つのメソッドがあることがわかります。

21.2. cgi — CGI (ゲートウェイインタフェース規格) のサポート — Python 3.3.3 ドキュメント

getlist()メソッドは引数に指定したnameの値の全てのリストを返し、getfirst()メソッドはnameが重複している場合は先頭の値のみ返しされにデフォルト値の設定もできます。

ということでQUERY_STRINGの内容はcgiモジュールのFieldStorageのメソッドで得ることができます。

それで5行目のcgi.FieldStorageの引数の意味を知りたかったのですがGoogle検索では答えがみつからずcgi.pyで見つけました。
 fp              : file pointer; default: sys.stdin.buffer
            (not used when the request method is GET)
environ         : environment dictionary; default: os.environ
cgi.pyより引用
そもそもREQUEST_METHODでしているGETとPOSTの違いをしらないといけません。

formタグ内のmethod=postとmethod=getの違いがよく分かりません。どっちにしてもジ... - Yahoo!知恵袋

今回はURLの後ろにデータをくっつけているのでGETになりfpは指定しなくてもよいことになります。

その代わり日本語を渡したいときはBlogger:JavaScriptのpromptやalertで句読点を表示する方法でやったようなエンコードが必要でしょうね。(21.8.4. URL のクオートあたりを使うと思う。)

POSTのときはfile-like オブジェクトのWSGI定義変数'wsgi.input'をファイルポインタに指定します。

environはapp()の引数で受け取ったものを使わないといけませんのでenviron=environは必要です。

指定しない場合はos.environが使われるのですがその中身をみてみると49項目しかなかく、しかも「wsgi.」ではじまるWSGI定義変数が一つもありませんでした。
def calc(value):  # 引数を2倍にして返す関数。
    return value*2
def app(environ, start_response):  # WSGIアプリ。
    # print("\n".join([str(i) for i in environ.items()]))  # 辞書environの内容を出力。
    # import os
    # print("\n".join([str(i) for i in os.environ.items()]))  # os.environの内容を出力。
    import cgi
    fs = cgi.FieldStorage(environ=environ, fp=environ['wsgi.input'])  # POSTのときはfpの指定が必要。GETのときはなくても可。
    value = fs.getfirst('value', '2')  # 最初に出会ったvalueの値を取得。2番目の引数はデフォルト値。
    val = int(value)  # valueを整数型に変換。
    html = '''
    {0}を2倍にした計算結果={1}<br>
    <br>
    <a href="http://localhost:8000/?value={1}">この計算結果を2倍する</a>
    '''.format(value, calc(val))  # 出力するhtmlを作成。
    start_response('200 OK', [('Content-Type', 'text/html; charset=utf-8')])  # start_responseを設定。
    return [html.encode()]  # htmlをバイト列に変換して返す。
def run():
    port = 8000  # サーバが受け付けるポート番号を設定。
    from wsgiref.simple_server import make_server
    httpd = make_server("", port, app)  # appへの接続を受け付けるWSGIサーバを生成。第一引数はホストlocalhostがデフォルト。
    url = "http://localhost:{}".format(port)  # 出力先のurlを取得。
    import webbrowser
    webbrowser.open_new_tab(url)  # デフォルトのブラウザでurlを開く。
    httpd.serve_forever()  # WSGIサーバは手動で止めないといけません。
if __name__ == '__main__':
    run()
environの内容を確認したコードをコメントアウトしています。

参考にしたサイト


WSGI でなんかつくってみる #python_adv — Python Web Framework Advent Calendar 2012 1.0.0 documentation
ここを参考に例を作成させていただきました。

PEP 3333: Python Web Server Gateway Interface v1.0.1 — knzm.readthedocs.org 2012-12-31 documentation
WSGI仕様書の日本語訳。

Python 標準ライブラリ — Python 3.3.3 ドキュメント
Python3.3.3の公式マニュアル。

formタグ内のmethod=postとmethod=getの違いがよく分かりません。どっちにしてもジ... - Yahoo!知恵袋
URLの後ろにデータをエンコードして付加する方法がGETになります。

次の関連記事:Python(3)WSGI:ウェブページからshutdown()メソッドでWSGIサーバを停止させる

PR

0 件のコメント:

コメントを投稿