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) # return 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 (originally) this is run as a script, we will end up with # `__name__ == '__main__'` so to ensure the rpc request from the # client isn't blocked by `tractor.ModuleNotFound`, we want to just # use the explicit file-as-module name.. why u ask? this_mod: str = 'server' # WELP, when the `Portal.open_context()` api (used in # `client.py`) requests the RPC-ctx ep it will send # a `str`-like-ptr encoding the func-ref in form expected by # `pkgutil.resolve_name()`. # # Since the client's local namespace reference/path to this # `.server.py` mod will be from a direct manual import, that # `proxy_request()`-ref will render as `'server:proxy_request'` # (as delivered from `NamespacePath.from_ref()` since that's how # `.open_context()` serializes the func's-ref for IPC transit). # SO, we need to be sure we "enable" this module name so that the # nsp maps to an enabled module in the `Actor._mods: dict`. 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)