Server Build Exercises on AWS (Amazon Web Services) 2022 edition

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桁の数字は行番号で、この解説のために追加された部分です。 もちろんソースコードにはありません

ソースコード

Last updated on 25 Nov 2021
Published on 25 Nov 2021
Copyright (C) 2021-2022 Ken'ichi Fukamachi, All rights reserved. CC BY-NC-SA 4.0
We appreciate AWS Academy Japan for the offer of the learning environment.

Powered by Hugo. Theme by TechDoc. Designed by Thingsym.