[Dd]enzow(ill)? with DB and Python

DBとか資格とかPythonとかの話をつらつらと

Pythonで一番小さいWEBフレームワークbottle.py その5(response,mimetype)

今回はGENERATING CONTENTを中心にまとめます。

純粋なwsgiはファンクションがiterableなbyte stringを戻すように定められています。厳密にはPythonで使用されるstr(Unicode)も戻すことはできませんが、Bottleではそういった戻り値をいいかんじに変換してくれるのであまり意識せずに使うことができます。

bottleが許すデータ型

Bottleは関数が戻すデータ型をwsgiで扱えるように変換してくれます。

データ型 変換後 補足
ディクショナリ 自動でJSON文字列に変換される Content-typeが"application/json"ブラウザは正しくJSONとして受け取れる
Falseや空文字等 長さ0のコンテンツとして戻す からのページが表示される
Unicode文字列 UTF-8をエンコードに指定したByte文字列
Fileオブジェクト Content-Length or Content-Typeは自動でセットされない ファイルにかぎらず.read()を実装するもの等も対象

エンコーディング

以下のようにただ文字列を戻す場合、Bottleではデフォルトでutf-8をエンコードに指定しています。

@route("/")
def index():
    return "こんにちは"

f:id:denzow:20170927230954p:plain
ヘッダー1

responseオブジェクトの属性を変更することで指定を任意のキャラクターセットに変更することができます。

from bottle import response

@route("/")
def index():
    response.content_type = 'text/html; charset=shift-jis'
    return 'こんにちは'

f:id:denzow:20170927230959p:plain
ヘッダー2

ファイルの強制ダウンロード

static_fileを使用して静的ファイルを戻す場合、Bottleが自動でそのファイルに適したMime-Typeを指定してくれます。そのため、画像なども正しくブラウザ内で開かれます。ただ、場合によっては相手にダウンロードさせたい場合もあります。そのようなときはstatic_filedownloadを指定します。

@route('/download/<filename:path>')
def download(filename):
    return static_file(filename, root='/path/to/static/files', download=filename)

この場合、/download/xxxxでアクセスしたファイルはブラウザで開くのではなくダウンロードされます。例えば以下のコードを考えます。

@route('/static/<file_path:path>')
def server_static(file_path):
    return static_file(file_path, root='./static/')


@route('/download/<file_path:path>')
def download_static(file_path):
    import os
    # ファイル名を取得し、ダウンロード時の指定に使っている
    file_name = os.path.basename(file_path)
    return static_file(file_path, root='./static/', download="DL_" + file_name)

そして、./static/material/sample.logといテキストファイルが有る場合は以下のどちらのURLでもファイルにアクセスできます。

  1. http://host:port/static/material/sample.log
  2. http://host:port/download/material/sample.log

1.はブラウザでそのままテキストが表示されますが、2.はDL_sample.logという名前でファイルがそのままダウンロードされます。2.の場合はHTTPヘッダにContent-Disposition:attachment; filename="DL_sample.log"がBottleによって追加されるため指定したファイル名でダウンロードさせることができるわけです。

なお、download=Trueにした場合はファイル名でそのままダウンロードされます。Bottleの以下の部分の動きですね。

def static_file(filename, root,
                mimetype=True,
                download=False,
                charset='UTF-8',
                etag=None):
:
    filename = os.path.abspath(os.path.join(root, filename.strip('/\\')))
:
    if download:
        # Trueを渡せばファイル名自体、True Likeな値を渡せばそれがダウンロード時に指定される
        download = os.path.basename(filename if download is True else download)
        headers['Content-Disposition'] = 'attachment; filename="%s"' % download 

なかなかの懐の深さを感じますね。