如何使用 Python 脚本启动和停止包含 "http.server.serveforever" 的 Python 脚本。

4

背景

我有一个名为server.py的Python 3脚本,它使用了内置的http.server模块。该脚本可以归纳如下:

from http.server import HTTPServer
from http.server import BaseHTTPRequestHandler

class MyRequestHandler (BaseHTTPRequestHandler):
    def do_POST(self):
        # Code omitted for brevity

    def do_GET(self):
        # Code omitted for brevity

def start_server():

    # Begin serving
    # -------------
    server = HTTPServer(('', port), MyRequestHandler)
    print("server now running on port {0} ...".format(port))

    server.serve_forever()

# Start the Server
# ----------------
if __name__ == '__main__':
    start_server()

MyRequestHandler可以根据请求的URI动态导入模块来处理GET和POST请求。

虽然以上方法可行,但是在那个脚本创建之后,有一个需求需要远程更新整个“包”(即服务器脚本加上所有加载“on-the-fly”的“模块脚本”,并且它们位于子文件夹中)。

为此,我编写了另一个Python 3服务器脚本(称为updater.py),当接到指令时会获取一个zip文件,然后解压缩以覆盖原始的server.py脚本以及所有其他相关脚本和子文件夹。

问题

这一切都很顺利,但是现在我遇到了麻烦。我认为最好的方法是让updater.py脚本控制server.py的运行。它可以关闭server.py和与其相关的一切,在覆盖所有内容之前,然后在覆盖后重新启动。

基于这个想法,我已经使用subprocess.Popen开启了服务器进程,相信我可以在覆盖server.py之前杀死Python进程,但是这并没有像希望的那样工作。下面是我编写的trial.py脚本以测试这个想法:

import sys
import subprocess

def main():
    def start_process():
        proc = subprocess.Popen([sys.executable, 'server.py'])
        print("Started process:")
        print(proc.pid)
        return proc

    def kill_process(the_process):
        print("Killing process:")
        print(the_process.pid)
        the_process.kill()

    process = None

    while True:
        user_input = input("Type something: ")

        if user_input == 'start':
            process = start_process()
        if user_input == 'kill':
            kill_process(process)
        if user_input == 'exit':
            break

if __name__ == '__main__':
    main()

这似乎启动并终止了一个Python进程,但是当此脚本运行时,服务器并未运行,因此我不确定它正在启动和终止什么!输入“start”,然后输入“exit”(从而退出trial.py脚本)允许服务器运行,尽管我不明白为什么,因为我认为subprocess.Popen应该使生成的进程独立于父进程运行?

编辑:感谢@Håken Lid的敏锐观察,我注意到我所做的一切都只是跳出了while循环,而没有退出脚本。这使我相信while循环以某种方式阻止子进程运行(因为一旦退出循环,服务器就会启动)。

2
如果子进程独立于父进程,那它就不会被称为“子进程”了,对吧? - Håken Lid
多尴尬啊!我知道这个,但我的大脑却走了一个岔路。我混淆了启动一个单独的进程和启动一个单独的独立进程。我认为可以说(现在你鼓励我重新阅读我的问题和代码)while循环以某种方式阻塞了子进程的执行。 - PeterByte
2个回答

1
根据我们的讨论,我建议在“server.py”中提供清空stdio缓冲区的方法。如果您还想提供用户输入功能,则需要一个线程来打印(或将缓冲区清空到黑洞中),同时在主线程上等待用户输入。以下是我可能会如何实现的大致想法。
import sys
import subprocess
from threading import Thread 
#This could probably be solved with async, but I still
#haven't learned async as well as I know threads

def main():

    def start_process():
        proc = subprocess.Popen([sys.executable, 'server.py'], 
                                stdin=subprocess.PIPE, 
                                stdout=subprocess.PIPE)
        print("Started process:")

        def buf_readerd(proc, inbuf, outbuf):
            while proc.poll() is None:
                outbuf.write(inbuf.readline()) #may need to add a newline.. I'm not sure if readline ends in a \n

        stdoutd = Thread(target=buf_readerd, args=(proc, proc.stdout, sys.stdout), daemon=True)
        stderrd = Thread(target=buf_readerd, args=(proc, proc.stderr, sys.stderr), daemon=True)
        stdoutd.start()
        stderrd.start()
        print("started pipe reader daemons")

        print(proc.pid)
        return proc
# ...

0

好的,我想我现在已经自己解决了这个问题。问题是我在while循环内部调用input()阻塞了子进程的执行。脚本在接收到输入之前什么也不做。我将subprocess.poll()作为循环的一个组成部分,并将input()的调用放在此循环内部。如果我按几次<return>,服务器就会启动,然后我可以使用它。如果我输入kill,服务器将被杀死,如预期。

我不确定这在我的updater.py脚本的上下文中如何工作,但我已经学到了一个宝贵的教训:“在发布到StackOverflow之前要更深入地思考”!

import sys
import subprocess

def main():
    def start_process():
        proc = subprocess.Popen([sys.executable, 'server.py'])
        print("Started process:")
        print(proc.pid)
        return proc

    def kill_process(the_process):
        print("Killing process:")
        print(the_process.pid)
        the_process.kill()

    user_input = input("Type something: ")

    if user_input == 'start':
        process = start_process()

        while process.poll() is None:
            user_input = input()
            if user_input == 'kill' or user_input == 'exit':
                kill_process(process)

if __name__ == '__main__':
    main()

1
那个行为对我来说似乎很奇怪。你尝试过不同的stdin和stdout配置,看看是否在等待完整的缓冲区?如果没有正确解决,这可能会成为以后的问题。 - Aaron
1
在popen构造函数中加入stdin=subprocess.PIPEstdout=subprocess.PIPE,并启动一个辅助线程来定期读取它们的内容并转储到sys.stdoutsys.stderr,同时等待主线程的用户输入。 - Aaron
我也觉得有点奇怪。一旦进程启动,它就会轮询并返回“None”,这很公平。然后它等待用户输入。此时,我希望服务器进程正在运行,但实际上并没有。只有在尝试了2/3次用户输入(即按<return>键2或3次)之后,“服务器现在正在运行…”消息才会出现。在该消息出现之前,服务器也不会响应,因此它绝对不会在按几次<return>键之前运行。 - PeterByte
打印“服务器现在运行”的是“server.py”还是主进程? - Aaron
"server.py"脚本正在打印“服务器正在运行”的消息。 - PeterByte
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接