Google App Engine上でFlaskを利用してNo Content(204)を返すとき、 ```Content-Length``` が0じゃないとエラーになる

## 概要

前回、Google App EngineでFlaskを利用する際のNo Content(204)ステータスコードの利用方法を調査したけれど、原因がいまいち特定できていませんでした。
原因がわかりました！:clap:

Google App Engine上のFlaskでレスポンスをNo Content(204)で返す方法を調べた
https://qiita.com/kai_kou/items/801ae9715b5b8f4736b8


## 原因

No Content(204)を返すとき、```Content-Length``` が```0``` じゃないと、GAEでgunicornプロセスが落ちる(白目


## 検証

前回の記事のソースを利用して検証します。

GitHubにもソースをアップしていますので、ご参考ください。
https://github.com/kai-kou/how-to-use-gae-no-content


```sh:環境設定
> git clone https://github.com/kai-kou/how-to-use-gae-no-content.git
> cd how-to-use-gae-no-content
> python -m venv venv
> . venv/bin/activate
> pip install -r requirements.txt
```

環境が用意できたらflaskを起動します。

```sh
> flask run
```

```curl``` で確認してみます。

```sh:flask_runしていないコンソール
> curl 127.0.0.1:5000/good_no_content -i
HTTP/1.0 204 NO CONTENT
Content-Type: application/json
Content-Length: 0
Server: Werkzeug/0.14.1 Python/3.6.6

> curl 127.0.0.1:5000/bad_no_content -i
HTTP/1.0 204 NO CONTENT
Content-Type: application/json
Content-Length: 5
Server: Werkzeug/0.14.1 Python/3.6.6

```

はい。GAEにもデプロイしています。
GitHubからソース取得している場合、```app.yaml``` の```service``` を変更するか削除してください。

```sh
> touch app.yaml
> gcloud app deploy
```

デプロイできたら確認してみます。

```sh
> curl https://[GAEのサービス名]-dot-[GCPのプロジェクトID].appspot.com/good_no_content -i
HTTP/2 204
content-type: application/json
x-cloud-trace-context: 436098c2c9196bd34b10448cb57643b0;o=1
server: Google Frontend
alt-svc: quic=":443"; ma=2592000; v="44,43,39,35"

> curl https://[GAEのサービス名]-dot-[GCPのプロジェクトID].appspot.com/bad_no_content -i
HTTP/2 500
x-cloud-trace-context: 0f98972aa581c03afa7af2d39596b8af;o=1
content-type: text/html; charset=UTF-8
server: Google Frontend
content-length: 323
alt-svc: quic=":443"; ma=2592000; v="44,43,39,35"
(略)

```

はい。```bad_no_content``` はやはりエラーになります。

前回はHTTPステータスコードと```Content-Type``` しかみてなかったのですが、ローカル実行時の```Content-Length``` に違いがあることのがわかりました。


```sh
# Good
Content-Length: 0

# Bad
Content-Length: 5
```

そのへんから情報を漁っていると、Flask-RESTfulというライブラリのGitHubにそれらしきIssueがありました。。。

204 status returns non-zero content-length
https://github.com/flask-restful/flask-restful/issues/736

試しに、だめな方の実装を変更してみます。```headers``` に```Content-Length``` を追加します。

```python:app.py(一部)
# GAEでエラーになる
@app.route('/bad_no_content', methods=['GET'])
def bad_no_content():
  response = make_response(jsonify(None), 204)
  response.headers['Content-Length'] = 0
  return response

```

```sh
> flask run
```

```sh:flask_runしていないコンソール
> curl 127.0.0.1:5000/bad_no_content -i
HTTP/1.0 204 NO CONTENT
Content-Type: application/json
Content-Length: 0
Server: Werkzeug/0.14.1 Python/3.6.6
```

GAEにデプロイして確認します。

```sh
> gcloud app deploy

> curl https://[GAEのサービス名]-dot-[GCPのプロジェクトID].appspot.com/bad_no_content -i
HTTP/2 204
content-type: application/json
x-cloud-trace-context: b26c9b04d914cbc0e38de16f8d7fe28f;o=1
server: Google Frontend
alt-svc: quic=":443"; ma=2592000; v="44,43,39,35"
```

おぅ。。。エラーが発生しなくなりましたぁぁぁ。

嫌がらせに以下のように変更して実行してみます。コンテンツを明示的に返すようにします。


```python:app.py(一部)
# GAEでエラーになる
@app.route('/bad_no_content', methods=['GET'])
def bad_no_content():
  # コンテンツを設定してみる
  response = make_response(jsonify({'message':'hoge'}), 204)
  response..headers['Content-Length'] = 0
  return response

```

```sh
> flask run
```

```sh:flask_runしていないコンソール
> curl 127.0.0.1:5000/bad_no_content -i
HTTP/1.0 204 NO CONTENT
Content-Type: application/json
Content-Length: 0
Server: Werkzeug/0.14.1 Python/3.6.6

```

GAEにデプロイして確認します。

```sh
> gcloud app deploy

> curl https://[GAEのサービス名]-dot-[GCPのプロジェクトID].appspot.com/bad_no_content -i
HTTP/2 204
content-type: application/json
x-cloud-trace-context: 27edb7f43586aec504497cc61b46be4d
date: Tue, 30 Oct 2018 08:48:21 GMT
server: Google Frontend
alt-svc: quic=":443"; ma=2592000; v="44,43,39,35"
```

ヘッダー優先みたいですね。

ついでに```''``` と```None``` とでレスポンス内容の際をみてみました。
```jsonify``` を利用すると改行が入ってしまう模様。ｲﾗﾅｲﾖｫ
```None``` は```null``` に変換されます。

|レスポンス実装|レスポンス内容|
|---|---|
|make_response('', 204).data|b''|
|make_response(None, 204).data|エラー|
|make_response(jsonify(''), 204).data|b'""\n'|
|make_response(jsonify(None), 204).data|b'null\n'|


### make_responseが面倒だ！

```make_response``` を利用せず、以下のように実装することもできます。


```python
headers = {
  'Content-Type': app.config['JSONIFY_MIMETYPE'],
  'Content-Length': 0
}
return '', 204, headers
```


## まとめ

**No Content(204)を返すとき、```Content-Length``` が```0``` じゃないと、GAEでgunicornプロセスが落ちるから気をつけましょう**(再掲


前回は原因不明のまま、回避策だけを見出して終わったのですが、今回、なんとか原因がわかって、モヤモヤが晴れました^^

こういった問題が起こり得るので、Dockerイメージでほぼ同一環境！最強！ヒャッハーと油断しているといざクラウド環境に上げてから、痛い目に合いそうで(実際合った)怖いですね。

早めに運用環境で検証しておくのに越したことはないですね。教訓！


## 参考

204 status returns non-zero content-length
https://github.com/flask-restful/flask-restful/issues/736