JSONやHTMLを返す

この章はまだ書き途中です。気が向いたときに書き直していきますがこの資料の感想をいただけると頑張るかもしれません

JSONResponseクラスを用意する

Responseオブジェクトは文字列を返すのには非常に向いていました。 実際Webアプリケーションを開発する中ではこのように文字列を返すことはあまりなく、 Webブラウザで表示するためのHTMLを返したり、クライアントアプリケーションのためにJSON形式のAPIを用意することのほうが多くあります。 まずはJSON APIの開発に便利な JSONResponse クラスを用意してみましょう。

class JSONResponse(Response):
    default_content_type = 'text/json; charset=UTF-8'

    def __init__(self, dic, status=200, headers=None, charset=None, **dump_args):
        self.dic = dic
        self.json_dump_args = dump_args
        super().__init__('', status=status, headers=headers, charset=charset)

    @property
    def body(self):
        return [json.dumps(self.dic, **self.json_dump_args).encode(self.charset)]

Jinja2を使ってHTMLを返す

BottleやDjangoのようなフレームワークでは自前でテンプレートエンジンを用意していますが、 今回は、デファクトスタンダードとなっているJinja2を使ってHTMLを返していきましょう。

Jinja2 おさらい

Jinja2の使い方をおさらいしてみましょう.

>>> import os
>>> from jinja2 import Environment, FileSystemLoader
>>>
>>> templates = [os.path.join(os.path.abspath('.'), 'templates')]
>>> env = Environment(loader=FileSystemLoader(templates))
>>> template = env.get_tempplate('users.html')
>>> template.render(title='Hello World', users=['user1', 'users2'])

TemplateResponse クラス

それではこれを簡単に扱えるようなResponseクラスを用意します。

class TemplateResponse(Response):
    default_content_type = 'text/html; charset=UTF-8'

    def __init__(self, filename, status='200 OK', headers=None, charset='utf-8', **tpl_args):
        self.filename = filename
        self.tpl_args = tpl_args
        super().__init__(body='', status=status, headers=headers, charset=charset)

    def render_body(self, jinja2_environment):
        template = jinja2_environment.get_template(self.filename)
        return template.render(**self.tpl_args).encode(self.charset)

render_bodyを呼び出す際に、environmentを渡す必要があるため、つぎのようにAppクラスを書き換えましょう.

from jinja2 import Environment, FileSystemLoader

class App:
    def __init__(self, templates=None):
        self.router = Router()
        if templates is None:
            templates = [os.path.join(os.path.abspath('.'), 'templates')]
        self.jinja2_environment = Environment(loader=FileSystemLoader(templates))

    ...

    def __call__(self, env, start_response):
        method = env['REQUEST_METHOD'].upper()
        path = env['PATH_INFO'] or '/'
        callback, kwargs = self.router.match(method, path)

        response = callback(Request(env), **kwargs)
        start_response(response.status, response.header_list)
        if isinstance(response, TemplateResponse):
            return [response.render_body(self.jinja2_environment)]
        return [response.body]

これで実装はOKです。使ってみましょう。

main.py

@app.route('^/user/$', 'GET')
def users(request):
    users = ['user%s' % i for i in range(10)]
    return TemplateResponse('users.html', title='User List', users=users)

templates/users.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
</head>
<body>
    <ul>
        {% for user in users %}
        <li>{{ user }}</li>
        {% endfor %}
    </ul>
</body>
</html>