123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- """
- * Implementation of remote access
- Use https://github.com/wang0618/localshare service by running a ssh subprocess in PyWebIO application.
- The stdout of ssh process is the connection info.
- """
- import json
- import logging
- import os
- import threading
- import time
- import shlex
- from subprocess import Popen, PIPE
- import shutil
- logger = logging.getLogger(__name__)
- success_msg = """
- ================================================================================
- PyWebIO Application Remote Access
- Remote access address: {address}
- ================================================================================
- """
- _ssh_process = None # type: Popen
- def am_i_the_only_thread():
- """Whether the current thread is the only non-Daemon threads in the process"""
- alive_none_daemonic_thread_cnt = sum(
- 1 for t in threading.enumerate()
- if t.is_alive() and not t.isDaemon()
- )
- return alive_none_daemonic_thread_cnt == 1
- def remote_access_service(local_port=8080, server='app.pywebio.online', server_port=1022, setup_timeout=60):
- """
- Wait at most one minute to get the ssh output, if it gets a normal out, the connection is successfully established.
- Otherwise report error and kill ssh process.
- :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.
- """
- global _ssh_process
- cmd = "ssh -oStrictHostKeyChecking=no -R 80:127.0.0.1:%s -p %s %s -- --output json" % (
- local_port, server_port, server)
- args = shlex.split(cmd)
- logger.debug('remote access service command: %s', cmd)
- _ssh_process = Popen(args, stdout=PIPE, stderr=PIPE)
- logger.debug('remote access process pid: %s', _ssh_process.pid)
- success = False
- def timeout_killer(wait_sec):
- time.sleep(wait_sec)
- if not success and _ssh_process.poll() is None:
- _ssh_process.kill()
- 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)
- success = True
- except json.decoder.JSONDecodeError:
- if not success and _ssh_process.poll() is None:
- _ssh_process.kill()
- if success:
- if connection_info.get('status', 'fail') != 'success':
- print("Failed to establish remote access, this is the error message from service provider:",
- connection_info.get('message', ''))
- else:
- print(success_msg.format(address=connection_info['address']))
- # wait ssh or main thread exit
- while not am_i_the_only_thread() and _ssh_process.poll() is None:
- time.sleep(1)
- if _ssh_process.poll() is None: # main thread exit, kill ssh process
- logger.debug('App process exit, killing ssh process')
- _ssh_process.kill()
- else: # ssh process exit by itself or by timeout killer
- stderr = _ssh_process.stderr.read().decode('utf8')
- if stderr:
- logger.error('PyWebIO application remote access service error: %s', stderr)
- else:
- logger.info('PyWebIO application remote access service exit.')
- def start_remote_access_service_(**kwargs):
- try:
- remote_access_service(**kwargs)
- except KeyboardInterrupt: # ignore KeyboardInterrupt
- pass
- finally:
- if _ssh_process:
- logger.debug('Exception occurred, killing ssh process')
- _ssh_process.kill()
- raise SystemExit
- def start_remote_access_service(**kwargs):
- if not shutil.which("ssh"):
- logging.error("No ssh client found, remote access service can't start.")
- return
- 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)
- thread = threading.Thread(target=start_remote_access_service_, kwargs=kwargs)
- thread.start()
- return thread
- if __name__ == '__main__':
- import argparse
- logging.basicConfig(level=logging.DEBUG)
- parser = argparse.ArgumentParser(description="Remote Access service")
- parser.add_argument("--local-port", help="the local port to connect the tunnel to", type=int, default=8080)
- 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()
- t = start_remote_access_service(local_port=args.local_port, server=args.server, server_port=args.server_port)
- t.join()
|