flaskでhttpステータスを返却する方法

## はじめに
http通信が失敗した場合に、httpステータスとメッセージを返却してユーザにエラー時の動作を通知することが良くあります。
flaskはpythonのwebフレームワークで、httpステータスやメッセージの返却する方法をまとめます。


## 環境
 - python:3.6.5
 - flask:1.0.2


## エンドポイントの関数からhttpステータスを指定する
URLに紐づけた関数のreturn時にhttpステータスを追加することでクライアントに返却するhttpステータスを簡単に指定できます。

サーバ側

``` python:flask_main.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/hello')
def hello():
    return jsonify({'message': 'hello internal'}), 500

app.run()
```

クライアント側

``` python:request_url.py

import requests

response = requests.get('http://localhost:5000/hello')

print('httpステータス:{}, メッセージ:{}'.format(response.status_code, response.text))
```

実行

``` 
# python .\request_url.py
httpステータス:500, メッセージ:{"message":"hello internal"}
```

returnにhttpステータス500を指定してあげることでクライアント側にhttpステータスとメッセージが返却できました。
この方法はflaskの仕様を知っていれば、簡単でソースがシンプルになる点が利点だと思います。
一方returnがあればどこでも自由にステータスが指定できてしまうため、ちゃんとしたルールのもと作らないと思わぬところでreturnする欠点もあります。


## Responseクラスにhttpステータスを指定する
flaskは上のようなreturnだけでなく、ユーザへの返却にResponseクラスを使用することができます。
そのResponseクラスにhttpステータスを追加することでクライアントに返却するhttpステータスを簡単に指定できます。

サーバ側

``` python:flask_main.py
from flask import Flask, Response
import json

app = Flask(__name__)

@app.route('/hello')
def hello():
    return Response(response=json.dumps({'message': 'hello response'}), status=500)

app.run()
```

クライアント側

``` python:request_url.py

import requests

response = requests.get('http://localhost:5000/hello')

print('httpステータス:{}, メッセージ:{}'.format(response.status_code, response.text))
```

実行

``` 
# python .\request_url.py
httpステータス:500, メッセージ:{"message": "hello response"}
```

Responseクラスを生成時にhttpステータス500を指定してあげることでクライアント側にhttpステータスとメッセージが返却できました。
この方法はぱっと見でhttpステータスの指定がわかりやすい点が利点だと思います。
一方return時にクラスを生成しているため、ソースが若干見にくく、エラーだと言うイメージが持ちづらい点が欠点になります。



## abort関数にhttpステータスを指定する
flaskはabortと言うhttp用エクセプションのような関数を持っています。
これにhttpステータスとメッセージを追加することでクライアントに返却するhttpステータスを指定できます。

サーバ側

``` python:flask_main.py
from flask import Flask, jsonify, abort
import json

app = Flask(__name__)

@app.route('/hello')
def hello():
    abort(500, 'hello abort')
    return jsonify({'message': 'hello'})

app.run()
```

クライアント側

``` python:request_url.py

import requests

response = requests.get('http://localhost:5000/hello')

print('httpステータス:{}, メッセージ:{}'.format(response.status_code, response.text))
```

実行

``` 
# python .\request_url.py
httpステータス:500, メッセージ:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>hello abort</p>
```

abort関数にhttpステータス500を指定してあげることでクライアント側にhttpステータスとメッセージが返却できました。
今までの方法と違い、flask内に定義されているhtmlに指定したメッセージを埋め込んでユーザに返却しています。
大抵は独自のエラー画面を持っているため、この方法は使用しないと思いますが、エラー画面を作成するのが手間な時は便利かもしれません。
さらに、どこからでもabortを呼べばユーザに返却されるため、上位の関数に影響を与えないのは利点だと思います。



## abortをpythonでキャッチする
上のabortの例だとflaskのデフォルトのhtmlが表示されました。
次はエラーハンドラーにabortを登録してメッセージを作り返して返却します。
Flaskクラスのerrorhandlerに対応するhttpステータスコードを入れればキャッチできます。
404をキャッチしたいときは```@app.errorhandler(404)```

サーバ側

``` python:flask_main.py
from flask import Flask, jsonify, abort
import json

app = Flask(__name__)

@app.route('/hello')
def hello():
    abort(500, 'hello abort')
    return jsonify({'message': 'hello'})

@app.errorhandler(500)
def error_500(e):
    print('httpステータス:{}, メッセージ:{}, 詳細:{}'.format(e.code, e.name, e.description))
    return jsonify({'message': 'internal server error', 'action': 'call me'}), 500

app.run()
```

クライアント側

``` python:request_url.py

import requests

response = requests.get('http://localhost:5000/hello')

print('httpステータス:{}, メッセージ:{}'.format(response.status_code, response.text))
```

実行

```
# python flask_main.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
httpステータス:500, メッセージ:Internal Server Error, 詳細:hello abort
```

``` 
# python .\request_url.py
httpステータス:500, メッセージ:{"action":"call me","message":"internal server error"}
```


エラーハンドラーにhttpステータスを入れることでabortをキャッチすることができました。
エラーハンドラーに登録した関数の引数のcodeにhttpステータス、nameにタイトル、descriptionにメッセージが入っていました。
これでabort時に独自のレスポンスを返却することができました。
abortは明示的にエラーだとわかる上に上位の関数に影響を与えないのでこの方法を使えばシンプルになるかと思います。


## exceptionをpythonでキャッチする
エラーハンドラーはhttpステータスだけでなく、エクセプションもキャッチすることができます。
Flaskクラスのerrorhandlerに対応するエクセプションを入れればキャッチできます。


サーバ側

``` python:flask_main.py
from flask import Flask, jsonify, abort
import json

app = Flask(__name__)

@app.route('/hello')
def hello():
    raise Exception('hello exception')
    return jsonify({'message': 'hello'})

@app.errorhandler(Exception)
def error_except(e):
    print('メッセージ:{}'.format(e.args))
    return jsonify({'message': 'Exception', 'action': 'call me'}), 500

app.run()
```

クライアント側

``` python:request_url.py

import requests

response = requests.get('http://localhost:5000/hello')

print('httpステータス:{}, メッセージ:{}'.format(response.status_code, response.text))
```

実行

```
# python flask_main.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
メッセージ:('hello exception',)
```

``` 
# python .\request_url.py
httpステータス:500, メッセージ:{"action":"call me","message":"Exception"}
```


エラーハンドラーにExceptionを入れることでExceptionをキャッチすることができました。
エラーハンドラーに登録した関数の引数のargsにメッセージが入っていました。
この方法を使えば、httpのことを意識せずにエンドポイントを作成することができます。


## 個人的に気に入っている方法
WEBの開発をする際にhttpステータスのことを理解せずに開発する人がいる状況が大いにあります。
その際に、エクセプションをエラーハンドラーに登録することでhttpの知識がなくても開発できるのでエクセプションの方法が気に入っています。
本来はエンドポイントの処理も別ファイルにしていますが、わかりやすさのためにここでは一つのファイルにしています。
エンドポイントの別ファイル化については[flaskのパスを指定する](https://qiita.com/mink0212/items/a4eb875f19b0e47718d3)を見てください。


### 利点
 - httpステータスを知らなくてもエクセプションの名前で大体のエラー内容がわかる。
 - pythonのエクセプションを別ファイルにまとめているため、httpのことを知らなくても作れる
 - httpのことを一つのファイルに閉じ込めているため、調査が容易
 - httpステータスで悩む人が減らせる



サーバ側

``` python:flask_main.py
from flask import Flask, jsonify, abort
from myexception import MyException, InputException, ServerException

import json

app = Flask(__name__)


@app.errorhandler(MyException)
def error_my_except(e):
    return jsonify({'message': e.message}), e.code


@app.route('/hello/<name>')
def hello(name):
    if name == 'me':
        raise InputException('input exception')
    if name == 'my':
        raise ServerException('server exception')

    return jsonify({'message': 'hello ' + name})

app.run()
```

``` python:myexception.py

class MyException(Exception):
    code = 0
    message = ''
    def __init__(self, code, message):
        self.code = code
        self.message = message

class ServerException(MyException):
    def __init__(self, message):
        super().__init__(500, message)
        
class InputException(MyException):
    def __init__(self, message):
        super().__init__(400, message)

```

## おわりに
httpステータスについて色々書いてきました。
httpステータスは人の解釈により値が変わってしまうので、なるべく一人の人が考えたほうが良いと思いこのような形になりました。
さらに、WEBとpythonで完全に分けて、WEB特化の人とpython特化の人で分担できたら理想だなと思っています。
何故かオールラウンダーになりがちな自分には特化のスキルを持つ人がとてもうらやましく思えてならないです。
オールラウンダーか特化かどっちがいいんだろうか・・・