FalconでAPIの前処理・後処理をHooksとMiddlewareで実装

# FalconでAPIの前処理・後処理を実装する

Flaskに比べて高速なAPIを実装できるFalconを利用した場合、共通的な前処理・後処理は2つの実装方法がある。
認証処理やデータベースの接続処理の共通化の調査結果をメモとしてまとめておく。

- Hooks
API単位にデコレータをセットし、前処理・後処理はそれぞれのHooksで設定する。API単位に細かく設定を変更した場合に向く。

- Middleware
ソースのAPI全体にミドルウェアとして設定する。前処理・後処理のほかメインルーチンも設定できる。
 

## Hooks ... API単位に設定
API単位に前処理・後処理をHooks(デコレータ)で設定する。Flaskでいうbefore_request、after_requestメソッドに相当する。

### [処理の実装]

```Python
def before_hook(req, resp, resource, params):
    # 前処理を実装する

def after_hook(req, resp, resource):
    # 後処理を実装する

class TestResource(object):
    @falcon.before(before_hook)
    @falcon.after(after_hook)
    def on_post(self, req, res):
        # POSTメソッドの処理

app = falcon.API(middleware=[])
app.add_route("/", TestResource())
```


### [処理の中断]
処理途中で例外を発生させる場合はraiseを利用する
たとえば前処理を途中で打ち切る場合、以下のようなコードを記載することで後続処理は実行されない。

```Python
def before_hook(req, resp, resource, params):
    raise falcon.HTTPBadRequest('Bad request')
```

## Middleware ... ソース内のAPI全体に設定
前処理、メインルーチン、後処理をそれぞれのメソッドで作成する。

### [処理の実装]

```Python:middleware.py
class TestMiddleware(object):
    # 前処理
    def process_request(self, req, resp):
        # 前処理を実装する

    # メインルーチン
    def prrocess_resource(self, req, resp, resource, params):
        # メインルーチンを実装する

    # 後処理
    def process_response(self, req, resp, resource, req_succeeded):
        # 後処理を実装する
```

### [Middlewareの登録]
falconの初期化時にMiddlewareを組み込む。パラメタは配列形式なので複数指定できる。Falconからは標準機能としてCORSMiddlewareが提供されている。

```Python
app = falcon.API(middleware=[middleware.TestMiddleware()])
```

### [処理の中断]
resp.complete、resp.statusを設定する。
たとえば前処理を途中で打ち切る場合、以下のようなコードを記載することで後続処理は実行されない。

```Python
class TestMiddleware(object):
    # 前処理
    def process_request(self, req, resp):
        resp.complete = True
        resp.status = falcon.HTTP_400
```


## その他
Flaskでのグローバル機能(g変数)に近い機能は、将来実装予定だと思われる。
このやり方が正しいかは不明だが、たとえば前処理でreqに独自のメンバ変数を格納することで、後続処理でその変数を参照した動作を作成できる。

### [例]
1. 前処理でSQLiteのコネクションを取得しreq.curにcursorオブジェクトを格納
2. API内部でreq.curを利用してSQLite3にINSERT
3. 後処理でcommit / rollback

```Python
# -*- coding:utf-8 -*-
import json
import falcon
import sqlite3

# SQLite3データベースの準備
conn = sqlite3.connect(':memory:')
cur = conn.cursor()
cur.execute('create table test(data text)')
conn.commit()
cur.close()

# 前処理のHooks
def before_hook(req, resp, resource, params):
    # SQLite3データベースのcursorオブジェクトを作成
    req.cur = conn.cursor()

# 後処理のHooks
def after_hook(req, resp, resource):
    # statusコードによりcommitかrollbackを実行
    if resp.status != falcon.HTTP_200:
        conn.rollback()
    else:
        conn.commit()
    req.cur.close()

class TestResource(object):
    @falcon.before(before_hook)
    @falcon.after(after_hook)
    def on_post(self, req, res):
        # POSTメソッドの実装
        data = json.load(req.bounded_stream)
        req.cur.execute("insert into test values(?)", (str(data),))
        msg = {"message": "POST OK"}
        #res.status = falcon.HTTP_400
        res.body = json.dumps(msg)

app = falcon.API(middleware=[])
app.add_route("/", TestResource())
if __name__ == "__main__":
    from wsgiref import simple_server
    httpd = simple_server.make_server("0.0.0.0", 8000, app)
    httpd.serve_forever()
```


## 参考ページ
[Falcon](https://falcon.readthedocs.io/en/latest/index.html)
[Hooks - Falcon](https://falcon.readthedocs.io/en/latest/api/hooks.html)
[Middleware - Falcon](https://falcon.readthedocs.io/en/latest/api/middleware.html)