From 61cd95d883bbcbe1ca0dd6deb3829b29f29cfeff Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Wed, 18 Sep 2024 22:47:59 -0400 Subject: [PATCH] Add a super naive multi-host-capable web-req proxier for @jc211 --- examples/multihost/client.py | 50 ++++++++++++++++++++++++ examples/multihost/server.py | 75 ++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 examples/multihost/client.py create mode 100644 examples/multihost/server.py diff --git a/examples/multihost/client.py b/examples/multihost/client.py new file mode 100644 index 0000000..888b100 --- /dev/null +++ b/examples/multihost/client.py @@ -0,0 +1,50 @@ +import tractor +import trio + + +log = tractor.log.get_console_log( + _root_name='my_app', + name='client', +) + + +async def client_main(): + + # enable console logging for our custom app's logger + tractor.log.get_console_log( + level='info', + _root_name='my_app', + name='client', + ) + + # presuming you can get a ref to the target server RPC-ctx func + from server import proxy_request + + async with ( + tractor.open_root_actor( + name='web_requester', + registry_addrs=[('127.0.0.1', 1616)], + enable_modules=[], # since this isn't a service actor + ), + + # use discovery api to find the server actor on your net + # (NOTE, in which case the below registry addr would have to + # be the public IP of that host!) + tractor.find_actor( + name='web_proxier', + registry_addrs=[('127.0.0.1', 1616)], + ) as portal, + + # open an RPC context with the remote actor, thus spawning + # a new task implemented as the function defined in the + # server code. + portal.open_context( + proxy_request, + address='https://github.com', + ) as (ctx, first), + ): + resp: dict = await ctx.result() + print(resp) + + +trio.run(client_main) diff --git a/examples/multihost/server.py b/examples/multihost/server.py new file mode 100644 index 0000000..8f43b58 --- /dev/null +++ b/examples/multihost/server.py @@ -0,0 +1,75 @@ +import httpx +import tractor +import trio + +log = tractor.log.get_console_log( + _root_name='my_app', + name='server_thingy', +) + + +@tractor.context +async def proxy_request( + ctx: tractor.Context, + address: str, +): + log.info( + 'Rxed client request\n' + f'{address}\n' + ) + async with httpx.AsyncClient() as client: + await ctx.started() # signal the remote task has started its client + log.info( + 'Opened `httpx` client..' + ) + + resp: httpx.Response = await client.get(address) # do the proxied request, get response. + log.info( + 'Got response..\n' + f'{resp}\n' + ) + + # only breaking this up to clarify that you didn't have to only return a single result you could have opened + # a long lived stream to avoid task spawning overhead in this service actor.. but more on that later.. + # + # NOTEs, cast to `str` here since we can't serialize the + # response type for the wire directly, at least no without + # a custom `msgspec.Decoder`!! + return str(resp) + # + # ^TODO, various typed msging options: + # -[ ] try returning just the `resp` verbatim => should raise + # an MTE + # -[ ] try defining a custom `Response` msg to proxy the orig + # types fields and/or a decoder to serialize it? + + +async def main(): + + # enable console logging for our custom app's logger + tractor.log.get_console_log( + level='info', + _root_name='my_app', + name='server_thingy', + ) + + # since run as a script this will likely be `__main__` + # so instead we want to just use our module name.. + this_mod: str = 'server' + async with tractor.open_root_actor( + name='web_proxier', + registry_addrs=[('127.0.0.1', 1616)], + enable_modules=[this_mod], + loglevel='info', + ): + # just block waiting for a peer actor to connect and open an RPC context using the above + # proxy endpoint. + log.info( + 'proxy server up bby!\n' + 'waiting to serve some requests..\n' + ) + await trio.sleep_forever() + + +if __name__ == '__main__': + trio.run(main)