FlaskですべてのURLにPrefixを追加する

## 1. はじめに
Flaskの拡張ポイントを利用して、JavaEEのWebアプリのコンテキスト名のように、全てのURLにPrefixを追加する方法について説明します。実装サンプルとしてはPrefixを環境変数（`APP_ROOT`）で設定できるようにしたいと思います。

### 1.1. 検証環境
* Python 2.7.13
* Flask 0.12.2

## 2. URLのマッピングルールは`url_rule_class`で変更
FlaskでURLのマッピングルールの処理を行うクラスは`url_rule_class`で変更することができます。
デフォルトの設定は`werkzeug.routing.Rule`クラスです。

Flaskの公式ドキュメントにもしっかりと書かれています。
http://flask.pocoo.org/docs/0.12/api/#flask.Flask.url_rule_class 

> **url_rule_class**
>  The rule object to use for URL rules created. This is used by 
>  add_url_rule(). Defaults to werkzeug.routing.Rule.
> 
>  New in version 0.7.

### 2.1. ソースコード
環境変数(`APP_ROOT`)に設定されているPrefixを全てのURLに付与するRuleクラスを作成し、`url_rule_class`で変更するようにします。
なお、Ruleクラスをゼロから作成するのは大変なので、今回は`werkzeug.routing.Rule`を継承して実装することにします。

```python:custom_rule_app.py
# -*- coding: utf-8 -*-
import os
from flask import Flask, make_response, jsonify
from werkzeug.routing import Rule

# flask
app = Flask(__name__)

# get prefix from environment variable
APP_ROOT = os.getenv("APP_ROOT")
if not APP_ROOT is None:
    # define custom_rule class
    class Custom_Rule(Rule):
        def __init__(self, string, *args, **kwargs):
            # check endswith '/'
            if APP_ROOT.endswith('/'):
                prefix_without_end_slash = APP_ROOT.rstrip('/')
            else:
                prefix_without_end_slash = APP_ROOT
            # check startswith '/'
            if APP_ROOT.startswith('/'):
                prefix = prefix_without_end_slash
            else:
                prefix = '/' + prefix_without_end_slash
            super(Custom_Rule, self).__init__(prefix + string, *args, **kwargs)

    # set url_rule_class
    app.url_rule_class = Custom_Rule

# rest api
@app.route('/hello', methods=['GET'])
def hello_world():
    return make_response(jsonify({'result':'hello world!'}))

@app.route('/goodbye', methods=['GET'])
def goodbye():
    return make_response(jsonify({'result':'goodbye!'}))

# main
if __name__ == "__main__":
    # 利用しているurl_rule_classを表示
    print app.url_rule_class
    # Flaskのマッピング情報を表示
    print app.url_map
    app.run(host='localhost', port=3000)
```

### 2.2. 動作確認

```console:環境変数を設定した場合（APP_ROOT=/app/api/v1）
C:\demo>set APP_ROOT=/app/api/v1

C:\demo>python custom_rule_app.py
<class '__main__.Custom_Rule'>
Map([<Custom_Rule '/app/api/v1/goodbye' (HEAD, OPTIONS, GET) -> goodbye>,
 <Custom_Rule '/app/api/v1/hello' (HEAD, OPTIONS, GET) -> hello_world>,
 <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>])
 * Running on http://localhost:3000/ (Press CTRL+C to quit)
127.0.0.1 - - [02/Oct/2017 16:27:42] "GET /app/api/v1/hello HTTP/1.1" 200 -
127.0.0.1 - - [02/Oct/2017 16:28:00] "GET /app/api/v1/goodbye HTTP/1.1" 200 -
127.0.0.1 - - [02/Oct/2017 16:28:12] "GET /hello HTTP/1.1" 404 -
127.0.0.1 - - [02/Oct/2017 16:28:18] "GET /goodbye HTTP/1.1" 404 -
```
環境変数（`APP_ROOT`）に`/app/api/v1`を設定してデモアプリを起動します。
標準出力の`<class '__main__.Custom_Rule'>`から、今回作成したRuleクラスが適用されているのが分かるかと思います。また、マッピング情報を見るとPrefixとして`/app/api/v1`がしっかりと付与されています。

実際にGETで`/app/api/v1/hello`にアクセスすると正常に動作し、ソースコードの`@app.route('/hello', methods=['GET'])`に該当する`/hello`にアクセスすると404エラーになります。

マッピング情報をよく見ると`<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>])`となっています。つまり、**静的ファイルの処理にはPrefixが付与されません。これは公式ドキュメントの「This is used by add_url_rule().」が答えです。静的ファイルのマッピングには add_url_rule() が使われないためです。**

静的ファイルのURLを変更する方法については「[Flaskで静的ファイルの格納ディレクトリとURLを変更する](https://qiita.com/5zm/items/db6acb96594e0ff7c549)」に記載していますので、こちらを参照ください。

```console:環境変数を設定しない場合
C:\demo>set APP_ROOT
環境変数 APP_ROOT が定義されていません

C:\demo>python custom_rule_app.py
<class 'werkzeug.routing.Rule'>
Map([<Rule '/goodbye' (HEAD, OPTIONS, GET) -> goodbye>,
 <Rule '/hello' (HEAD, OPTIONS, GET) -> hello_world>,
 <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>])
 * Running on http://localhost:3000/ (Press CTRL+C to quit)
```
環境変数（`APP_ROOT`）が設定されていない場合、デフォルトのルールクラスである`werkzeug.routing.Rule`のままになっているのが分かるかと思います。

## 3. さいごに
一般に公開されているマイクロサービス（REST API）ではURLに`/api/v1`等が含まれているものが多いかと思います。
今回はこのようなお決まり的な値をAP開発者全員が意識することなくコーディングできるよう、Flaskの拡張ポイントである`url_rule_class`を利用して、全てのURLにPrefixを追加する方法について説明しました。

Flaskには他にもたくさんの拡張ポイントが存在しますので、また別の機会に紹介していければと思います。