python web 命令行工具实现

有个服务器端命令行工具,想直接放到web上执行,比如说mysql命令行,python里有多种实现方式。

subprocess

通过subprocess的popen方法创建一个子进程,然后向子进程发送输入,另起线程获取子进程的输出。

import subprocess
import threading
import sys
import inspect
import ctypes

class CliService(object):

    def __init__(self):
        self.output_lock = threading.Lock()  # 用于输出操作的锁
        self.cmd_lock = threading.Lock()  # 用于判断命令执行状态的锁
        self.output = []
        self.shell_client = None
        self.read_thread = None  # 输出的读取线程
        self.is_active = False  # 当前是否活动
        self.prompt = """prompt=function(){print("mysql>");}\n"""  # 提示符
        self.cmd_running = False  # 命令是否在执行

    # kill掉某个线程
    def _async_raise(self, tid, exctype):
        """Raises an exception in the threads with id tid"""
        if not inspect.isclass(exctype):
            raise TypeError("Only types can be raised (not instances)")
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), ctypes.py_object(exctype))
        if res == 0:
            raise ValueError("invalid thread id")
        elif res != 1:
            # """if it returns a number greater than one, you're in trouble,
            # and you should call it again with exc=NULL to revert the effect"""
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
            raise SystemError("PyThreadState_SetAsyncExc failed")

    # 后台读取输出
    def read_output_background(self):
        while True:
            if not self.shell_client.stdout.readable():
                break
            line = self.shell_client.stdout.readline()  # 这里是阻塞的
            if not line:
                break
            line = line.decode("utf8")
            # 判断是否为结束表示符
            if line == "mysql>\n":
                self.cmd_lock.acquire(True)
                self.cmd_running = False
                self.cmd_lock.release()
            else:
                # 加入到output里
                self.output_lock.acquire(True)
                self.output.append(line)
                self.output_lock.release()
                print(line)
        print("read exit")
        self.read_thread = None
        if self.shell_client.poll() is not None:  # 子进程已经退出
            self.is_active = False
            self.cmd_running = False
        print("read: %s" % self.is_active)

    # 获取日志
    def get_output(self):
        try:
            self.output_lock.acquire(True)
            text = "".join(self.output)
            self.output.clear()
            return text
        finally:
            self.output_lock.release()

    # 以子进程形式运行命令
    def init_cmd(self, cmd):
        self.shell_client = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
                                             stderr=subprocess.PIPE, shell=True)
        if self.shell_client.poll() is None:
            self.is_active = True
        else:
            return False, u"建立连接失败"
        self.read_thread = threading.Thread(target=self.read_output_background)
        self.read_thread.start()
        # set prompt
        return self.run_cmd(cmd=self.prompt)

    # 执行命令
    def run_cmd(self, cmd):
        try:
            self.cmd_lock.acquire(True)
            #if self.cmd_running:
            #    return False, u"有未执行完成的命令"
            if not self.is_active:
                return False, u"当前连接不可用"
            self.cmd_running = True
            self.shell_client.stdin.write(cmd.encode(encoding="utf8"))
            self.shell_client.stdin.flush()
            return True, "push cmd done"
        except Exception as e:
            print(e)
            return False, "执行命令出现异常:%s" % e
        finally:
            self.cmd_lock.release()

    # 判断客户端是否已经结束
    def is_terminal(self):
        return not self.is_active

    def close(self):
        self.run_cmd(cmd="exit\n")

    def __del__(self):
        if self.read_thread is not None:
            self._async_raise(self.read_thread.ident, SystemExit)
            ("stop it")


if __name__ == "__main__":
    cmd = 'mysql'
    ms = CliService()
    ms.init_cmd(cmd)
    for cmd in sys.stdin:
        print(ms.is_terminal())
        if ms.is_terminal():
            print("exit")
            break
        ms.run_cmd(cmd)
        if ms.is_terminal():
            print("exit")
    print("main exit")

pexpect

上面使用subprocess的方式看上去有点复杂,需要另起线程去获取输出,有没有更简单的办法呢?
有的,它就是pexpect,使用超级简单!
使用pexpect.spawn生成子进程,通过sendline向子进程发送命令,同步获取命令输出结果。

import pexpect

class CliService(object):

    def __init__(self, cmd='', prompt=''):
        self.client = None
        self.prompt = prompt

        self.init_cmd(cmd)

    # 以子进程形式运行命令
    def init_cmd(self, cmd):
        self.client = pexpect.spawn(cmd, encoding='utf-8', echo=False)
        self.client.expect(self.prompt)

    # 执行命令
    def run_cmd(self, cmd):
        self.client.sendline(cmd)
        self.client.expect(self.prompt)

        return self.client.before

    def is_alive(self):
        return self.client.isalive()


if __name__ == '__main__':
    cli = CliService('mysql', 'mysql> ')

    for cmd in ['show databases;', 'show tables;']:
        print(cmd)
        print(cli.run_cmd(cmd))

不过上面两种方法都有一个致命的缺点,那就是需要维护会话与进程的关系,并且在服务端多进程的情况下,每次请求可能会到不同的进程下执行,这样上一次的子进程新的请求访问不了。

这里有一个实现方式是web端每次连接的时候,不直接创建子进程,而是创建一个任务,任务包含会话信息和命令信息,然后服务端运行一个后台常驻进程,从任务列表里获取任务并创建或者获取对应的子进程,执行相应的命令,并将命令结果写回任务里,通过websocket或者web端主动轮询命令结果。

当然这里的实现方式还是很多的,你会怎么实现呢?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PythonWeb是一种基于Python语言开发的网上商城下载工具PythonWeb具有以下特点和功能。 首先,PythonWeb采用Python作为开发语言Python是一种简单易学、功能强大的编程语言。它具有丰富的库和模块,可以快速开发出高效可靠的网上商城应用。 其次,PythonWeb支持网上商城的下载功能。用户可以通过PythonWeb快速下载网上商城的代码并进行安装和部署。PythonWeb提供了简单易用的命令行工具,用户只需要输入相应的命令,就可以自动下载网上商城所需的代码文件,节省了用户大量的时间和精力。 此外,PythonWeb还具有强大的扩展能力。PythonWeb提供了丰富的插件和扩展库,用户可以根据自己的需求自由选择和使用这些插件和扩展库,从而快速构建出符合自己需求的网上商城应用。这些插件和扩展库在开发过程中能够提供丰富的功能和特性,大大提升了开发效率和用户体验。 最后,PythonWeb还具有良好的跨平台性能。由于Python的跨平台特性,PythonWeb可以在多种操作系统上运行,包括Windows、Linux和Mac OS等。这样,用户不需要考虑操作系统的差异,能够轻松地在不同平台上下载网上商城应用,节省了用户的成本和精力。 综上所述,PythonWeb是一种功能强大、易于使用的Python网上商城下载工具。它提供了快速、简便的下载功能,具有良好的扩展能力和跨平台性能,能够满足用户对网上商城应用的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值