Werkzeugでレスポンスボディを後から差し替えてみた

Werkzeugでは werkzeug.wrappers.Response を使ってレスポンスオブジェクトを生成する際、 Response('Hello world') のようにコンストラクタの引数にレスポンスボディを渡します。

ただ、Responseオブジェクト生成後にレスポンスボディを修正する方法を知りたかったため、いろいろ試した時のメモです。

 
目次

 

環境

  • Python 3.6.6
  • Werkzeug 0.14.1

 

コンストラクタでレスポンスボディを指定

以下のWerkzeugアプリの中の index_handler() メソッドにて、コンストラクタでレスポンスボディを指定しています。

ルーティングにいろいろと書かれていますが、後から追加しますので、今は置いておきます。

from werkzeug.exceptions import HTTPException
from werkzeug.routing import Map, Rule
from werkzeug.wrappers import Request, Response


class Application:
    def __init__(self):
        self.url_map = Map([
            Rule('/', endpoint='index', methods=['GET']),
            Rule('/response', endpoint='response', methods=['GET']),
            Rule('/data', endpoint='data', methods=['GET']),
            Rule('/set_data', endpoint='set_data', methods=['GET']),
        ])

    def dispatch_request(self, request):
        adapter = self.url_map.bind_to_environ(request.environ)
        try:
            endpoint, values = adapter.match()
            return getattr(self, f'{endpoint}_handler')(request, **values)
        except HTTPException as e:
            return e

    def index_handler(self, request):
        return Response('Hello world')

    def wsgi_app(self, environ, start_response):
        request = Request(environ)
        response = self.dispatch_request(request)
        return response(environ, start_response)

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)


if __name__ == '__main__':
    from werkzeug.serving import run_simple
    app = Application()
    run_simple('0.0.0.0', 5000, app, use_debugger=True, use_reloader=True)

 
起動して localhost:5000/curlでアクセスすると

$ curl --include http://localhost:5000/
HTTP/1.0 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 12
Server: Werkzeug/0.14.1 Python/3.6.6
Date: Mon, 15 Oct 2018 13:19:56 GMT

Hello world

Hello worldが返ってきます。

 

response属性で差し替えるのはNG

Werkzeugのドキュメントを見ると response 属性があります。
http://werkzeug.pocoo.org/docs/0.14/wrappers/#werkzeug.wrappers.BaseResponse.response

これを

def response_handler(self, request):
    response = Response('Hello world\n')
    response.response = 'update response body\n'
    return response

のようにして使えば、一見レスポンスを差し替えられそうです。

ただ、実際には

$ curl --include http://localhost:5000/response
HTTP/1.0 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 12
Server: Werkzeug/0.14.1 Python/3.6.6
Date: Mon, 15 Oct 2018 13:22:44 GMT

update respo

と、Content-LengthヘッダがHello worldの時に計算した時のままなため、レスポンスボディが欠けています。

また、サーバのログを見ると

Warning: response iterable was set to a string.  This appears to work but means that the server will send the data to the client char, by char.  This is almost never intended behavior, use response.data to assign strings to the response object.
  _warn_if_string(self.response)

が残されており、response属性を使うのはダメそうです。

 

dataプロパティも不適切

次に、エラーメッセージにある data プロパティを使ってみます。

def data_handler(self, request):
    response = Response('Hello world\n')
    response.data = 'update response body\n'
    return response

 
curlでアクセスしてみます。

$ curl --include http://localhost:5000/data
HTTP/1.0 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 21
Server: Werkzeug/0.14.1 Python/3.6.6
Date: Mon, 15 Oct 2018 13:25:52 GMT

update response body

Content-Lengthが再計算され、レスポンスボディの欠けもありません。

また、サーバのログにも先ほどのエラーは出力されていません。

 
ただ、ドキュメントを見ると、

A descriptor that calls get_data() and set_data(). This should not be used and will eventually get deprecated.

http://werkzeug.pocoo.org/docs/0.14/wrappers/#werkzeug.wrappers.BaseResponse.data

とあるため、dataプロパティを使うのもよくなさそうです。

 

set_data() メソッドを使うのが良さそう

最後に set_data() メソッドを使います。

ドキュメントにも

Sets a new string as response. The value set must either by a unicode or bytestring. If a unicode string is set it’s encoded automatically to the charset of the response (utf-8 by default).

http://werkzeug.pocoo.org/docs/0.14/wrappers/#werkzeug.wrappers.BaseResponse.set_data

と書かれており、これを使うのが良さそうです。

def set_data_handler(self, request):
    response = Response('Hello world\n')
    response.set_data('update response body\n')
    return response

と実装します。

curlでアクセスしてみます。

$ curl --include http://localhost:5000/set_data
HTTP/1.0 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 21
Server: Werkzeug/0.14.1 Python/3.6.6
Date: Mon, 15 Oct 2018 13:30:48 GMT

update response body

とレスポンスが返り、サーバのログにもエラーが出ていませんでした。

 

ソースコード

GitHubに上げました。 response_body ディレクトリ以下が今回のコードです。
werkzeug-sample/response_body at master · thinkAmi-sandbox/werkzeug-sample