31. Tornado¶
31.1 I/O asincrono no bloqueante.¶
Tornado utiliza un bucle en un único thread de eventos.
31.1.1 Bloqueante¶
Un función se bloquea cuando espera por algo: E/S redm disco, mutex, etc.
31.1.2 Asíncrono¶
Una función asíncrona retorna antes del final de la función e inicia algún tipo de trabajo en modo secundario antes de lanzar una acción futura en la apricación. Hay varios tipos de interfaces asíncronas: * Callback * Devolver un "placeholder" (Future, Promide, Deferred) * Poner en cola * Registro de callback (señales POSIX )
En general en TORNADO las operaciones asíncronas devuelven objetos "placeholder" (Futures) a excepción de ciertas llamadas de bajo nivel como IOLoop que usa "callback". Futures normalmente se transforman en su resultado al usar await o yield
Ejemplo de llamada SINCRONA:
from tornado.httpclient import HTTPClient
def synchronous_fetch(url):
http_client = HTTPClient()
response = http_client.fetch(url)
return response.body
from tornado.httpclient import AsyncHTTPClient
async def asynchronous_fetch(url):
http_client = AsyncHTTPClient()
response = await http_client.fetch(url)
return response.body
from tornado.concurrent import Future
def async_fetch_manual(url):
http_client = AsyncHTTPClient()
my_future = Future()
fetch_future = http_client.fetch(url)
def on_fetch(f):
my_future.set_result(f.result().body)
fetch_future.add_done_callback(on_fetch)
return my_future
31.2 CORRUTINAS¶
Se recomienda usar la notación de corrutinas de Python > 3.5 async def /await
31.2.1 Como funcionan las corrutinas¶
Una función que contiene awiat/yield es un generador.
los generadores son asíncronos. Cuando son llamados devuelven un objeto generador en lugar de acabar la función
Si usamos el decorador @gen.coroutine, el ciclo interno simplificado de la corrutina queda así:
# Simplified inner loop of tornado.gen.Runner
def run(self):
# send(x) makes the current yield return x.
# It returns when the next yield is reached
future = self.gen.send(self.next)
def callback(f):
self.next = f.result()
self.run()
future.add_done_callback(callback)
31.2.2 Como llamar a una corrutina¶
Las corrutinas no lanzan "excepciones" en la forma normal: Cualquier excepción lanzada,será capturada en objeto "awitable"
En casi todos los casos cualquier función que llame a una corrutina debe ser a la vez una corrutina usando await o yield
En alguna ocasión se necesita "lanzar y olvidar·una corrutina sin esperaar por el resultado. En este caso se recomienda usar IOLoop.spawn_callback que deja a IOLoop como responsable de la llamada. Si falla IOLoop registra en el log una traza:
# The IOLoop will catch the exception and print a stack trace in
# the logs. Note that this doesn't look like a normal call, since
# we pass the function object to be called by the IOLoop.
IOLoop.current().spawn_callback(divide, 1, 0)
Using IOLoop.spawn_callback in this way is recommended for functions using @gen.coroutine, but it is required for functions using async def (otherwise the coroutine runner will not start).
En el nivel superior de un programa, si no está ejecutando el IOLoop , se puede inciar el IOLoop, ejecutar la corrutina y parar el IOLoop, todo esto con la llamada al método IOLoop.run_syn
# run_sync() doesn't take arguments, so we must wrap the
# call in a lambda.
IOLoop.current().run_sync(lambda: divide(1, 0))
31.3 Estructura de las aplicaciones web TORNADO¶
Por lo general consiste en una o varias subclase de RequestHandler
además de un objeto Àplication
que dirige (route) las llamadas entrantes y un main()
-
Ejemplo:
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
31.3.1 El objeto "Applicatioin"¶
Es responsable de la configuración global y de mantener la tabla de rutas (request → handler).
La tabla de rutas es una lista ( de tuplas) objeto [URLSpec](https://www.tornadoweb.org/en/stable/guide/structure.html#:~:text=a%20list%20of-,URLSpec,-objects%20(or%20tuples) con una expresión regular un un handler.
El orden es importante y la primera expresión regular que coincida es la usada.
-- continuar ---
31.4 Framework Web (Referencia)¶
-- seguir --
31.4.1 Tornado websocket¶
Protocolo bidireccional websocket.
31.5 HTTP Servidores¶
31.5.1 tornado.httpserver¶
Servidor no bloqueante mono-hilo.
El servidor se crea definiendo una subclase de HTTPServerConnectionDelegate. El delegado normalmente es tornado.web.Application.
HTTPServer soporta conexiones "keep-alive" cuando el cliente realiza la solicitud Connection: keep-alive
...
La inicialización de HTTPServe sigue uno de los siguienes casos: 1. listen: para procesos simples:
server = HTTPServer(app)
server.listen(8888)
IOLoop.current().start()
server = HTTPServer(app)
server.bind(8888)
server.start(0) # Forks multiple sub-processes
IOLoop.current().start()
- add_sockets Multiproceso avanzado
sockets = tornado.netutil.bind_sockets(8888)
tornado.process.fork_processes(0)
server = HTTPServer(app)
server.add_sockets(sockets)
IOLoop.current().start()
31.6 tornado.httpclient¶
Interfaz de cliente bloqueante y no bloqueante -- seguir ---
31.7 Operaciones de red asíncronas¶
31.7.1 tornado.ioloop¶
Bucle principal de eventos. Desde la versión 6 es una envoltura (wrapper) el bucle de enventos de asyncio y se pueden usar indistintamente.
Normalmente las aplicaciones usan un único IOLoop obtenido a través del método IOLoop.current
The IOLoop.start method (or equivalently, asyncio.AbstractEventLoop.run_forever) should usually be called at the end of the main() function. Atypical applications may use more than one IOLoop, such as one IOLoop per thread, or per unittest case.
Ejemplo de un servidor TCP:
import errno
import functools
import socket
import tornado.ioloop
from tornado.iostream import IOStream
async def handle_connection(connection, address):
stream = IOStream(connection)
message = await stream.read_until_close()
print("message from client:", message.decode().strip())
def connection_ready(sock, fd, events):
while True:
try:
connection, address = sock.accept()
except BlockingIOError:
return
connection.setblocking(0)
io_loop = tornado.ioloop.IOLoop.current()
io_loop.spawn_callback(handle_connection, connection, address)
if __name__ == '__main__':
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setblocking(0)
sock.bind(("", 8888))
sock.listen(128)
io_loop = tornado.ioloop.IOLoop.current()
callback = functools.partial(connection_ready, sock)
io_loop.add_handler(sock.fileno(), callback, io_loop.READ)
io_loop.start()