Programación asíncrona: alternativas
Gevent
1. Introducción
La librería Gevent permite crear en Python subrutinas para gestionar de manera eficaz los problemas de red. Se basa en el uso de greenlet y un bucle orientado a eventos con buen rendimiento (libev o libuv). La instalación se hace de la siguiente manera:
$ pip install gevent
La librería contiene un objeto socket que trabaja de manera concurrente y algunas funciones útiles, como se muestra en este ejemplo adaptado de la documentación oficial:
>>> from gevent import socket, spawn, joinall
>>> urls = ["eni.fr", "www.inspyration.fr", "www.python.org"]
>>> jobs = [spawn(socket.gethostbyname, url) for url in urls]
>>> joinall(jobs, timeout=10)
>>> [job.value for job in jobs]
['185.42.28.200', '213.246.53.26', '151.101.40.223']
En este ejemplo, vamos a pedir la IP asociada a un nombre de host y vamos a crear varias tareas para cada host y ponerlas de forma concurrente.
La función joinall permite esperar a que todas las tareas hayan terminado. La última línea permite recuperar el resultado de cada tarea.
2. DNS
Gevent incluye las herramientas necesarias para la resolución de nombre de dominio, como muestra el ejemplo anterior. Se trata de una reescritura iso-funcional parcial de las funcionalidades presentes en el módulo python socket. Se puede consultar la documentación oficial para saber cómo se deben utilizar estas funciones y lo que devuelven.
A continuación, se muestra la lista de estas funciones:
-
get_hostbyname
-
recibe como argumento...
Twisted
1. Introducción
Twisted es una herramienta que permite crear un servidor internet y no solamente un servidor web. En otras palabras, no solo administra el protocolo HTTP, sino que puede hacer WebDav, FTP o implementar sus propios protocolos.
Para instalarla:
$ pip install twisted
El objeto central es un reactor y este último va a hacer funcionar los protocolos, que son descripciones de los datos esperados y las respuestas que se van a proporcionar. Estos protocolos se construyen sobre los transportes, tales como el transporte TCP o UDP.
2. Cliente/servidor TCP
Aquí tenemos un ejemplo de servidor TCP básico: devuelve un eco (echo) del dato que recibe. Vamos a empezar importando lo que necesitamos:
from twisted.internet import reactor
from twisted.internet.protocol import Protocol, ServerFactory
A continuación, se muestra un ejemplo sencillo de protocolo que permite devolver el dato recibido:
class Echo(Protocol):
def dataReceived(self, data):
"As soon as any data is received, echo it back."
self.transport.write(data)
Es suficiente con conocer las diferentes funciones por sobrecargar, para crear un protocolo propio y hacer lo que queremos.
Para que funcione este protocolo, hay que montar el reactor:
def main():
"""This runs the protocol on port 8000"""
factory = ServerFactory()
factory.protocol = Echo
reactor.listenTCP(8000,factory)
reactor.run()
Este código le debería resultar muy familiar, si ya se ha leído el capítulo Programación de red o Programación web.
Después hay que ejecutarlo:
if __name__ == '__main__':
main()
Para la parte cliente, se escribe también un protocolo:
class EchoClient(protocol.Protocol):
def connectionMade(self):
self.transport.write(b"hello, world!")
def dataReceived(self, data):
print("Server said:", data)
...
Trollius
Como sabemos, asyncio está disponible solamente para Python 3.4 o posterior. sin embargo, existe una alternativa para Python 2.7, cuya rama siempre está activa y lo estará incluso algunos años.
Como sucede con asyncio, trollius ofrece un bucle orientado a eventos, así como los transportes y protocolos entre los que se puede encontrar TCP, UDP o SSL como ya lo hemos presentado en el capítulo Programación asíncrona: avanzada. Sin embargo, atención, estos protocolos se basan en los de twisted, más que en los de asyncio. Es uno de los motivos por el que hemos introducido twisted justo antes.
También, este módulo permite de forma más básica, crear subrutinas y tareas y sincronizarlas.
A continuación, se muestra un ejemplo de protocolo UDP (que se parece mucho a lo que hemos visto anteriormente):
class ServerUdpEchoProtocol:
def connection_made(self, transport):
print('{transport} started'.format(transport=transport))
self.transport = transport
def datagram_received(self, data, addr):
print('Data {data} received from {addr}'.format(data=data,
addr=addr))
...
HTTP asíncrono con aiohttp
1. Lado servidor
Ya se ha presentado bottlepy y mencionado la alternativa flask. Se trata de microframeworks web, que permiten responder a necesidades sencillas.
Hay una alternativa que sintácticamente es muy cercana y que presenta la ventaja de utilizar la programación asíncrona.
He aquí un ejemplo de introducción de aiohttp:
from aiohttp.web import RouteTableDef, Response , Application,
run_app
routes = RouteTableDef()
@routes.get('/')
async def hello(request):
return Response(text="Hello World!")
app = Application()
app.add_routes(routes)
run_app(app)
Vamos a compararlo con un ejemplo similar de bottlepy:
from bottle import route, run, template
@route('/')
def index():
return "Hello World!"
run(host="localhost", port=8080)
o Flask:
from flask import Flask
app = Flask("test App")
@app.route("/")
def hello():
return "Hello World!"
Con aiohttp, los métodos se pueden escribir usando la programación asíncrona:
from aiohttp.web import json_response
async def test(self, request): ...