def start(self): """ запустить дочерний процесс консоли self.env - переоперделенные переменные окружения read_output_thread - поток читатель из std_out check_idle_thread - поток, проверяет активное ли соединение, JavaScript делает переодически запросы к консоли для получить новые буквы ответа, и если давно небыло опроса, то пора выходить """ self.process = subprocess.Popen( 'cmd.exe', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env, bufsize=1, cwd=self.physical_path ) self.stdout_reader = OutputReader(self.process.stdout) self.stderr_reader = OutputReader(self.process.stderr) read_output_thread = Thread(target=self.read_output) read_output_thread.daemon = True read_output_thread.start() check_idle_thread = Thread(target=self.check_idle_thread) check_idle_thread.daemon = True check_idle_thread.start()
class WebConsole(object): """ реализация веб-консоли 1 экземпляра после запуска проверяет активно ли соединение с клиентом, если он не опрашивал консоль в течении IDLE_INTERVAL, то надо все закрыть """ _consoles = {} # активные консоли. для разных узлов в дереве IDLE_INTERVAL = 30 @classmethod def get_console(cls, path): """ получить консоль для указанного пути. если нет - None :param path: :return: """ return cls._consoles.get(path, None) @classmethod def create_console(cls, path): """ если нет в активных. то создать WebConsole и добавить в активные, по указанному пути :param path: :return: """ if not path in cls._consoles: console = WebConsole(path) cls._consoles[path] = console return cls._consoles[path] def __init__(self, path): """ подготовить окружение к запуску дочернего процесса нужно - загрузить .zoo - найти на каком энжайне он работает - найти энжайн - взять текущие переменные окружения - взять переменные окружения энжайна - взять переменные окружения .zoo - раскрыть все переменные окружения запустить дочерний процесс :param path: """ self.path = path self.core = Core.get_instance() self.physical_path = self.core.api.os.web_server.map_webserver_path(self.path) self.zoo_config = self.core.api.os.web_server.get_zoo_config(self.physical_path) self.env = os.environ.copy() # emulate zoo module variables self.env["INSTANCE_ID"] = "0" self.env["APPL_PHYSICAL_PATH"] = self.physical_path self.env["APPL_PHYSICAL_SHORT_PATH"] = self.core.api.os.shell.get_short_path_name(self.physical_path) self.env["APPL_VIRTUAL_PATH"] = self.path self.env["APPL_ID"] = self.env["APPL_VIRTUAL_PATH"].replace("/","") #self.env["IIS_BINDNGS"] = self.core.api.os.web_server.get_site() if self.zoo_config: if self.zoo_config['engine']: engine_config = self.core.engine_storage.get_product(self.zoo_config['engine']).config if engine_config: if 'environment_variables' in engine_config: engine_env = engine_config.get('environment_variables') self.update_env(self.env, engine_env) if 'environment_variables' in self.zoo_config: app_env = self.zoo_config.get('environment_variables') self.update_env(self.env, app_env) self.env = self.expand_variables_in_dict(self.env) self.process = None self.std_out = "" self.std_err = "" self.stdout_reader = None self.stderr_reader = None self.timestamp = 0 self.start() def expand_variables_in_dict(self, envs): """ развернуть переменные окружения в словаре для каждого значения - сначала системные - затем зу :param envs: :return: """ result = dict() for (key, value) in envs.items(): result[key.upper()] = os.path.expandvars(envs[key]) result[key.upper()] = self.expand_zoo_variables(envs[key]) return result def expand_zoo_variables(self, string_to_process): """ развернуть в строке переменные окружения zoo поискать в строке '%KEY%' если есть то заменить на соответсвующее значение из словаря и так для каждого ключа в словаре :param string_to_process: :return: """ result = string_to_process for (key, value) in self.env.items(): if string_to_process.lower() != key.lower(): result = result.lower().replace("%" + key.lower() + "%", value) if result.lower().find("%") != -1: #repeat if nested expand needed for (key, value) in self.env.items(): if string_to_process.lower() != key.lower(): result = result.lower().replace("%" + key.lower() + "%", value) if result.lower() != string_to_process.lower(): logging.info("expand_zoo_variables > {0} --> {1}".format( string_to_process, result.lower())) return result.lower() def __repr__(self): return 'WebConsole({0})'.format(self.path) def to_dict(self): return object_attrs_to_dict(self, ['path', 'physical_path']) def start(self): """ запустить дочерний процесс консоли self.env - переоперделенные переменные окружения read_output_thread - поток читатель из std_out check_idle_thread - поток, проверяет активное ли соединение, JavaScript делает переодически запросы к консоли для получить новые буквы ответа, и если давно небыло опроса, то пора выходить """ self.process = subprocess.Popen( 'cmd.exe', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env, bufsize=1, cwd=self.physical_path ) self.stdout_reader = OutputReader(self.process.stdout) self.stderr_reader = OutputReader(self.process.stderr) read_output_thread = Thread(target=self.read_output) read_output_thread.daemon = True read_output_thread.start() check_idle_thread = Thread(target=self.check_idle_thread) check_idle_thread.daemon = True check_idle_thread.start() def read_output(self): """ читать все что выдала консоль. запомнить в self.std_out, self.std_er """ while True: if not self.process or self.process.poll() is not None: break out = self.stdout_reader.read_nowait() self.std_out += out err = self.stderr_reader.read_nowait() self.std_err += err # if not out and not err: time.sleep(0.1) logging.debug('read_output_thread completed') def write(self, command): """ просто записать строку в консоль :param command: :return: """ logging.debug(command) self.process.stdin.write(str.encode(command)) self.process.stdin.flush() self.timestamp = time.time() resp = {} return resp def read(self): """ прочитать из консоли, кажется не используется :return: """ out = self.std_out self.std_out = '' err = self.std_err self.std_err = '' if out: logging.debug('stdout: {0}'.format(out)) if err: logging.debug('stderr: {0}'.format(err)) self.timestamp = time.time() return { 'stdout': out, 'stderr': err, } def check_idle_thread(self): """ тело потока, проверка на активность клиента консоли вечный цикл если давно небыло запроса к консоли, то закрыть ее """ self.timestamp = time.time() while True: time.sleep(10) if self.check_idle(): break logging.debug('check_idle_thread completed') def check_idle(self): """ поверить наступило ли условие для выхода если ппроцесс умер если небыло давно запросов в веб консоли :return: """ logging.debug('check idle console') if not self.process or self.process.poll() is not None: return True if time.time() - self.timestamp >= self.IDLE_INTERVAL: # console is idle for IDLE_INTERVAL second logging.info('{0} is idle, close it'.format(self)) WebConsole.cancel_console(self) return True return False def cancel(self): """ убить процесс """ self.process.terminate() self.process = None @classmethod def cancel_console(cls, console): """ хендлер. закрытия консоли :param console: """ logging.debug('Canceling {0}'.format(console)) console.cancel() del cls._consoles[console.path] def update_env(self, env, env2): for (key, value) in env2.items(): env[key.upper()] = os.path.expandvars(value)
class WebConsole(object): """ реализация веб-консоли 1 экземпляра после запуска проверяет активно ли соединение с клиентом, если он не опрашивал консоль в течении IDLE_INTERVAL, то надо все закрыть """ _consoles = {} # активные консоли. для разных узлов в дереве IDLE_INTERVAL = 30 @classmethod def get_console(cls, path): """ получить консоль для указанного пути. если нет - None :param path: :return: """ return cls._consoles.get(path, None) @classmethod def create_console(cls, path): """ если нет в активных. то создать WebConsole и добавить в активные, по указанному пути :param path: :return: """ if not path in cls._consoles: console = WebConsole(path) cls._consoles[path] = console return cls._consoles[path] def __init__(self, path): """ подготовить окружение к запуску дочернего процесса нужно - загрузить .zoo - найти на каком энжайне он работает - найти энжайн - взять текущие переменные окружения - взять переменные окружения энжайна - взять переменные окружения .zoo - раскрыть все переменные окружения запустить дочерний процесс :param path: """ self.path = path self.core = Core.get_instance() self.physical_path = self.core.api.os.web_server.map_webserver_path(self.path) self.zoo_config = self.core.api.os.web_server.get_zoo_config(self.physical_path) self.env = os.environ.copy() # emulate zoo module variables self.env["INSTANCE_ID"] = "0" self.env["APPL_PHYSICAL_PATH"] = self.physical_path self.env["APPL_PHYSICAL_SHORT_PATH"] = self.core.api.os.shell.get_short_path_name(self.physical_path) self.env["APPL_VIRTUAL_PATH"] = self.path self.env["APPL_ID"] = self.env["APPL_VIRTUAL_PATH"].replace("/", "") if self.zoo_config: zoo_env = self.core.api.os.web_server.create_environment(self.zoo_config, self.env) self.env = zoo_env self.process = None self.pipe_stdin = None self.pipe_stdout = None self.pipe_stderr = None self.std_out = "" self.std_err = "" self.stdout_reader = None self.stderr_reader = None self.timestamp = 0 self.start() def __repr__(self): return 'WebConsole({0})'.format(self.path) def to_dict(self): return object_attrs_to_dict(self, ['path', 'physical_path']) def start(self): """ запустить дочерний процесс консоли self.env - переоперделенные переменные окружения read_output_thread - поток читатель из std_out check_idle_thread - поток, проверяет активное ли соединение, JavaScript делает переодически запросы к консоли для получить новые буквы ответа, и если давно небыло опроса, то пора выходить """ self.process = subprocess.Popen( 'cmd.exe', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env, bufsize=1, cwd=self.physical_path ) self.stdout_reader = OutputReader(self.process.stdout) self.stderr_reader = OutputReader(self.process.stderr) read_output_thread = Thread(target=self.read_output) read_output_thread.daemon = True read_output_thread.start() check_idle_thread = Thread(target=self.check_idle_thread) check_idle_thread.daemon = True check_idle_thread.start() def read_output(self): """ читать все что выдала консоль. запомнить в self.std_out, self.std_er """ while True: if not self.process or self.process.poll() is not None: break out = self.stdout_reader.read_nowait() self.std_out += out err = self.stderr_reader.read_nowait() self.std_err += err # logging.debug(" from output %s" % out) # if not out and not err: time.sleep(0.1) logging.debug('read_output_thread completed') def write(self, command): """ просто записать строку в консоль :param command: :return: """ logging.debug(command) self.process.stdin.write(str.encode(command)) self.process.stdin.flush() self.timestamp = time.time() resp = {} return resp def read(self): """ прочитать из консоли, кажется не используется :return: """ out = self.std_out self.std_out = '' err = self.std_err self.std_err = '' logging.debug('ping connection: ') if out: logging.debug('stdout: {0}'.format(out)) if err: logging.debug('stderr: {0}'.format(err)) self.timestamp = time.time() return { 'stdout': out, 'stderr': err, } def check_idle_thread(self): """ тело потока, проверка на активность клиента консоли вечный цикл если давно небыло запроса к консоли, то закрыть ее """ self.timestamp = time.time() while True: time.sleep(10) if self.check_idle(): break logging.debug('check_idle_thread completed') def check_idle(self): """ поверить наступило ли условие для выхода если ппроцесс умер если небыло давно запросов в веб консоли :return: """ logging.debug('check idle console') if not self.process or self.process.poll() is not None: return True if time.time() - self.timestamp >= self.IDLE_INTERVAL: # console is idle for IDLE_INTERVAL second logging.info('{0} is idle, close it'.format(self)) WebConsole.cancel_console(self) return True return False def cancel(self): """ убить процесс """ self.process.terminate() self.process = None @classmethod def cancel_console(cls, console): """ хендлер. закрытия консоли :param console: """ logging.debug('Canceling {0}'.format(console)) console.cancel() del cls._consoles[console.path] def update_env(self, env, env2): for (key, value) in env2.items(): env[key.upper()] = os.path.expandvars(value)