今回は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 "こんにちは"
response
オブジェクトの属性を変更することで指定を任意のキャラクターセットに変更することができます。
from bottle import response @route("/") def index(): response.content_type = 'text/html; charset=shift-jis' return 'こんにちは'
ファイルの強制ダウンロード
static_file
を使用して静的ファイルを戻す場合、Bottleが自動でそのファイルに適したMime-Type
を指定してくれます。そのため、画像なども正しくブラウザ内で開かれます。ただ、場合によっては相手にダウンロードさせたい場合もあります。そのようなときはstatic_file
のdownload
を指定します。
@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でもファイルにアクセスできます。
http://host:port/static/material/sample.log
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
なかなかの懐の深さを感じますね。