WIP: Create examples/multihost/
script set to show some distributed cases! #3
|
@ -0,0 +1,63 @@
|
||||||
|
import tractor
|
||||||
|
import trio
|
||||||
|
|
||||||
|
|
||||||
|
log = tractor.log.get_console_log(
|
||||||
|
_root_name='my_app',
|
||||||
|
name='client',
|
||||||
|
)
|
||||||
|
_loglevel: str = 'cancel'
|
||||||
|
|
||||||
|
|
||||||
|
async def client_main():
|
||||||
|
|
||||||
|
# enable console logging for our custom app's logger
|
||||||
|
tractor.log.get_console_log(
|
||||||
|
level=_loglevel,
|
||||||
|
_root_name='my_app',
|
||||||
|
name='client',
|
||||||
|
)
|
||||||
|
|
||||||
|
# presuming you can get a ref to the target server RPC-ctx func,
|
||||||
|
# pass it directly as our rpc-ctx endpoint below.
|
||||||
|
from server import proxy_request
|
||||||
|
#
|
||||||
|
# NOTE, see he equiv note in `server.py` explaining why this will
|
||||||
|
# render more or less to `'server:proxy_request'` according to
|
||||||
|
# `tractor.msg.NamespacePath.from_ref(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
|
||||||
|
loglevel=_loglevel,
|
||||||
|
),
|
||||||
|
|
||||||
|
# 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,
|
||||||
|
|
||||||
|
tractor.wait_for_actor(
|
||||||
|
name='web_proxier',
|
||||||
|
registry_addr=('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)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
trio.run(client_main)
|
|
@ -0,0 +1,91 @@
|
||||||
|
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)
|
Loading…
Reference in New Issue