第15章 WEB対応

WEBフレームワーク

Pythonには多くのウェブフレームワークが存在します。これらのフレームワークは、ウェブアプリケーションやAPIを効率的に開発するためのツールやライブラリを提供しています。以下は、Pythonで最も人気のあるウェブフレームワークのいくつかを紹介します。

  1. Flask
    • 概要:Flaskは、軽量で柔軟性が高いマイクロフレームワークです。
    • 特徴:
      • 小さく、簡単に拡張できる設計
      • Jinja2テンプレートエンジンを使用
      • データベースへの接続やORMを自分で選択する自由度
      • RESTfulなリクエストの処理が得意
      • 大規模なアプリケーションから小規模なアプリケーションまで対応
  2. Django
    • 概要:Djangoは、高レベルでプラグアンドプレイ式のフレームワークで、"batteries-included"アプローチを採用しています。
    • 特徴:
      • 豊富な機能を持つ統合型フレームワーク
      • ORMが組み込まれており、多くのデータベースと簡単に接続可能
      • 自動的に管理画面を提供
      • 安全性を考慮した設計(CSRF、XSS、SQLインジェクション対策など)
      • 豊富なドキュメントと大きなコミュニティ
  3. Pyramid
    • 概要:Pyramidは、中規模から大規模なアプリケーションを対象とした軽量フレームワークです。
    • 特徴:
      • 柔軟性が高く、必要なコンポーネントを選択して使用できる
      • URLジェネレーション、セキュリティ、テンプレートのサポート
      • さまざまなデータベースやストレージエンジンとの連携が可能
  4. FastAPI
    • 概要:FastAPIは、モダンで高速なAPIの開発に特化したフレームワークです。
    • 特徴:
      • 高速性(NodeJSやGoと比較しても高速)
      • 非同期処理のサポート
      • タイプヒンティングとPydanticベースの自動リクエスト・レスポンスシリアライゼーション
      • 自動的にAPIドキュメント(Swagger UIやReDoc)を生成
  5. Tornado
    • 概要:Tornadoは、非同期ネットワーキングライブラリを持つウェブサーバーおよびフレームワークです。
    • 特徴:
      • 高い同時接続数を持つアプリケーション向け
      • WebSockets、HTTP2などの最新のネットワーク技術をサポート
      • 非同期I/Oを利用した高速なパフォーマンス

HTTPリクエストの扱い

ここでは、外部ライブラリであるrequestsを用いた例を紹介します。requestsはHTTPリクエストを簡単に扱うことができるライブラリです。

まず、requestsライブラリをインストールします。

pip install requests

次に、各HTTPメソッドを使用する基本的なPythonのコード例を示します。

import requests

# GETリクエスト
response = requests.get('https://example.com/api/resource')
print(response.json())

# POSTりクエスト
data = {
    'key1': 'value1',
    'key2': 'value2'
}
response = requests.post('https://example.com/api/resource', data=data)
print(response.json())

# PUTリクエスト
data = {
    'key1': 'new_value1',
    'key2': 'new_value2'
}
response = requests.put('https://example.com/api/resource/123', data=data)
print(response.json())

# DELETEリクエスト
response = requests.delete('https://example.com/api/resource/123')
print(response.status_code)

これらの基本的な操作を通じて、PythonでHTTPメソッドを使ってリソースにアクセスする方法の概要を掴むことができます。具体的なアプリケーションやAPIの開発には、上述したウェブフレームワークや他のツール、ライブラリを組み合わせて使用することが一般的です。


クッキーとセッション

ッキーは、Webサーバーからクライアントに送られ、クライアント側(ブラウザ)で保存される小さなデータの断片です。これは、次回そのクライアントが同じサーバーにリクエストを送る際に、そのクッキーがサーバーに送り返されることで、ユーザーの状態や過去の行動をサーバーが追跡する手助けをするものです。

クッキーの設定

Pythonの標準ライブラリを使用してクッキーを設定する方法を示します。

from http.server import BaseHTTPRequestHandler, HTTPServer
from http.cookies import SimpleCookie

class SimpleRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        cookie = SimpleCookie()
        cookie["user"] = "Alice"
        cookie["user"]["path"] = "/"
        
        self.send_response(200)
        self.send_header('Set-Cookie', cookie["user"].OutputString())
        self.end_headers()
        self.wfile.write(b"Cookie set!")

httpd = HTTPServer(('localhost', 8000), SimpleRequestHandler)
httpd.serve_forever()


クッキーの読み取り

クッキーはリクエストヘッダーに含まれるため、次のようにしてクッキーを読み取ることができます。

cookie_string = self.headers.get('Cookie')
cookie = SimpleCookie(cookie_string)
user = cookie["user"].value if "user" in cookie else "Unknown"


セッションの基本的な仕組み

  1. セッションIDの生成: ユーザーがセッションを開始するたびに、ユニークなセッションIDを生成します。
  2. クッキーへのセッションIDの保存: 生成したセッションIDを、クッキーとしてユーザーのブラウザに送信します。
  3. サーバー側でのデータの保存: セッションIDに紐付けられたデータを、サーバー側に保存します。これはメモリ、データベース、ファイルシステムなどで実現できます。
  4. セッションIDを使ったデータの参照: ユーザーが再度リクエストを行うと、クッキーからセッションIDを読み取り、それをキーとしてサーバー側のデータを参照します。

以下は、非常にシンプルなセッション管理を実装した例です。実際の環境での利用は推奨されませんが、概念を理解するための参考としてください。

import http.server
import os
import hashlib

SESSIONS = {}

class SimpleSessionHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        # クッキーからセッションIDを取得
        session_id = self.headers.get('Cookie')

        # セッションIDが存在しない、または無効な場合は新しいセッションを生成
        if not session_id or session_id not in SESSIONS:
            session_id = hashlib.sha256(os.urandom(16)).hexdigest()
            self.send_header('Set-Cookie', session_id)
            SESSIONS[session_id] = "Hello, new user!"

        # レスポンスの送信
        self.send_response(200)
        self.end_headers()
        response_string = f"Your session data: {SESSIONS[session_id]}"
        self.wfile.write(response_string.encode())

httpd = http.server.HTTPServer(('localhost', 8000), SimpleSessionHandler)
httpd.serve_forever()


セッションの注意点

  1. セッションハイジャック: セッションIDが第三者に漏れると、そのIDを使ってセッションを乗っ取られる可能性があります。
  2. セッション固定攻撃: 攻撃者がセッションIDを事前に設定し、それを犠牲者に使わせる攻撃。
  3. データの保存: メモリ内にデータを保存すると、サーバーが再起動したりクラッシュするとデータが失われます。そのため、永続的なストレージの利用を検討する必要があります。
  4. セキュアな通信: クッキーを含む全ての通信はHTTPSを使用して暗号化する必要があります。

レスポンスヘッダーとステータスコード

レスポンスヘッダを設定するためには、send_headerメソッドを、ステータスコードを設定するためにはsend_reponseメソッドを使用します。

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)  # ステータスコードを設定
        self.send_header('Content-type', 'text/plain; charset=utf-8')  # ヘッダを設定
        self.send_header('Custom-Header', 'This is a custom header.')  # カスタムヘッダを設定
        self.end_headers()
        self.wfile.write(b"Hello, World!")

このようにして、ステータスコードとヘッダを設定し、クライアント(通常はWebブラウザ)に対して情報を提供することができます。


CSRF対策

CSRF対策の一般的な方法として、トークンを利用する手法を取り上げ、Pythonを用いた簡易的なサンプルコードを提供します。


ランダムなCSRFトークンの生成と保存。

import os
import secrets

# ランダムなCSRFトークンを生成
def generate_csrf_token():
    return secrets.token_hex(16)

# セッションへのCSRFトークンの保存 (ここでは辞書として模倣)
session = {}
session['csrf_token'] = generate_csrf_token()


HTMLフォームでのCSRFトークンの使用。

<!-- あるアクションを行うためのフォーム -->
<form action="/perform_action" method="post">
    <input type="hidden" name="csrf_token" value=""> <!-- CSRFトークンを隠しフィールドとして埋め込む -->
    <!-- 他のフォーム要素 -->
    <input type="submit" value="Submit">
</form>


サーバ側でのCSRFトークンの検証。

from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import parse_qs

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length).decode('utf-8')
        post_vars = parse_qs(post_data)

        # CSRFトークンの検証
        if post_vars.get('csrf_token') != session.get('csrf_token'):
            self.send_response(403)  # Forbidden
            self.end_headers()
            self.wfile.write(b'Invalid CSRF token')
            return
        
        # 以下、実際のアクションの処理
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'Action performed successfully')

# サーバの起動
with HTTPServer(('localhost', 8080), SimpleHTTPRequestHandler) as httpd:
    httpd.serve_forever()

上記のサンプルでは、非常にシンプルなHTTPサーバーを使っていますが、実際のアプリケーションでは、より高度なサーバーやフレームワークを使用することが一般的です。


JSONの扱い

Pythonにおいて、JSONの操作は非常に簡単で、標準ライブラリにあるjsonモジュールを使用します。以下は、基本的なJSONの扱いに関するソースコードの説明です。


PythonのオブジェクトをJSON文字列に変換(エンコード)

import json

data = {
    "name": "John",
    "age": 30,
    "city": "New York"
}

# PythonのオブジェクトをJSON文字列に変換
json_string = json.dumps(data)
print(json_string)  # {"name": "John", "age": 30, "city": "New York"}


JSON文字列をPythonのオブジェクトに変換(デコード)

import json

json_string = '{"name": "John", "age": 30, "city": "New York"}'

# JSON文字列をPythonのオブジェクトに変換
data = json.loads(json_string)
print(data)  # {'name': 'John', 'age': 30, 'city': 'New York'}


PythonのオブジェクトをJSONファイルとして保存

import json

data = {
    "name": "John",
    "age": 30,
    "city": "New York"
}

# PythonのオブジェクトをJSONファイルに保存
with open('data.json', 'w') as file:
    json.dump(data, file)


JSONファイルをPythonのオブジェクトとして読み込み

import json

# JSONファイルをPythonのオブジェクトとして読み込み
with open('data.json', 'r') as file:
    data = json.load(file)
    print(data)  # {'name': 'John', 'age': 30, 'city': 'New York'}


以上が、Pythonの標準ライブラリを使用してJSONの基本的な操作を行う方法です。エンコードやデコードの際に、さまざまなオプションを利用してカスタマイズすることも可能です。


APIクライアント作成

PythonでAPIクライアントを作成する場合、requestsというサードパーティのライブラリを使用するのが一般的です。このライブラリを使用することで、HTTPリクエストを簡単に行うことができます。


インストール

まず、requestsライブラリをインストールします。

pip install requests


APIクライアントの作成

以下は、公開APIである「JSONPlaceholder」を使って、TODOリストを取得するシンプルなAPIクライアントのサンプルコードです。

import requests

class APIClient:
    BASE_URL = "https://jsonplaceholder.typicode.com/"

    @classmethod
    def get_todos(cls, user_id=None):
        url = cls.BASE_URL + "todos"

        # user_idが指定されていれば、そのユーザーのTODOのみをフィルタリングして取得
        if user_id:
            response = requests.get(url, params={"userId": user_id})
        else:
            response = requests.get(url)

        if response.status_code == 200:
            return response.json()
        else:
            response.raise_for_status()

# 使用例
api_client = APIClient()
todos = api_client.get_todos()
print(todos)  # すべてのTODOリストを表示

specific_todos = api_client.get_todos(user_id=3)
print(specific_todos)  # user_id=3 のユーザーのTODOリストを表示


練習問題1.

Pythonの組み込みライブラリであるhttp.serverを使って、簡単なAPIを作成してください。

  1. GET メソッドで /greet?name=<YOUR_NAME> にアクセスした際に、Hello, <YOUR_NAME>! というレスポンスを返すサーバーを作成してください。
  2. <YOUR_NAME> の部分には任意の名前を入力できるようにしてください。


練習問題2.

Pythonのrequestsライブラリを使用して、以下の外部APIを呼び出し、結果を整形して表示してください。

URL: https://api.agify.io/?name=<YOUR_NAME>

このAPIは、入力された名前に基づいて推定される平均年齢を返します。

  1. 任意の名前を入力してAPIを呼び出し、その名前に関連する推定平均年齢を表示してください。
  2. エラーが発生した場合、適切なエラーメッセージを表示してください。


練習問題3.

Pythonの組み込みライブラリであるhttp.serverを使って、以下の要件を満たすサーバーを作成してください。

  1. /にアクセスした際に、カレントディレクトリにあるindex.htmlファイルを表示してください。
  2. /imageにアクセスした際に、カレントディレクトリにあるsample.jpg画像ファイルを表示してください。
  3. 存在しないルートにアクセスされた場合、404 Not Found のエラーメッセージを表示してください。