Skip to content

Commit

Permalink
use new remote access service
Browse files Browse the repository at this point in the history
  • Loading branch information
wang0618 committed Oct 3, 2021
1 parent b0c1bf6 commit 797bc88
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 72 deletions.
5 changes: 2 additions & 3 deletions pywebio/platform/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,7 @@ def start_server(applications, port=0, host='', debug=False,

print('Listen on %s:%s' % (host, port))

if remote_access or remote_access == {}:
if remote_access is True: remote_access = {}
start_remote_access_service(**remote_access, local_port=port)
if remote_access:
start_remote_access_service(local_port=port)

web.run_app(app, host=host, port=port)
5 changes: 2 additions & 3 deletions pywebio/platform/django.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,8 @@ def start_server(applications, port=8080, host='', cdn=True,
session_cleanup_interval=session_cleanup_interval,
debug=debug, max_payload_size=max_payload_size, **django_options)

if remote_access or remote_access == {}:
if remote_access is True: remote_access = {}
start_remote_access_service(**remote_access, local_port=port)
if remote_access:
start_remote_access_service(local_port=port)

use_tornado_wsgi = os.environ.get('PYWEBIO_DJANGO_WITH_TORNADO', True)
if use_tornado_wsgi:
Expand Down
5 changes: 2 additions & 3 deletions pywebio/platform/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,8 @@ def start_server(applications, port=0, host='', cdn=True,
if port == 0:
port = get_free_port()

if remote_access or remote_access == {}:
if remote_access is True: remote_access = {}
start_remote_access_service(**remote_access, local_port=port)
if remote_access:
start_remote_access_service(local_port=port)

uvicorn.run(app, host=host, port=port, **uvicorn_settings)

Expand Down
5 changes: 2 additions & 3 deletions pywebio/platform/flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,8 @@ def start_server(applications, port=8080, host='', cdn=True,
if not debug:
logging.getLogger('werkzeug').setLevel(logging.WARNING)

if remote_access or remote_access == {}:
if remote_access is True: remote_access = {}
start_remote_access_service(**remote_access, local_port=port)
if remote_access:
start_remote_access_service(local_port=port)

has_coro_target = any(iscoroutinefunction(target) or isgeneratorfunction(target) for
target in make_applications(applications).values())
Expand Down
76 changes: 28 additions & 48 deletions pywebio/platform/remote_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,46 +32,27 @@
================================================================================
PyWebIO Application Remote Access
Remote access address: https://{address}
The remote access service is provided by localhost.run (https://localhost.run/).
The remote access address will be reset in every 6 hours and only one
application can enable remote access at the same time, if you use the free tier.
To set up and manage custom domains go to https://admin.localhost.run/
Remote access address: {address}
================================================================================
"""

ssh_key_gen_msg = """
===============================================================================
PyWebIO Application Remote Access Error
You need an SSH key to access the remote access service.
Please follow Gitlab's most excellent howto to generate an SSH key pair:
https://docs.gitlab.com/ee/ssh/
Note that only rsa and ed25519 keys are supported.
===============================================================================
"""

_ssh_process = None # type: Popen


def remote_access_service(local_port=8080, setup_timeout=60, key_path=None, custom_domain=None, need_exist=None):
def remote_access_service(local_port=8080, server='app.pywebio.online', server_port=1022, setup_timeout=60,
need_exit=None):
"""
:param local_port: ssh local listen port
:param server: ssh server domain
:param server_port: ssh server port
:param setup_timeout: If the service can't setup successfully in `setup_timeout` seconds, then exit.
:param key_path: Use a custom ssh key, the default key path is ~/.ssh/id_xxx. Note that only rsa and ed25519 keys are supported.
:param custom_domain: Use a custom domain for your remote access address. This need a subscription to localhost.run
:param callable need_exist: The service will call this function periodicity, when it return True, then exit the service.
:param callable need_exit: The service will call this function periodicity, when it return True, then exit the service.
"""

global _ssh_process

domain_part = '%s:' % custom_domain if custom_domain is not None else ''
key_path_arg = '-i %s' % key_path if key_path is not None else ''
cmd = "ssh %s -oStrictHostKeyChecking=no -R %s80:localhost:%s localhost.run -- --output json" % (
key_path_arg, domain_part, local_port)
cmd = "ssh -oStrictHostKeyChecking=no -R 80:localhost:%s -p %s %s -- --output json" % (
local_port, server_port, server)
args = shlex.split(cmd)
logger.debug('remote access service command: %s', cmd)

Expand All @@ -87,6 +68,7 @@ def timeout_killer(wait_sec):
threading.Thread(target=timeout_killer, kwargs=dict(wait_sec=setup_timeout), daemon=True).start()

stdout = _ssh_process.stdout.readline().decode('utf8')
logger.debug('ssh server stdout: %s', stdout)
connection_info = {}
try:
connection_info = json.loads(stdout)
Expand All @@ -103,7 +85,7 @@ def timeout_killer(wait_sec):
print(success_msg.format(address=connection_info['address']))

# wait ssh or parent process exit
while not need_exist() and _ssh_process.poll() is None:
while not need_exit() and _ssh_process.poll() is None:
time.sleep(1)

if _ssh_process.poll() is None: # parent process exit, kill ssh process
Expand All @@ -112,30 +94,21 @@ def timeout_killer(wait_sec):
else: # ssh process exit by itself or by timeout killer
stderr = _ssh_process.stderr.read().decode('utf8')
logger.debug("Stderr from ssh process: %s", stderr)
conn_id = re.search(r'connection id is (.*?),', stderr)
logger.debug('Remote access connection id: %s', conn_id.group(1) if conn_id else '')
try:
ssh_error_msg = stderr.rsplit('**', 1)[-1].rsplit('===', 1)[-1].lower().strip()
except Exception:
ssh_error_msg = stderr
if 'permission denied' in ssh_error_msg:
print(ssh_key_gen_msg)
elif ssh_error_msg:
print(ssh_error_msg)
if stderr:
print(stderr)
else:
print('PyWebIO application remote access service exit.')


def start_remote_access_service_(local_port, setup_timeout, ssh_key_path, custom_domain):
def start_remote_access_service_(**kwargs):
ppid = os.getppid()

def need_exist():
def need_exit():
# only for unix
return os.getppid() != ppid

try:
remote_access_service(local_port=local_port, setup_timeout=setup_timeout,
key_path=ssh_key_path, custom_domain=custom_domain, need_exist=need_exist)
remote_access_service(**kwargs, need_exit=need_exit)
except KeyboardInterrupt: # ignore KeyboardInterrupt
pass
finally:
Expand All @@ -145,8 +118,15 @@ def need_exist():
raise SystemExit


def start_remote_access_service(local_port=8080, setup_timeout=60, ssh_key_path=None, custom_domain=None):
multiprocessing.Process(target=start_remote_access_service_, kwargs=locals()).start()
def start_remote_access_service(**kwargs):
server = os.environ.get('PYWEBIO_REMOTE_ACCESS', 'app.pywebio.online:1022')
if ':' not in server:
server_port = 22
else:
server, server_port = server.split(':', 1)
kwargs.setdefault('server', server)
kwargs.setdefault('server_port', server_port)
multiprocessing.Process(target=start_remote_access_service_, kwargs=kwargs).start()


if __name__ == '__main__':
Expand All @@ -156,10 +136,10 @@ def start_remote_access_service(local_port=8080, setup_timeout=60, ssh_key_path=

parser = argparse.ArgumentParser(description="localhost.run Remote Access service")
parser.add_argument("--local-port", help="the local port to connect the tunnel to", type=int, default=8080)
parser.add_argument("--custom-domain", help="optionally connect a tunnel to a custom domain", default=None)
parser.add_argument("--key-path", help="custom SSH key path", default=None)
parser.add_argument("--server", help="the local port to connect the tunnel to", type=str,
default='app.pywebio.online')
parser.add_argument("--server-port", help="the local port to connect the tunnel to", type=int, default=1022)
args = parser.parse_args()

start_remote_access_service(local_port=args.local_port, ssh_key_path=args.key_path,
custom_domain=args.custom_domain)
start_remote_access_service(local_port=args.local_port, server=args.server, server_port=args.server_port)
os.wait() # Wait for completion of a child process
15 changes: 3 additions & 12 deletions pywebio/platform/tornado.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,17 +314,9 @@ def start_server(applications, port=0, host='',
The files in this directory can be accessed via ``http://<host>:<port>/static/files``.
For example, if there is a ``A/B.jpg`` file in ``http_static_dir`` path,
it can be accessed via ``http://<host>:<port>/static/A/B.jpg``.
:param bool/dict remote_access: Whether to enable remote access, when enabled,
:param bool remote_access: Whether to enable remote access, when enabled,
you can get a temporary public network access address for the current application,
others can access your application via this address.
Using remote access makes it easy to temporarily share the application with others.
The remote access service is provided by `localhost.run <https://localhost.run/>`_.
You can use a dict to config remote access service, the following configurations are currently supported:
- ``ssh_key_path``: Use a custom ssh key, the default key path is ``~/.ssh/id_xxx``. Note that only rsa and ed25519 keys are supported.
- ``custom_domain``: Use a custom domain for your remote access address. This need a subscription to localhost.run.
See also: `Custom Domains - localhost.run <https://localhost.run/docs/custom-domains/>`_
:param bool auto_open_webbrowser: Whether or not auto open web browser when server is started (if the operating system allows it) .
:param int reconnect_timeout: The client can reconnect to server within ``reconnect_timeout`` seconds after an unexpected disconnection.
If set to 0 (default), once the client disconnects, the server session will be closed.
Expand Down Expand Up @@ -372,9 +364,8 @@ def start_server(applications, port=0, host='',
if auto_open_webbrowser:
tornado.ioloop.IOLoop.current().spawn_callback(open_webbrowser_on_server_started, host or 'localhost', port)

if remote_access or remote_access == {}:
if remote_access is True: remote_access = {}
start_remote_access_service(**remote_access, local_port=port)
if remote_access:
start_remote_access_service(local_port=port)

tornado.ioloop.IOLoop.current().start()

Expand Down

0 comments on commit 797bc88

Please sign in to comment.