name: orientation class: title, smokescreen, shelf, no-footer # 情報システム開発基礎演習<br><small>第05回 www.py注解</small> <div class=footnote> <small><small> Copyright (C) Ken'ichi Fukamachi <fukachan@fml.org>, 2021-2025. CC BY-NC-SA 4.0 </small></small> </div> --- class: compact # (Web)ルーティングの概要 <div class=footnote> <small><small> (脚注1) 月曜日の午前中にやっていたのは「(パケット)ルーティング」です <br> (脚注2) どちらもルーティングと呼んでいます。 いちだん高いところから抽象的に/俯瞰的に見れば 「条件Xのときは処理Aに切り替える」 という意味で同じような処理をしています。 だから、どちらもルーティングなのですね。 これで納得してもらえますかね? </small></small> </div>  --- class: compact # 準備 <div class=footnote> <small><small> (脚注) Python3の文法やオブジェクト指向プログラミングの分からないところは メディコンさんに聞いてください </small></small> </div> <small> - 行番号入りのソースコードを準備してあります。ポータルからダウンロードしてください - www.py.v1.91.txt というファイルです - 解説は行番号つきですので適宜www.py.v1.91.txtファイルを参照してください - (a)<b>(いつもの)図の最低限の理解</b>と(b)<b>第6回以降で必要となる知識</b>だけを解説します - 手短に10〜15分程度で解説します (ちなみに重要なのは8頁〜です) - <b>Python3の文法やオブジェクト指向プログラミングが分かることは大前提です</b> (説明しません) </small> --- class: compact,img-right # WWWサーバ動作のしくみ(再掲) <div class=footnote> <small><small> </small></small> </div>  <small> - (0) あらじめWWWサーバ(給仕)を80/tcpで起動してあります。 サーバはHTTPを待っています - (1) WWWサーバにリクエストがやって来ます - <b>`GET /... HTTP/1.0`といった文字列がHTTPのペイロード(データ)</b>です。 GETの第1引数は<b>コンテンツの指定=(URLに含まれている)パス</b> - (2) WWWサーバの内部動作、今回の主役 - (3) HTTPリクエストの結果(返事) - (4) WWWブラウザというアプリの動作 </small> --- class: compact # (Web)ルーティングの概要(再掲) <div class=footnote> <small><small> (脚注) このあと、GET /upload.html ... とか POST /api/upload/v1 とか ... を説明します </small></small> </div>  <small><small> - HTTPのペイロード中のGETの第一引数が<b>パス(図のオレンジ色の部分)</b>です </small></small> --- class: compact # <small>(0a) WWWサーバオブジェクトの作成</small> <div class=footnote> <small><small> (脚注1) ここは分からなくても第6回以降の演習に差しつかえません (脚注2) デーモンという言い回しはMITの作成した最初のタイムシェアリングシステム CTSS(Compatible Time Sharing System)まで遡れるはずです (「私、気になります」な人むけの説明;<b>IT古典部:-)</b>) </small></small> </div> <small> ``` 0341 if __name__ == "__main__": ``` - `python3 www.py`のように実行したときだけWWWサーバが起動する条件文を書いてあります (0341行め) - もしもメイン関数を実行する必要がある場合であれば(0341行め, これが定番の書き方です) - 0342行め以降を実行します ``` 0350 with socketserver.TCPServer((HTTP_HOST, HTTP_PORT), httpHandler) as httpd: ``` - 変数名 httpd でWWWサーバオブジェクトを生成します (0350行め) - `with`構文です - `as 変数名`で生成したオブジェクトの変数名を指定できます - 変数名httpdはHTTP Daemonの略です。 Unixに代表されるタイムシェアリングシステムでは、 このように「ずっと動きつづけるサーバプロセス」を<b>デーモン</b>と呼んでいます </small> --- class: compact,img-right # <small>(0b) WWWサーバオブジェクトの作成</small> <div class=footnote> <small><small> (脚注) httpHandlerクラスにWWWサーバの動作詳細を書くことを理解してください </small></small> </div>  <small> 0350行目の最重要部分が次の部分です ``` socketserver.TCPServer(エンドポイント, クラス名) ``` - WWWサーバオブジェクトを作成します - エンドポイントの指定です - インターネット上でサーバを一意に特定できる情報として「IPアドレス(住所)とポート番号(誰)」を指定します - クラス名(ここではhttpHandler)で指定するのが 「<b>WWWサーバの動作詳細を定義するクラス名</b>」です。 <b>httpHandlerクラスを別途つくりこむ</b>必要があります </small> --- class: compact # <small>(0c) WWWサーバの起動</small> <div class=footnote> <small><small> (脚注) ここは分からなくても第6回以降の演習に差しつかえません </small></small> </div> <small> ``` 0365 httpd.serve_forever() ``` - WWWサーバをスタートさせます(0365行め) - この関数名を見れば分かるとおり、ずっと(forever)走りつづけます - Ctrl-Cでサーバを殺すまで止まりません </small> --- class: compact,img-right # <small>(1) www.pyに図の(1)に相当するコードはありません</small> <div class=footnote> <small><small> (脚注) Go言語で書いても似たようなもんです、はい </small></small> </div>  <small> - 最近のプログラミング言語が提供するHTTPライブラリは、このくらいの抽象度/ブラックボックスが普通です - HTTPを受けとめるコードは、ライブラリの奥深くにあり、ユーザが見ることも、操作することもありません - HTTPが受け止めた後の詳細な動作だけをhttpHandlerクラスで定義していきます </small> --- class: compact # <small>(2a) httpHandlerクラス: クラスの定義、コンストラクタ</small> <div class=footnote> <small><small> (脚注1) このあとは図の(2)部分の詳細を解説していきます (脚注2) 用語が分からない人はメディコンさんに教えてもらってください <br> (脚注3) 5月〜6月の課題で、このあたりの改造/編集は無いはずですが、 最終課題では改造が必要になる人がいる"かも"しれません </small></small> </div> <small> - 課題(httpHandlerクラスの一部を編集する)だけなら、 継承関係やコンストラクタがどうこうとか無関係なので、詳細は省略します - 一応、どの行か?だけ紹介しておきます ``` 0058 class httpHandler(http.server.SimpleHTTPRequestHandler): ``` - 0058 〜 0238 行までがhttpHandlerクラス。 <b>http.server.SimpleHTTPRequestHandlerクラスを継承</b>してhttpHandlerクラスを作成しています (ライブラリは 0023-0028 行めで import しています) ``` 0059 def __init__(self, *args, **kwargs): ``` - コンストラクタ </small> --- class: compact,img-right # <small>(2b) httpHandlerクラス: メソッドの復習</small> <div class=footnote> <small><small> (脚注1) 図(2)の詳細がhttpHandlerクラス (脚注2) 年中つかう"curlコマンドで何かをダウンロード"する時もGETメソッドです </small></small> </div>  <small> - 図(1)のHTTPが渡すパラメータは、正確には <br> <b>メソッド コンテンツパス HTTP/バージョン</b> - GETメソッド ... ブラウザでindex.htmlやupload.htmlを見る時に使います。 <b>ダウンロード(だけ)する</b>時はデフォルトのGETメソッド - POSTメソッド ... FORM文で<b>キーバリューやファイルを送信</b>する時 - 自分で書いたFORM文を見かえしてください。 `<form method="POST" ...>`のように<b>method属性を指定</b>していますよね? - このとき図の(1)では 「POST /api/upload/v1 HTTP/1.0」のようにパラメータを渡します </small> --- class: compact # <small>(2c) httpHandlerクラス: メソッドごとの詳細</small> <div class=footnote> <small><small> (脚注1) 理由するライブラリやフレームワークによって流儀は様々です。 あくまでも本解説はPython3デフォルトのライブラリの使い方 <br> (脚注2) オブジェクト指向プログラミングでは、 オブジェクトに紐づいて実行できる関数をメソッドと呼んでいますが、 HTTPのメソッドと紛らわしいので、 本解説ではメソッドではなく関数と書くことにします </small></small> </div> <small> ``` 0131 def do_POST(self): ``` - httpHandlerクラスでは、 HTTPのメソッドごとに「do_メソッド」という関数が定義されています - 「do_大文字メソッド名」で定義します。例: `do_GET`、`do_POST` - `do_POST`関数(0131行め〜0156行め)がPOSTメソッドの処理の詳細です - 実質、POSTメソッドを処理するトップレベル関数です。 `do_POST`の中身は入出力と 「渡されたパスを見ながら呼び出す関数を切り替える」だけの処理が書いてあります(詳細は次頁〜) </small> --- class: compact # <small>(2d) httpHandlerクラス: HTTPの動作(復習) </small> <div class=footnote> <small><small> (脚注) 結局のところパスとは場所です。 Web APIサーバは、 場所1 -> 処理1、場所2 -> 処理2のように動作を切り替えていきます </small></small> </div> <small> ``` FORM文の例(再掲) <form method="POST" enctype="multipart/form-data" action="http://学籍番号.cloud.fml.org/api/upload/v1"> ``` - GETの場合: - ブラウザで`http://学籍番号.cloud.fml.org/upload.html`にアクセスすると - HTTPは`GET /upload.html HTTP/1.0`のように、 欲しいコンテンツのパスを引数にします - POSTの場合: POSTでも同様です - FORM文のACTIONにあるURLから `POST /api/upload/v1 HTTP/1.0`のようにパラメータを渡します - 引数「/api/upload/v1」にコンテンツの意味はありませんが、 このパス「/api/upload/v1」部分を使って、 WWWサーバに<b>「何の処理をしてほしいのか」</b>を伝えることが出来ます </small> --- class: compact # <small>(2e) httpHandlerクラス: do_POST()とHTTPの動作</small> <div class=footnote> <small><small> (脚注1) 0143-0144行めが、演習の最初に使ったlsformです ... 送信したキーバリューを表示するWeb APIサーバ(オウム返し) </small></small> </div> <small> ``` // upload.htmlのFORM文の行 <form method="POST" enctype="multipart/form-data" action="http://学籍番号.cloud.fml.org/api/upload/v1"> // www.pyの該当する行 0145 if self.path == "/api/upload/v1": 0146 message = self.upload(form) ``` - httpHandlerクラスの中では `self.path`という変数を見れば、 GETメソッドやPOSTメソッドが渡してきたパス(例: /api/upload/v1)を知ることが出来ます - WWWサーバは`self.path`を文字列比較して、 期待されている処理を知ることが出来ます - 0145-0146行めが第4回の演習で使ったアップロードの裏側です - この条件文にマッチしてself.upload()が呼ばれます。 引数はformオブジェクト(次頁を参照) - self.upload()は、 送信されてきたファイルを受け取り、 EC2上にファイルとして作成します </small> --- class: compact # <small>(2f) httpHandlerクラス: do_POST()への入力</small> <div class=footnote> <small><small> </small></small> </div> <small> ``` 0145 if self.path == "/api/upload/v1": 0146 message = self.upload(form) ``` - `do_POST`から呼び出す関数には引数として`form`オブジェクトを渡してください - `do_POST`の最初の方で`form`オブジェクトを生成しています(0135-0139行) - 呼びだした側では`form`オブジェクトから値を取り出せます - 例:inputタグで定義したキーバリューを取り出すには `form[key].value` 変数(0168行) - 例:inputタグで定義したファイル(アップロードしたファイル)を取り出す (0178-0179行め) </small> --- class: compact # <small>(2g) httpHandlerクラス: do_POST()からの出力 </small> <div class=footnote> <small><small> </small></small> </div> <small> ``` 0145 if self.path == "/api/upload/v1": 0146 message = self.upload(form) ``` - 0146行めが使い方の典型例です - `do_POST`から呼び出された関数は<b>文字列を返す</b>ように作成してください - upload関数の場合、0180行を参照 - その返り値を`do_POST`のローカル変数messageに代入してください (0146行) - ローカル変数messageをブラウザに返すコードは定義済みです - 変更不要、そのまま使ってください - 該当するコードは、あらかじめ`do_POST`の最後の方に書いてあります (0150-0156行) </small>