- Flask 1.0.2
- REST APIサーバだけでなく、SPA(Single Page Application)のWebフロントサーバーとしての役割を
Flask
が担うにはどうするか?
概要
SPAルーターに定義された各URLパスに対するリクエストを捌きつつ、SPAのエントリポイントとなるindex.html
や各種JavaScript/CSSなど静的ファイルを担当し、さらにRESTエンドポイントとしても動作させるための設定をまとめた。
通常はフロントにWebサーバーを置いて、静的リソースへのリクエストはそちらに担当させることが多い。しかし簡易的なツールなど「Python単独で動作すること」が要件になることもある。その場合に参考にして欲しい。
全体像
次に示す4パターンのリクエストに対処する必要がある。
# | 分類 | 種別 | パスのパターン | 備考 |
---|---|---|---|---|
1 | バックエンド | REST API | /api/**/* |
|
2 | フロントエンド | 静的ファイル (HTML/JS/CSS) |
/ /*
|
ファイルが存在する場合のみ |
3 | 静的ファイル (画像/JSON) |
/images/* /json/*
|
||
4 | SPAルータに定義されたURLパス | 上記以外 | ブラウザリロードやURL直打ちで発生する |
本記事で示すサンプルコードのプロジェクト構成を以下に示す。
<root>
+---src/
| | main.py
| +---app/
| __init__.py
| api.py
| view.py
+---static/
+---html/
| index.html
| bundle.js
| bundle.css
+---images/
| image001.png
+---json/
messages.json
バックエンド
Blueprint
としてRESTエンドポイントを定義する。
ここではFlask-RESTful
を利用しているが、特に必須ではない。Flask
単体でも良いし、他のプラグインを使っても良い。
from typing import Dict
from flask import Blueprint
from flask_restful import Resource, Api
hello = Blueprint('hello', __name__)
api = Api(hello)
@api.resource('/hello')
class HelloResource(Resource):
def get(self) -> Dict[str, str]:
return {
'hello': 'world',
}
main.py
にて定義したRESTエンドポイントを登録する。
Flask#register_blueprint()
を呼び出す際にURLプリフィックス/api
を指定するのがポイント。
from flask import Flask
from app.api import hello
flask_app = Flask(__name__)
# REST API を定義
flask_app.register_blueprint(hello, url_prefix='/api')
上記のmain.py
は後ほど追記する。
フロントエンド
HTML/JavaScript/CSSファイルをserveする設定。これもやはりBlueprint
として定義する。
リクエストとして受け取ったファイル名が<root>/static/html/
ディレクトリに存在すればそのファイルを返し、存在しなければindex.html
を返すのがポイント。これにより、SPAルーターに定義されたURLパスに対するリクエストの処理をJavaScriptへ委ねることができる。
from flask import Blueprint, send_from_directory
from flask.helpers import NotFound
html = Blueprint('html', __name__)
@html.route('/', defaults={'filename': ''})
@html.route('/<path:filename>')
def index(filename: str):
try:
return send_from_directory('../static/html', filename)
except NotFound:
return send_from_directory('../static/html', 'index.html')
main.py
に追記し、上記で定義したBlueprint
を登録する。さらに画像ファイルおよびJSONファイルといった他の静的リソースに関するBlueprint
も作成・登録している。
from flask import Flask, Blueprint
from app.api import hello
from app.view import html
flask_app = Flask(__name__)
# REST API を定義
flask_app.register_blueprint(hello, url_prefix='/api')
# 静的ファイル(画像、JSON)
json = Blueprint('json', __name__, static_url_path='/json', static_folder='../static/json')
images = Blueprint('images', __name__, static_url_path='/images', static_folder='../static/images')
flask_app.register_blueprint(json)
flask_app.register_blueprint(images)
# 静的ファイル(HTML/JavaScript/CSS)
flask_app.register_blueprint(html)
if __name__ == '__main__':
flask_app.run(host='0.0.0.0', port=8000)
- 注意
-
Blueprint
の定義・登録順が上記のサンプルコードと異なると正しく作動しない可能性がある
-
補足
本記事のサンプルコードの動作確認のために、以下の8パターンのHTTPリクエストに対して期待するレスポンスが返却されることを確認した。
# | リクエスト | レスポンス |
---|---|---|
1 | / |
index.html |
2 | /index.html |
index.html |
3 | /bundle.js |
bundle.js |
4 | /bundle.css |
bundle.css |
5 | /foo/bar/buz |
index.html |
6 | /images/image001.png |
image001.png |
7 | /json/messages.json |
messages.json |
8 | /api/hello |
{"Hello":"World"} |