Using Asyncio Libraries with txtorcon¶
It is possible to use Twisted’s asyncioreactor in order to use Twisted together with asyncio libraries. This comes with a couple caveats:
You need to install Twisted
Twisted “owns” the event-loop (i.e. you call
reactor.run()
);You need to convert Futures/co-routines to Deferred sometimes (Twisted provides the required machinery)
Here is an example using the aiohttp library as a Web server behind an Onion service that txtorcon has set up (in a newly-launched Tor process):
wanted: I can’t get this example to work properly with a Unix socket.
web_onion_service_aiohttp.py
¶
# This launches Tor and starts an Onion service using Twisted and
# txtorcon, and then starts a Web server using the aiohttp library.
#
# This style of interop between asyncio and Twisted requires twisted
# to use the "asyncioreactor" and for code to convert Futures/Tasks to
# Deferreds (most of which is already in Deferred)
#
# Thanks to Mark Williams for the inspiration, and this code:
# https://gist.github.com/markrwilliams/bffb9c293194d105169ea06f03484ba1
#
# note: if run in Python2, there are SyntaxErrors before we can tell
# the user nicely
import asyncio
from twisted.internet import asyncioreactor
# get our reactor installed as early as possible, in case other
# imports decide to import a reactor and we get the default
asyncioreactor.install(asyncio.get_event_loop())
from twisted.internet.task import react
from twisted.internet.defer import ensureDeferred, Deferred
from twisted.internet.endpoints import UNIXClientEndpoint
import txtorcon
try:
from aiohttp import web
except ImportError:
raise Exception(
"You need aiohttp to run this example:\n pip install aiohttp"
)
def as_future(d):
return d.asFuture(asyncio.get_event_loop())
def as_deferred(f):
return Deferred.fromFuture(asyncio.ensure_future(f))
def get_slash(request):
return web.Response(
text="I am an aiohttp Onion service\n",
)
def create_aio_application():
app = web.Application()
app.add_routes([
web.get('/', get_slash)
])
return app
async def _main(reactor):
if False:
print("launching tor")
tor = await txtorcon.launch(reactor, progress_updates=print)
else:
tor = await txtorcon.connect(
reactor,
UNIXClientEndpoint(reactor, "/var/run/tor/control"),
)
print("Connected to tor {}".format(tor.version))
# here, we've just chosen 1234 as the port. We have three other
# options:
# - select a random, unused one ourselves
# - put "ports=[80]" below, and find out which port txtorcon
# selected after
# - use a Unix-domain socket
# we create a Tor onion service on a specific local TCP port
print("Creating onion service")
onion = await tor.create_onion_service(
ports=[
(80, 1234) # 80 is the 'public' port, 1234 is local
],
private_key='RSA1024:MIICWwIBAAKBgQCmHEH1y7/RUUeeaSTgB3iQFfWMep38JDlAbDoEPltRxzgEh8bXMsNbemdiCuZmJVni96KrRh2/I2NwWi6C81xfcA8BjVzdCmEbL1B+KOeqZlrjoEMQl56NpbXIIzFZdyILaQtv3EZMoShNHSkta6e66oWUu2B2fkluwYyPxRAdvQIDAQABAoGAYkObHX2PlpK/jE1k3AZvYsUqwhSTOuJu39ZmJ7Z/rQvt7ngnv4wvFwF9APmzvD9iQir+FtXeqQCVRZSDqUGvpW0WgA+8aDA3BGWCZwKhWRWj18RLjsMX+wKP6OBpSIlNjELU8zc5PWWsCmT7AqAdVD7vqp2895LiP4M8vwwZB30CQQDb/fjoG1VWpFWXgjRHEYOoPj7d7J5FcRrbSgc57lvMv/2+4OVl2aRaGEjigfBnR7Pjbyxv/5K1h078PBWNumjPAkEAwUyN3SLJOMBM74LS2jh9AB/sNitLT7/O1f8zT0siC58TmTbeZsj3VqSsmrUiVSptQcOm+5F0UPvYxsI+B2UbswJAdV9dq8jZkS6AlCNd7QUFL4B2XkVedEJSR+mJTXlE9UsCARNQkTS7oW4PhPo633+8FH4+QUskZUHZ/G26OjHYtQJAIAKyd418LzbBRuSuUE8MfEnND0dqKGHGOfASKi5yC+SjFTtd5z2eoC2TG+elMN9eyoZBD+YNkh+yzW97YDQhOwJAKFKLmdlJve1lJah1ZllZfk2ipNeYVX+q1Mv7TE6IXGqU/Xt3HS8h9Zd8ml/Yms1z9X7hFIjQ/XcSiJhqcin8Vg==',
version=2, # FIXME use v3; using old tor for now
progress=print,
)
# we're now listening on some onion service URL and re-directing
# public port 80 requests to local TCP port 1234.
app = create_aio_application()
runner = web.AppRunner(app)
await as_deferred(runner.setup())
site = web.TCPSite(runner, 'localhost', 1234)
await as_deferred(site.start())
# now we're completely set up
print("Onion site on http://{}".format(onion.hostname))
await Deferred()
def main():
return react(
lambda reactor: ensureDeferred(
_main(reactor)
)
)
if __name__ == '__main__':
main()