class: compact # <small>第11回おしながき</small> <div class=footnote> <small><small> </small></small> </div> <small> 1. おしらせ - ポータルのパスワード ... ENyr - 新www.pyの`(debug)`行について - 偽クラウド BWS (Bibi Web Services) :-) - AWS ACFへ招待しました 1. EL (確認テスト) 1. 今回のハイライト 1. 演習 </small> --- class: title, smokescreen, shelf, no-footer # ハイライト&演習パート --- class: compact # <small>第11回のハイライト</small> <div class=footnote> <small><small> </small></small> </div> <small> - クラウドといえば「必要な時に」「必要な量」=スケールアウトができること - ステートレスなWeb APIサーバ = いくらでもコピーを起動できる = 障害時にはデータがなくなる - 別途、データを保存するサービスが必要 - キャッシュ ... AWS ElastiCache (高いから演習では BWS cache) - RDBMS ... AWS RDS (おすすめは Aurora) </small> --- class: title, shelf, no-footer # 課題: www.pyのcart機能の実装 <div class=footnote> <small><small> www.py v1.84のcart関数(190行目以降)の改造です。 とりあえずノーヒントで頑張ってみてほしいですね。 </small></small> </div> --- class: compact,img-right # <small>課題(第11回〜第12回)</small> <div class=footnote> <small><small> (脚注) www.pyの動作の見かけは今までと変わりません。ただし裏側は異なる状態です。 来週(第12回)、 1号機のwww.pyで保存した数量が2号機のwww.pyでも表示されることを確認します </small></small> </div>  <small> - 右図の2.の部分を、キャッシュシステムも用いて、きちんと作ります。 実現することは 1. ショッピングカートに数量をいれて送信すると、その数量が返ること 1. そして、この数量が、キャッシュに記録されていること - 実行例は次頁以降を参照 - 第10回以降はCookieの利用が前提です。 この<B>「サーバがブラウザに一時的に与えるパスワード」相当のCookieが必須</B>。 ?の人は第10回の演習パートを復習してください </small> --- class: compact, col-3 # <small>実行例(第11回)</small> <div class=footnote> <small><small> (脚注) 裏側の動作が正しいのかどうかは、 ブラウザの画面だけ見ても分かりません;-) <br> (1) リロードしても同じ出力です (2) 二号機でも(ホスト名以外)同じ出力です、次頁を参照 <br> (2)は第12回分の予定ですけど、進められる人は進めてかまいません。 そのぶん最終課題に早く取り組めるというものです、うむ </small></small> </div> <small> <small>  <br> ショッピングカートを開き  <br> 数量をいれ、送信すると  <br> 結果が表示されます  <br> 最初の画面に戻り、一番下「see my cart (カートを見る)」をクリックすると同じ結果が表示されるはずです </small> </small> --- class: compact, img-right # <small>実行例(第12回以降)</small> <div class=footnote> <small><small> (脚注) どんな構成でも、この見栄えは同じです。 ただし、 このスクリーンショットでURLが同じなのはELB構成で取得したから </small></small> </div>   <small> - 右図、<B>画面下部の`(debug)`行で、ホスト名だけが変わっている</B>ことが分かるでしょうか? - 二つのEC2を動かしてアクセスするので - 表示されるカートの情報は同じですが、 - `(debug)`行のホスト名とブラウザのURLは異なります <small> --- class: compact # <small>第11回の仕様: www.py を改造し、キャッシュにデータを保存する</small> <div class=footnote> <small><small> (脚注) 新しいwww.pyのソースコード中にcart関数の"ひながた"は用意されています。 次ページ以降の説明にそって、書いてください </small></small> </div> <small> ``` http://学籍番号.cloud.fml.org/cart.html ``` - (a) 上のURL(cart.html)にアクセスし、 - (b) 商品の数量を入力 - (c) 送信する - (d) Web APIサーバは受け取った数量をブラウザに返す - (裏側ではキャッシュに商品と数量の組を保存する) (NEW) - <B>ブラウザとのやりとりにはCookie Session IDをつける(実装済,開発は不要)</B> - (e) ブラウザが表示する - (f) <B>リロードもしくは再度カートにアクセスすると、先ほどの商品と数量が表示される (NEW)</B> - Web APIサーバのURLは次のものとする ``` http://学籍番号.cloud.fml.org/api/cart/v1 ``` </small> --- class: compact,col-2 # <small>解説: キャッシュシステム</small> <div class=footnote> <small><small> (脚注1) プログラミングがメインの授業ではないので、 bws.pyはブラックボックスのままでよいです。 興味のある人はコードを読んでください (脚注2) AWS Academyでもキャッシュサービス(AWS ElastiCache)は使えるのですが、 もともと高価な上に、サービスを止められないので、課金がすごいことになります。 そこでインチキクラウドBWS(Bibi Web Serices)の登場です(w) </small></small> </div> <small> ``` [構成図] www.py ---(key,value)---> キャッシュシステム ``` - Pythonでは、 `cache = { key: val }` と書けば、辞書(dict変数)cacheにキー(key)と値(val)を設定できます。 これのWeb API版だと思ってください - ただ、dict変数のように簡単には書けません。 bws.py モジュールにあるコードを使い、 書きこみ(set)と読みこみ(get)をします ``` [www.pyのコード例] import bws cache = bws.cacheInit(host, port, password) bws.cacheSet(cache, "123456789-b292900-item-01", 3) v = bws.cacheGet(cache, "123456789-b292900-item-01") print(v) # 3 と表示される ``` - AWSの本物を使うと高価なので、 代わりに(うちの偽クラウド)BWSを使ってください - <B>全員で同じサーバを使うので、 キーには長い文字列(session-id+学籍番号+商品名)が必須です </B> </small> --- class: compact # <small>演習の準備(ダウンロードとPythonモジュールの追加)</small> <div class=footnote> <small><small> (脚注) 注意事項: いろいろダウンロードするものがあります。確実にダウンロードを実行してください </small></small> </div> <small> 1. ファイルを3個、ダウンロードしてください。 URLは次のとおりです <B>(注: https になりました!)</B> ``` https://api.fml.org/dist/www.py https://api.fml.org/dist/bws.py https://api.fml.org/dist/cart.html ``` - www.pyは、新しいバージョンに入れ替えます - cart.htmlは文字化けしないように英語だけに戻しました 2. Pythonのredisモジュールをインストールしてください。 ``` $ sudo apt install -y python3-redis ``` - Redisというキャッシュシステムへアクセスするためのライブラリです </small> --- class: compact # <small>演習の準備(ダウンロードとPythonモジュールの追加)の確認</small> <div class=footnote> <small><small> </small></small> </div> - ファイルの配置が次のようになっていることを確認してください - www.pyとbws.pyは/home/admin直下に置いてください - cart.htmlはhtdocs(www.pyが見せるコンテンツの置き場所)に置いてください ``` [例] treeコマンド(に似た)出力 /home/admin |-- bws.py |-- htdocs | +-- cart.html | +-- index.html +-- www.py 2 directories, 4 files ``` --- class: compact # <small>ステップ0: CGI FORMの操作法を勉強する</small> <div class=footnote> <small><small> (脚注1) Python公式のマニュアル -> <A HREF="https://docs.python.org/ja/3.11/library/cgi.html"> https://docs.python.org/ja/3.11/library/cgi.html </A> <br> (脚注2) form[key] はオブジェクトです。 FORMで送られてくるものが文字列とは限らないですしね </small></small> </div> <small> - まずはlsformという関数(www.py v1.84の154行目〜)を読んでください。 これは送られてきたキーと値の一覧を表示するという、 (そのまま何かに使えそうな?)FORMを操作する関数の良い見本になっています - 関数の仮引数はselfとformです。 self(とかthis)はオブジェクト指向言語のアレ(関数を呼び出したインスタンス自身?)です。 変数formにはHTMLのFORM文で送られてきたデータが一式はいっています - `form.keys()`はFORMにあるキーの全てが入っている配列を返します - 特定のキーの値は`form[key].value`で取得できます。 `form[key]`ではありません!ここは要注意 ``` [コードの例] # すべてのキーを取り出し for ループに投入します。key には item-01, item-02, ... が順に入ります for key in form.keys(): val = form[key].value # key (例: item-01)に対応する値を val に代入 ``` </small> --- class: title, shelf, no-footer # 解説: www.pyのcart機能の実装 <div class=footnote> <small><small> www.py v1.84のcart関数(190行目以降)の改造です。 とりあえずノーヒントで頑張ってみてほしいですね。 <br> なお、次ページ以降のスライドはヒントです。 www.py にはコメントとして(1)(1A)などと印があります。 <br> 次ページ以降で、それぞれの部分でやるべきことを順に説明していきます </small></small> </div> --- class: compact # <small>ステップ1: 入力</small> <div class=footnote> <small><small> www.py と言わず、すべての処理は次の3ステップの積み重ねである。 (a) データを入力し、 (b) 何かの処理をして (c) 結果を出力する </small></small> </div> <small> ``` def cart(self,form): # (1) INPUT user_id = "b2xxyyyy" session = self._get_session_cookie() # (1A) ``` - CGIのFORMからブラウザが送ってきたキーと値を取り出します - cart.htmlを見ればわかるとおり、キーは item-01 〜 item-03 で、値は数字 - `user_id`は各自の学籍番号に書き換えてください - `session`行は、そのままにしてください。session変数にはcookieのセッションIDが入ります - `# (1A)`行以降で、キャッシュに書きこむキーと値を準備してください - キャッシュに書きこむキーは、一意になるように 「セッションID-学籍番号-FORMのキー」とします ``` 例: 1733750385.8418589-b2902900-item-01 ``` </small> --- class: compact # <small>ステップ2: 関数固有の仕事(ここではキャッシュへの書きこみ)</small> <div class=footnote> <small><small> www.py と言わず、すべての処理は次の3ステップの積み重ねである。 (a) データを入力し、 (b) 何かの処理をして (c) 結果を出力する </small></small> </div> <small><small> ``` # (2) DO import bws cache = bws.cacheInit(redisHost, redisPort, redisPass, session) # (2A) ``` - キャッシュに書きこみます。キャッシュを操作する関数はbwsモジュールに用意されています - cacheInit()のパラメータを変更し、`# (2A)`行以降に、キャッシュに書きこむコードを書いてください - `bws.cacheInit(ホスト, ポート番号, パスワード, session)`の部分は、 ポータルサイトにパラメータが書いてあります。 それを使ってください。 - 用意されている関数の使い方は次のとおり ``` bws.cacheInit(ホスト, ポート番号, パスワード, セッションID) bws.cacheGet(cache, キー) bws.cacheSet(cache, キー, 値) ``` - cacheGet()とcacheSet()の第1引数cacheはcacheInit()が返した値です </small></small> --- class: compact # <small>ステップ3: 出力</small> <div class=footnote> <small><small> www.py と言わず、すべての処理は次の3ステップの積み重ねである。 (a) データを入力し、 (b) 何かの処理をして (c) 結果を出力する </small></small> </div> <small> ``` # (3) OUTPUT msg = "<p>shopping cart\n<table border=0 frame=void cellspacing=30>\n" # (3A) for r_key in sorted(bws.cacheSmembers(cache, session)): msg = msg + "<tr><td>{}<td>{}</tr>\n".format(r_key,r_val) msg = msg + "</table>\n" return msg ``` - 関数から返すHTMLを変数msg (文字列)として組み立ててください。 HTMLの雛形は、ここにすべて書いてあります。 あとは返す値は何か?(適切なキーと値)を考えてください - `# (3A)`の繰り返し文は、そのまま使ってください - `r_key`はredis keyの略でキャッシュサービスで使っているキーを意味しています - <B>`bws.cacheSmembers(cache, session)`は 同一CookieセッションIDで操作した全商品のキーを返します</B> <br> (この演習で便利なように考えてある) </small> --- class: compact,center # <small></small> <div class=footnote> <small><small> </small></small> </div> 