9.1 ウェブクライアント
-
ウェブはクライアントサーバーシステムになっている。クライアント→サーバーに要求。サーバー→クライアントには、TCP/IP接続を開設し、HTTPを介してURLその他の情報を送り、応答を受け取る。
-
応答にはステータス、応答データと形式がある。
-
最もよく知られたウェブクライアントはウェブブラウザである。様々な方法でHTTP要求を送ることができる。
-
個々のHTTP接続は他の接続に依存せず、独立している。(ステートレス)→HTTPの代わりに状態を管理する方法としてクッキーが使われる。
-
サーバーがクライアント固有情報をクッキーに収めてクライアントに送る。→クライアントはサーバーにクッキーを送り返すと、サーバーはクッキーからクライアントを一意に識別する。
9.1.1 Pythonの標準ウェブライブラリ
Python2ではウェブクライアントとサーバーモジュールはあまり纏まっていなかった。→Python3ではパッケージにまとめた。
-
httpはクライアントサーバーHTTPの詳細を管理する。
-
clientはクライアントサイドの処理を行う。
-
serverはPythonによるウェブサーバー開発を助ける。
-
cookiesとcookiejarは複数のサイトアクセスまたがって必要な情報を保存するクッキーを管理する。
-
urllibはhttpの上で実行される。
-
requestはクライアントの要求を処理する。
-
responseはサーバーの応答を処理する。
-
parseはURLを部品に切り分ける。
>>> import urllib.request as ur
>>> url="https://raw.githubusercontent.com/koki0702/introducing-python/master/dummy_api/fortune_cookie_random1.txt"
>>> conn=ur.urlopen(url)
>>> print(conn)
<http.client.HTTPResponse object at 0x10733c8d0>
>>> data=conn.read()
>>> print(data)
b'You will be surprised by a loud noise.\\r\\n\\n[codehappy] http://iheartquotes.com/fortune/show/20447\n'
# HTTPステータスコードの表示。
# 200は全てのコードがうまくいったことを示す。
>>> print(conn.status)
200
# データ形式表示
# HTTP応答ヘッダーのContent-Typeで指定されている。
>>> print(conn.getheader("Content-Type"))
text/plain; charset=utf-8
>>> for key,value in conn.getheaders():
... print(key,value)
...
Content-Type text/plain; charset=utf-8
Content-Security-Policy default-src 'none'; style-src 'unsafe-inline'; sandbox
Strict-Transport-Security max-age=31536000
X-Content-Type-Options nosniff
X-Frame-Options deny
X-XSS-Protection 1; mode=block
ETag W/"9f5651c47de1d25d4c531d22c98b96ea61d10d7e4f5c6eb6cbeecd9e01cdfbf8"
Cache-Control max-age=300
X-Geo-Block-List
Via 1.1 varnish-v4
X-GitHub-Request-Id D326:4317:329188:36E387:5E2EBEE7
Content-Length 99
Accept-Ranges bytes
Date Mon, 27 Jan 2020 10:43:52 GMT
Via 1.1 varnish
Connection close
X-Served-By cache-tyo19932-TYO
X-Cache MISS
X-Cache-Hits 0
X-Timer S1580121832.776951,VS0,VE269
Vary Authorization,Accept-Encoding, Accept-Encoding
Access-Control-Allow-Origin *
X-Fastly-Request-ID 034e4e0799a3de2ed0ae382d63bcd716a0574002
Expires Mon, 27 Jan 2020 10:48:52 GMT
Source-Age 0
>>>
9.1.2 標準ライブラリを超えて
- サードパーティモジュールのrequestsを使った方が便利。
>>> import requests
>>> url="https://raw.githubusercontent.com/koki0702/introducing-python/master/dummy_api/fortune_cookie_random1.txt"
>>> resp=requests.get(url)
>>> resp
<Response [200]>
>>> print(resp.text)
I know that there are people who do not love their fellow man, and I people like that!
-- Tom Lehrer, Satirist and Professor
[codehappy] http://iheartquotes.com/fortune/show/21465
9.2 ウェブサーバー
9.2.1 Pythonによる最も単純なウェブサーバー
$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
127.0.0.1 - - [27/Jan/2020 20:09:04] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [27/Jan/2020 20:09:04] code 404, message File not found
127.0.0.1 - - [27/Jan/2020 20:09:04] "GET /favicon.ico HTTP/1.1" 404 -
127.0.0.1 - - [27/Jan/2020 20:12:55] "GET /wheel HTTP/1.1" 200 -
q
C
^C
python -m http.server 9999
Serving HTTP on 0.0.0.0 port 9999 (http://0.0.0.0:9999/) ...
^C
9.2 ウェブサーバー
- ウェブフレームワークは、ウェブサイトを作るための機能を提供するものなので、単純なウェブサーバー以上のことをする。主な機能を以下に示す。
- ルーティング:URLを解析してサーバー関数を呼び出す。
- テンプレート:サーバーサイドのデータをHTMLページに流し込む。
# ポート番号は他の値も指定できる。
python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
# 127.0.0.1 :クライアントのアドレス
# 一つ目の - :リモートユーザー名
# 二つ目の - :ログインユーザー名
# "GET / HTTP/1.1":ウェブサーバーに送られたコマンド
# GETはHTTPメソッド、/は要求されたリソース(ルートディレクトリ)、HTTP/1.1はHTTPのバージョン。
# 200:HTTPステータスコード
127.0.0.1 - - [27/Jan/2020 20:09:04] "GET / HTTP/1.1" 200 -
9.2.2 WSGI
Pythoウェブアプリケーションとウェブサーバーの間の普遍的なAPIであるWSGIが定義されてから飛躍的に前進した。
9.2.3 フレームワーク
ウェブフレームワークは以下の機能の一部または全部を提供する。
- ルーティング
- テンプレート
- 認証と権限付与:ユーザー名、パスワード、パーミッションを処理する。
- セッション:ユーザーが来ている間に、一時的なデータストレージを維持管理する。
9.2.4 Bottle
- BottleはPythonファイルのみで作られているので非常に試しやすく、デプロイも簡単。
pip install bottle
Collecting bottle
Downloading bottle-0.12.18-py3-none-any.whl (89 kB)
|████████████████████████████████| 89 kB 692 kB/s
Installing collected packages: bottle
Successfully installed bottle-0.12.18
from bottle import route, run
# URLと直後の関数を対応付けるために@routeデコレータを使う。
# この場合、/(ホームページ)をhome関数が処理する。
@route("/")
def home():
return "It is not fancy, but it is my home page"
# run()関数はbottleに組み込まれているPythonによるテストウェブサーバーを実行する。
run(host="localhost",port=9999)
python3 bottle1.py
Bottle v0.12.18 server starting up (using WSGIRefServer())...
Listening on http://localhost:9999/
Hit Ctrl-C to quit.
# http://localhost:9999にアクセス。
It is not fancy, but it is my home page
# ホームページかが要求された時にbottleがこのファイルの内容を返すようにする。
from bottle import route, run, static_file
@route("/")
# rootが示す"."はカレントディレクトリ。
def home():
return static_file("index.html",root=".")
run(host="localhost",port=9999)
python bottle2.py
Bottle v0.12.18 server starting up (using WSGIRefServer())...
Listening on http://localhost:9999/
Hit Ctrl-C to quit.
# http://localhost:9999/にアクセス。
My new and improved home page!!!
from bottle import route, run, static_file
@route("/")
def home():
return static_file("index.html",root=".")
# URLを介して関数に引数を渡す。
# echo()の引数thingにはURLの中の/echo/の後ろの文字列引数を代入する。
@route("/echo/<thing>")
def echo(thing):
return "Say hello to my little friend: %s" % thing
# debug=True
# reloader=True
run(host="localhost",port=9999)
python bottle3.py
Bottle v0.12.18 server starting up (using WSGIRefServer())...
Listening on http://localhost:9999/
Hit Ctrl-C to quit.
127.0.0.1 - - [27/Jan/2020 20:59:21] "GET /echo/Mothra HTTP/1.1" 200 37
# http://localhost:9999/echo/Mothraにアクセス。
Say hello to my little friend: Mothra
- debug=Trueを指定すると、HTTPエラーが返された時にデバッグページが作られる。
- reloader=Trueを指定すると、Pythonコードに変更を加えた時にブラウザにページが再ロードされる。
9.2.5 Flask
- Bottleよりも気が利いている。
pip install flask
...
Successfully installed Jinja2-2.10.3 MarkupSafe-1.1.1 Werkzeug-0.16.1 click-7.0 flask-1.1.1 itsdangerous-1.1.0
- Flaskの静的ファイルのデフォルトホームディレクトリはstaticであり、そのディレクトリのファイルに対するURLも/staticで始まる。そこで、ホームディレクトリを"."にして、/というURLがindex.htmlファイルにマッピングされるようにしている。
- run()関数の中でdebug=Trueを設定すると、自動再ロードが有効になる。bottleはデバッグと再ロードで別々の引数を使っている。また、例外発生時に、Flaskはどこで問題た起きたか詳細情報を特別な書式で表したページを返してくる。
from flask import Flask
app=Flask(__name__, static_folder=".",static_url_path="")
@app.route("/")
def home():
return app.send_static_file("index.html")
@app.route("/echo/<thing>")
def echo(thing):
return thing
# reloader=True
app.run(port=9999, debug=True)
# http://127.0.0.1:9999/にアクセス。
My new and improved home page!!!
# http://127.0.0.1:9999/echo/Godzillaをブラウザに入力。
Godzilla
- Flaskにはjinja2をいうテンプレがある。
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Flask2 Example</title>
</head>
<body>
Say hello to my little friend:{{ thing }}
</body>
</html>>
from flask import Flask, render_template
app=Flask(__name__)
# thing=thingはURLから読み込んだ<thing>をthingという変数に代入し、これを"flask2.html"に渡している。
@app.route("/echo/<thing>")
def echo(thing):
return render_template("flask2.html",thing=thing)
app.run(port=9999,debug=True)
# http://127.0.0.1:9999/echo/Gameraをブラウザに入力
< Say hello to my little friend:Gamera >
9.2.5.1 URLパスの一部という形での引数渡し
<html>
<head>
<title>Flask3 Example</title>
</head>
<body>
Say hello to my little friend: {{ thing }}.
Alas, in just destroyed {{ place }}!
</body>
</html>
from flask import Flask, render_template
app=Flask(__name__)
@app.route("/echo/<thing>/<place>")
def echo(thing, place):
return render_template("flask3.html",thing=thing, place=place)
app.run(port=9999, debug=True)
# http://127.0.0.1:9999/echo/Rodan/McKeesportにアクセス。
Say hello to my little friend: Rodan. Alas, in just destroyed McKeesport!
from flask import Flask, render_template, request
app=Flask(__name__)
# GET引数として渡す。
@app.route("/echo/")
def echo():
thing=request.args.get("thing")
place=request.args.get("place")
return render_template("flask3.html",thing=thing, place=place)
app.run(port=9999, debug=True)
# http://127.0.0.1:9999/echo/?thing=Gorgo&place=Wilmerdingにアクセス。
Say hello to my little friend: Gorgo. Alas, in just destroyed Wilmerding!
from flask import Flask, render_template, request
app=Flask(__name__)
@app.route("/echo/")
# 辞書の**を使えばキーを辞書化してfalask3.htmlへ渡すことができる。
def echo():
kwargs={}
kwargs["thing"]=request.args.get("thing")
kwargs["place"]=request.args.get("place")
return render_template("flask3.html",**kwargs)
app.run(port=9999, debug=True)
9.2.6 Python以外のウェブサーバー
- 本番システムでのウェブサーバーは以下の二つ。
- mod_wsgiモジュール付きのApache
- uWSGIアプリケーションサーバー付きのnginx
9.2.7 その他のフレームワーク
たくさんありますね。
9.3 ウェブサービスとオートメーション
ウェブはHTML形式以外の様々な形式でアプリケーションとデータを結びつける強力な手段として使われる。
9.3.1 Web APIとREST
- Web APIでウェブページのデータを提供できる
- RESTfulサービスはHTTPの動詞を決まった方法で使う。
- HEAD:リソースについての情報を取得する。
- GET:サーバーからリソースのデータを取得する。
- POST:サーバーのデータを更新する。
- PUT:この動詞は新しいリソースを作る。
- DELETE:削除
9.3.2 JSON
- ウェブクライアントとサーバーのデータ交換に適している。
9.3.3 クロールとスクレイピング
いつかの勉強用のために。
「Scrapyチュートリアル」
https://doc-ja-scrapy.readthedocs.io/ja/latest/intro/tutorial.html
9.3.5 BeautifulSoupによるHTMLのスクレイピング
- ウェブサイトから既にHTMLとデータが取り出してある場合、BeautifulSoupが役に立つ。
9.4 復習課題
9-1 Flaskのデバッグ、サイロードできる開発用ウェブサーバーを使って骨組みだけのウェブサイトを作ろう。サーバーはホスト名localhost、ポート5000を使って起動すること。
from flask import Flask
app=Flask(__name__)
app.run(port=5000, debug=True)
9-2 ホームページに対する要求処理するhome()関数を追加しよう。 It is aliveという文字列を返すようにセットアップせよ。
from flask import Flask
app=Flask(__name__)
@app.route("/")
def home():
return "It is alive"
app.run(port=5000, debug=True)
9-3 以下のような内容でhome.htmlをという名前のjinja2テンプレートファイルを作ろう。
<html>
<head>
<title>It is alive!</title>
<body>
I am of course referring to {{thing}},which is {{height}} feet tall and {{color}}.
</body>
</html>
9-4 home.htmlテンプレートを使うようにサーバーのhome()関数を書き換えよう。
home()関数には、thing, height,colorの3個のGET引数を渡すこと。
from flask import Flask, render_template, request
app=Flask(__name__)
@app.route("/")
def home():
thing=request.args.get("thing")
height=request.args.get("height")
color=request.args.get("color")
return render_template("home.html",thing=thing, height=height, color=color)
app.run(port=5000, debug=True)
感想
Web系をさらっと復習しました。
ここら辺は使うとしてももうちょい先になりそうだ。
参考文献
「Bill Lubanovic著 『入門 Python3』(オライリージャパン発行)」