Flask (Werkzeug)のResponseのmimetypeとcontent_typeの違い

Flask で HTML ではないデータを返送する場合、通常は HTML 応答に Content-Type ヘッダーを設定することになる。そのためには Response オブジェクトの mimetype プロパティや content_type プロパティに値を設定すれば良いのだけれど、これまでどちらに値を設定しても同じだと思っていた。が、実は動作の違いがあることに気が付いたので、調べてみた。

Flask もとい Werkzeug の Response オブジェクトでは、content_type プロパティが Content-Type ヘッダーの値そのものであるのに対して、mimetype プロパティは「Content-Type ヘッダー値のうち MIME-Type の部分」を表している。つまり Content-Type が text/html; charset=utf-8 であれば、mimetype プロパティは text/html の部分に対応している。

これらのプロパティを読み出す場合は上記の理解だけで何ら問題無いけれど、値を「設定する」場合は少しだけ注意が必要だ。というのも content_type プロパティはヘッダー値の全体が置き換わるのに対し、mimetype プロパティに値を設定すると MIME Type 部分だけが置き換わるわけではなく、「全体を書き換えつつ、ただし設定した値が text/*** だった場合は charset パラメータが自動的に追加される」という動きをする:

Special note for mimetype and content_type: For most mime types mimetype and content_type work the same, the difference affects only ‘text’ mimetypes. If the mimetype passed with mimetype is a mimetype starting with text/, the charset parameter of the response object is appended to it. In contrast the content_type parameter is always added as header unmodified.
Request / Response Objects — Werkzeug Documentation (0.15.x)

試してみると、確かにそのような動作になっているね:

>>> import flask
>>> resp = flask.Response("Hello")
>>> resp.headers
Headers([('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', '5')])
>>>
>>> # mimetypeへの設定時はcharsetパラメータが自動補完される
>>> resp.mimetype = "text/css"
>>> resp.headers
Headers([('Content-Type', 'text/css; charset=utf-8'), ('Content-Length', '5')])
>>>
>>> # "text/"でなければ補完されない
>>> resp.mimetype = "application/json"
>>> resp.headers
Headers([('Content-Type', 'application/json'), ('Content-Length', '5')])
>>>
>>> # content_typeプロパティの場合は問答無用で値全体が置き換わる
>>> resp.content_type = "text/css"
>>> resp.headers
Headers([('Content-Type', 'text/css'), ('Content-Length', '5')])
>>>
>>> # charset付きの値を設定すれば同じことができる
>>> resp.content_type = "text/css; charset=utf-8"  
>>> resp.headers
Headers([('Content-Type', 'text/css; charset=utf-8'), ('Content-Length', '5')])

どちらが良いかは好みの問題という気はする。ただ、違いがあることは記憶にとどめておいた方が良さそうだね。