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).