概要
前回、Google App EngineでFlaskを利用する際のNo Content(204)ステータスコードの利用方法を調査したけれど、原因がいまいち特定できていませんでした。
原因がわかりました!
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
> 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を起動します。
> flask run
curl
で確認してみます。
> 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
を変更するか削除してください。
> touch app.yaml
> gcloud app deploy
デプロイできたら確認してみます。
> 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
に違いがあることのがわかりました。
# 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
を追加します。
# 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
> 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にデプロイして確認します。
> 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"
おぅ。。。エラーが発生しなくなりましたぁぁぁ。
嫌がらせに以下のように変更して実行してみます。コンテンツを明示的に返すようにします。
# 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
> 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にデプロイして確認します。
> 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
を利用せず、以下のように実装することもできます。
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