www.py ソースコード注解
www.pyのソースコード解説です。 注意: ソースコードの各行左端に4桁の数字がふってあり、以下では適宜この行番号を使い解説します。 この行番号は、この解説のために追加された部分で、もちろんソースコードには本来ありません
全体のおおまかな構成
www.py は大きく分けて次のように構成されています。 おそらく、 ブラウザから検索キーワードを取り込み、 そのキーワードで処理を分岐させる部分の先のあたりを改造したいでしょうから、 お急ぎのかたは、 このドキュメントの後半にあるdo_POST(0112-0120)あたりの解説だけ読めば十分でしょう
行番号 | |
---|---|
0004〜0011 | ライブラリのimport |
0014〜0024 | 設定 |
0028〜0091 | MySQLの操作(class OurSQL 〜) |
0094〜0155 | HTTPリクエストの処理(class Handler 〜) |
0158〜0183 | main部分(if name == “main”: 〜) |
本当はクラスごとに別ファイルに分けたうえでimportするほうが流儀にかなっているはずですが、 便宜上ひとつのファイルにまとめています。 importしているライブラリについては google してください:-) 設定は「変数=値」という形式で、定義されている変数は次の7つです。 それらしい名前なので意味は明瞭だと思います
HTTP_HOST HTTP_PORT HTDOCS_DIR MYSQL_USER MYSQL_PASSWORD MYSQL_HOST MYSQL_DATABASE
変数が大文字なのは、グローバルな変数であるという意思表示です
main部分
0181-0183(ソースコードの最後の3行)だけが重要で、あとはオマケ機能です。
0162-0173行は、コメントにdebugとあるとおり、デバッグと初期化で使っているコードで、 環境変数SQL_INITやSQL_DUMPが指定されたときに初期化やテーブルの中身を表示するものです。 また、Python3でのみ動くコードなので、 0175-0178行ではpythonのバージョン3未満はダメというチェックをしています
0180 # run python www server (httpd)
0181 with socketserver.TCPServer((HTTP_HOST, HTTP_PORT), Handler) as httpd:
0182 print("(debug) serving at port", HTTP_PORT)
0183 httpd.serve_forever()
この0181-0183行が http server の本体です。 0181 行でサーバを初期化しています。 HTTP_HOSTというアドレスでHTTP_PORTというポート番号を待ち受けするWWWサーバです。 受け取ったHTTPリクエストは、そのままHandlerクラスへ渡されています。 0183行で、サーバがリクエストを無限ループして待つという呪文をかけるとサーバが走り始めます
HTTP リクエストは 0181行目で定義したとおり Handler クラスへ渡されるので、 HTTP リクエスト処理の詳細は Handler クラスに書いていきます
HTTP リクエストを処理する Handler クラス
0099行目にあるように、http.server.SimpleHTTPRequestHandlerを継承したHandlerクラスを定義しています。 コンストラクタでは、デフォルトのHTMLファイルを置くディレクトリ(HTDOS_DIR)も指定しています
0099 class Handler(http.server.SimpleHTTPRequestHandler):
0100 def __init__(self, *args, **kwargs):
0101 super().__init__(*args, directory=HTDOCS_DIR, **kwargs)
do_GET(0103)はHTTPリクエストの GET を処理する関数です。 典型的なオーバーライトの書き方になります。
0104-0108行はWeb業界で言うところのルーティングです。 HTTPリクエストのパス(self.path)を調べ(比較し)、 マッチした文字列があれば、その関数を呼び出します。 どのルーティングにもマッチしなかった場合(つまりデフォルトルート:-)?にあたる処理)は、 親クラスの do_GET へ丸投げです(0110行)。
0103 def do_GET(self):
0104 # basic routing
0105 if self.path == '/shop':
0106 return self.__shop_menu()
0107 if self.path == '/hello':
0108 return self.__hello_world()
0109
0110 return super().do_GET()
do_POST(0112-0120)は、HTTPリクエストの POST を処理する関数です。 ここが商品検索の入り口です。 いわゆるCGIの処理をしています。
0112 def do_POST(self):
0113 self._set_headers()
0114 form = cgi.FieldStorage(
0115 fp=self.rfile,
0116 headers=self.headers,
0117 environ={'REQUEST_METHOD': 'POST'}
0118 )
0119 key=form.getvalue("key")
0120 self.__shop_search(key, self.wfile)
検索ページの HTML は /var/www/html/shop.html で、単純なFORMになっています (なおGETのルーティングで/shopの場合__shop_menu(0132-0145)を呼び出すという例も書いてありますが、 単にルーティングの例として書いただけなので、無視してかまいません)。
(shop.htmlより抜粋)
<form action="/search" method="POST">
研究室内売店 商品検索:
<input type="text" name="key" />
<input type="submit" name="submit"/>
</form>
上のHTMLのとおり、検索キーワードは変数 key に設定され、HTTPプロトコルのPOSTでデータが渡されます。 POSTで渡されたHTTPリクエストはcgiモジュールがparseし(0114-0118)、 0119行目で検索のキー(変数名key)を取り出してその値をローカル変数のkeyに代入したあと、 0120行目で__shop_search(0147-0155)という関数に後の処理をまかせます。 _shop_search()の引数にあるself.wfileはHTTPリクエストに返事をする先(ファイルハンドル)です。
__shop_search()関数では渡された key を mysql へクエリ(0149)し、 その結果(r_msg)を文字列 message に組み立て、 HTTPのデータを返す先(wfile)に書き込んで終了です。 文字列 message を単なる UTF-8 のバイト列に変換し、 出力先(ファイルハンドル)に書き込む0155行目の書き方が HTTP リクエストへの返事の仕方のお作法になります。
0147 def __shop_search(self, key, wfile):
0148 oursql = OurSQL()
0149 r_msg = oursql.query(key)
0150 if r_msg == '':
0151 r_msg = '取扱っておりません'
0152 r_msg = r_msg + "<hr>ご来店おまちしております\n"
0153 message = "<p>(debug) [POST response] search key = {}\n\n<p>".format(key)
0154 message = message + r_msg + "\n\n"
0155 wfile.write(bytes(message, "utf8"))
MySQL を操作する処理を集めた OurSQL クラス
OurSQL というクラス名は何も考えずにMySQLの反対語くらい?というノリでつけた名前です。 深い意味はないので気にしないでください
コンストラクタ(0033-0044)とディストラクタ(0046-0047)は、 そういうものなので説明は省略します
次の3つの関数はmainのオマケ部分(0162-0173)で使うコードです。 名前のとおり、テーブルの作成、INSERT、SELECTの見本になっています。 PythonからRDBMSを操作する必要がでてきた人は参考にしてください (みなさん、たぶん mysql コマンドなり別の方法で操作してます?よくわからない…)
0050 def create_table(self):
0064 def insert(self):
0076 def select(self):
肝心な部分はクエリを投げる0084-0091行のところでしょう
0084 def query(self, key):
0085 r=''
0086 cursor = self.conn.cursor()
0087 cursor.execute('SELECT name,price FROM shop_menu WHERE name=\'{}\''.format(key))
0088 for (name, price) in cursor:
0089 r = r + "商品 <br>{} の価格は {} 円です\n".format(name, price)
0090 cursor.close()
0091 return r
mysqlサーバへのコネクション self.conn はコンストラクタが用意しています。 コネクションからカーソル cursor を作り(0086)、 cursor.execute()関数にクエリを書けばcursorに答えが入ります。 0088-0089行のforループでcursorから値を取り出し、 ローカル変数 r に答えの文字列を構築しています。 0091行で変数rの値を返しますが、 これが HTTP リクエストの検索処理の答え(0149行めの r_msg へ代入される値)として使われます
全ソースコード
各行左端4桁の数字は行番号で、この解説のために追加された部分です。 もちろんソースコードにはありません