Chapter 17

共通機能パッケージを整理する

パッケージを整理する

前章でhenangoというパッケージを作成しました。

改めて説明しておくと、このパッケージは 「仮にこのWebアプリケーションを使って全く別のWebサービスを作ることになった場合でも必要になる(=使い回せる)機能」 をまとめるパッケージです。


例えば「Webサービス」を作るといっても、「個人のブログ」や「キュレーションメディア」から、果ては「Gmail」や「Github」まで様々なものがあります。
しかし、どんなWebサービスを作るにしろ、

  • HTTPリクエストを送受信できる
  • HTTPリクエストをparseして、HTTPRequestクラスに変換する
  • リクエストのpathを見て、エンドポイントごとに違う「レスポンス生成関数」を呼び出す
  • cookieやsessionを管理する(後ほどでてきます)

などの機能は必ず必要になります。

これらの共通機能だけを1つのパッケージにまとめておくことで、色々なWebサービスを作ることになったときも使いまわしが効きます。

より具体的に言うと、ブログを作るにしろ、メディアを作るにしろ、共通機能パッケージをまるごとコピーして、urls.pyviews.pyだけ編集すればすぐに新しいWebサービスが構築できますよ、といった具合です。

これらの共通機能部分のことを総称して Webフレームワーク と呼び、様々な種類のものがライブラリ化され世の中に出回っています。
PythonであればDjangoFlask、PHPであればLaravelCakePHP、JavaであればSpring、Goであればechoなどが有名です。


本書で作るWebアプリケーションは、厳密に言うと共通機能と呼べるものは存在しません。
このWebアプリケーションを「共通で」使うことになる複数のWebサービスが存在しないからです。

YAGNIの法則 に従えば、実際にこのWebアプリケーションを使って複数のWebサービスを作ることになるまで共通機能部分の切り出しは行わないほうが良いでしょう。

しかし、皆さんが普段使っているであろうWebフレームワークの挙動をより深く理解する、というのが本書の目的の一つですので、この共通機能(=フレームワーク)部分とサービス固有の部分とを区別して整理しておくことにします。

以後は
「仮にこのWebアプリケーションを使って全く別のWebサービスを作ることになった場合でも必要になる(=使い回せる)機能」
henangoパッケージにまとめていきます。

このhenangoパッケージは、皆さんの使ったことがある「Webフレームワーク」に相当するパッケージですので、そのことを意識しながら
henangoでいう〇〇という機能は、Laravelでいう××という機能のことかなぁ」
などと照らし合わせながら読み進めていただけると効果的かと思います。


パッケージの整理は細々とした変更をそれなりの量加えることになりますので、いくつかのステップに分けて説明していきます。

STEP1: サーバーの起動スクリプトをwebserver.pyから分離する

現在、サーバーの起動スクリプトがwebserver.pyに含まれています。
この後webserver.pyhenangoモジュールへお引越しする予定なのですが、起動スクリプトはプロジェクトのトップディレクトリ(= studyディレクトリ)にないとimportが面倒なことになりますので、まずはこれを分離させます。

pythonのご都合だと思っていただいて構いません。多くの言語で、セキュリティの観点から起動スクリプトより上位ディレクトリのソースコードは実行しにくくできています。)

studyディレクトリに起動スクリプトstart.pyを追加し、webserver.pyからサーバーを起動する記述を削除します。

ソースコード

study/start.py

https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter17/start.py

https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter17/webserver.py

解説

study/start.py

from webserver import WebServer

if __name__ == "__main__":
    WebServer().serve()

特に解説は不要でしょう。
webserver.pyにかかれていた記述を切り出しただけです。

study/webserver.py

49行目以降

# 削除
# if __name__ == "__main__":
#     WebServer().serve()

サーバーを起動する記述が書かれていたのを削除しました。

動かしてみる

起動スクリプトが変更になりましたので、起動コマンドも変更になります。

今回からは、コンソールでstudyディレクトリまで移動した後、

$ python start.py

としてサーバーを起動させてください。
(webserver.pyからstart.pyへ変更になっています。)

挙動は変わっていませんので、いくつかのページ(/index.html/parametersなど)を表示させてみて、問題がないか確認してください。

STEP2: webserver.pyhenangoパッケージへ移動する

では、改めてwebserver.pyworkerthread.pyをお引越しさせます。
henangoディレクトリ下に、新しくserverというディレクトリを作成し、その下に移動させます。

また、本書序盤では「Webサーバーとは何か」を説明したり「スレッドとはなにか」を説明するために敢えてファイル名にもそれらのワードを入れていましたが、今となっては少し冗長ですので

【ファイル名】
webserver.py => server.py
workerthread.py => worker.py

【クラス名】
WebServer => Server
WorkerThread => Worker

へとついでに変更してしまいます。

ソースコード

study/henango/server/server.py

https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter17-2/henango/server/server.py

study/henango/server/worker.py

https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter17-2/henango/server/worker.py

study/start.py

https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter17-2/start.py

解説

study/henango/server/server.py

import socket

from henango.server.worker import Worker


class Server:
    """
    Webサーバーを表すクラス
    """

    # ...

ディレクトリ、ファイル名、クラス名を変更したのみですので、その他内容には変更はありません。
importするworkerのクラス名(Worker)が変更になっているのも忘れないようにしてください。

study/henango/server/worker.py

# ...

class Worker(Thread):
    # ...

こちらも同様です。

study/start.py

from henango.server.server import Server

if __name__ == '__main__':
    Server().serve()

こちらは、ファイル移動なども特になく、importするクラス名が変更になったのみです。

STEP3: STATIC_ROOTの設定値を切り出す

上記を終えたあと、サーバーを起動して/index.htmlへアクセスしてみてください。
404になっていると思います。

これは、worker.py内で静的ファイルのディレクトリを示す変数STATIC_ROOTが相対パスの指定になっており、ディレクトリ構造が変わったことでファイルをみつけられなくなってしまったためです。

フレームワークのディレクトリ構造が変わることは普通滅多にないこととはいえ、静的ファイルを配置するディレクトリはプロジェクトごとに変化することは十分考えられます。
現在はプロジェクトルート直下にstaticというディレクトリを用意していますが、プロジェクトによってstaticという名前を避けたいとか、resouces/staticに配置したいとか、そういったことはよくあります。

そういった状況でもフレームワークには手を入れなくて良いように、STATIC_ROOTの設定値はフレームワーク外から上書きできたほうがよいでしょう。
フレームワークの設定値を上書きするためのモジュールsettings.pyをプロジェクトルートに作成しましょう。

ソースコード

study/henango/server/worker.py

https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter17-3/henango/server/worker.py

study/settings.py

https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter17-3/settings.py

解説

study/henango/server/worker.py

122-132行目

    def get_static_file_content(self, path: str) -> bytes:
        """
        リクエストpathから、staticファイルの内容を取得する
        """
        default_static_root = os.path.join(os.path.dirname(__file__), "../../static")
        static_root = getattr(settings, "STATIC_ROOT", default_static_root)

        # pathの先頭の/を削除し、相対パスにしておく
        relative_path = path.lstrip("/")
        # ファイルのpathを取得
        static_file_path = os.path.join(static_root, relative_path)

static_rootを取得する処理を変更しています。
settingsモジュールにSTATIC_ROOTという値が存在すればそれを取得し、なければデフォルトの値を使用しています。

study/settings.py

import os

# 実行ファイルのあるディレクトリ
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# 静的配信するファイルを置くディレクトリ
STATIC_ROOT = os.path.join(BASE_DIR, "static")

STATIC_ROOTを上書き定義しています。
今のところ有意な値はSTATIC_ROOTだけですが、今後色々と追加される予定です。

動かしてみる

それでは、start.pyでサーバーを起動して、動作確認してみてください。
今度は/index.htmlも正常に表示されるはずです。


Djangoを使ったことがある人は、私達のWebアプリケーションがもうかなりDjangoっぽくなってきたことにお気づきでしょう。
Djangoを使ったことがない人も、それぞれのモジュールやパッケージが皆さんの使っているWebフレームワークにところどころ似ていることに気づくでしょう。

「アレをもうちょっと変えたら、もっと自分の知っているフレームワークに近づくんじゃないか?」
と疑問に思った人は、本書の筋から離れて改良してもらって全く構いません。

本書では、できるだけ自然に(= 必要になったときに)拡張を重ねていく方針ですので、あえて機能不足のまま進めている箇所があちこちにあります。

しかし、勉強はなにより自分が楽しいと思うまま、好奇心の赴くままにやるのが一番吸収が早いものです。

気が向いた方は、是非皆さん独自の「オレオレフレームワーク」を作り上げてみてください。