Python Tornado

Tornado is a Python networking library used for building webservices and APIs.

My Uses

It is currently (as of Jan 2024) used in the wake/ hibernate web page, where it uses the secure cookie support to provide a simple login mechanism.

Tornado as Web Server

Tornado as router and template engine:

import asyncio
import tornado.web, tornado.template

class WidgetGen:
    ...

def make_app():
    return tornado.web.Application([
        (r"/", WidgetGen),
    ], autoreload=True)

async def main():
    app = make_app()
    app.listen(8888)
    tornado.autoreload.watch('widget.html')
    await asyncio.Event().wait()

if __name__ == "__main__":
    asyncio.run(main())

This creates a web server on port 8888 which will server the route /. When GET / is served, it will call methods in the WidgetGen class named after the HTTP method, ie.:

class WidgetGen(tornado.web.RequestHandler):
    def get(self):
        myip = self.getMyIP()
        self.render("widget.html", myip=myip, buttons=self.getButtons())

    def getMyIP(self):
        myip = self.request.remote_ip
        return f"{myip}"

widget.html contains the HTML page with Jinja2 templating, which uses moustache style embedding {{ var }} and embedded (modified) python inside {% code %} tags.

<!doctype html>
<head>
    <title>Demo Widget</title>
</head>
<body>
<h1>{{ myip }}</h1>
<div id="bcBtnWidget">
{% for btn in buttons %}
<button type="button" id="bcbtn-{{ btn['host'] }}" class="bcbtns {{ btn['action'] }}" onclick="event.preventDefault(); sendAction('{{ btn['action'] }}', '{{ btn['host'] }};');" {{ btn['state'] }}>
    <span class="bcbtns command {{ btn['action'] }}">{{ btn['action'] }}</span><br /><span class="bcbtns host">{{ btn['host'] }}</span>
</button>
{% end %}
</div>
</body>

Secure Cookies

NB there is some confusion whether the method names are get/set_signed_cookie or get/set_secure_cookie.

A symetric crypto key is used to encode cookie contents. The key is defined in the call to instantiate tornado.web.Application, e.g.

    return tornado.web.Application([
        (r"/", WidgetGen),
    ], autoreload=True, cookie_secret="do not tell anyone")

Now cookies can be sent/ checked using:

self.set_secure_cookie("MYCOOKIE", "cookie-calue, expires_days=1)

cookieValue = self.get_secure_cookie("MYCOOKIE",b"default if not set").decode()

Setting expiry_days=0, does delete the cookie (tested on Chrome).