如何在Python simplhttpserver中禁用输出缓冲

0 投票
1 回答
598 浏览
提问于 2025-04-18 15:02

下面这段代码是一个脚本的一部分,这个脚本实现了一个简单的HTTP服务器实例,当收到GET请求时,它会触发一个第三方模块。我可以捕捉到这个第三方模块的标准输出信息,并把这些信息发送到网页浏览器上。

目前,这个脚本会收集所有的标准输出信息,并在调用的模块完成后一次性发送给客户端……

因为我希望每条信息在发送到标准输出时就能在浏览器中显示,所以需要关闭输出缓冲。

我该如何在Python的简单HTTP服务器中做到这一点呢?

def do_GET(self):
    global key

    stdout_ = sys.stdout #Keep track of the previous value.
    stream = cStringIO.StringIO()
    sys.stdout = stream

    ''' Present frontpage with user authentication. '''
    if self.headers.getheader('Authorization') == None:
        self.do_AUTHHEAD()
        self.wfile.write('no auth header received')
        pass
    elif self.headers.getheader('Authorization') == 'Basic '+key:
        if None != re.search('/api/v1/check/*', self.path):
            recordID = self.path.split('/')[-1]
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.send_header('Access-Control-Allow-Origin', '*')
            self.send_header('Access-Control-Allow-Methods', 'GET,POST,PUT,OPTIONS')
            self.send_header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization")
            self.end_headers()
            notStarted = True
            while True:
                if notStarted is True:
                    self.moduleXYZ.start()
                    notStarted is False
                if "finished" in stream.getvalue():
                    sys.stdout = stdout_ # restore the previous stdout.
                    self.wfile.write(stream.getvalue())
                    break

更新

我修改了方法,从类中获取状态信息,而不是使用标准输出。我还加入了Martijn的好主意,来跟踪变化。

现在我运行服务器时,发现我真的需要使用线程吗?看起来这个脚本在完成之前会一直等待,然后才进入循环。

我应该在服务器中实现线程,还是在模块类中实现线程呢?

   def do_GET(self):
        global key

        ''' Present frontpage with user authentication. '''
        if self.headers.getheader('Authorization') == None:
            self.do_AUTHHEAD()
            self.wfile.write('no auth header received')
            pass
        elif self.headers.getheader('Authorization') == 'Basic '+key:
            if None != re.search('/api/v1/check/*', self.path):
                recordID = self.path.split('/')[-1]
                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Access-Control-Allow-Origin', '*')
                self.send_header('Access-Control-Allow-Methods', 'GET,POST,PUT,OPTIONS')
                self.send_header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization")
                self.end_headers()
                self.moduleABC.startCrawl()

                while True:
                    if self.moduleABC.done:
                        print "done"
                        break
                    output = self.moduleABC.statusMessages
                    self.wfile.write(output[sent:]) 
                    sent = len(output)


            else:
                self.send_response(403)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()

更新 2(已解决)

这是我更新后的GET方法。第三方模块的类对象是在GET方法中实例化的。模块的主方法是在一个线程中运行的。我使用了Martijn的想法来监控进度。

我花了一些时间才明白,必须在发送给浏览器的状态文本中附加一些额外的字节,以强制刷新缓冲区!

感谢你们的帮助!

def do_GET(self):
    global key
    abcd = abcdModule(u"abcd")

    ''' Present frontpage with user authentication. '''
    if self.headers.getheader('Authorization') == None:
        self.do_AUTHHEAD()
        self.wfile.write('no auth header received')
        pass
    elif self.headers.getheader('Authorization') == 'Basic '+key:
        if None != re.search('/api/v1/check/*', self.path):
            recordID = self.path.split('/')[-1]
            abcd.setMasterlist([urllib.unquote(recordID)])
            abcd.useCaching = False
            abcd.maxRecursion = 1
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.send_header('Access-Control-Allow-Origin', '*')
            self.send_header('Access-Control-Allow-Methods', 'GET,POST,PUT,OPTIONS')
            self.send_header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization")
            self.end_headers()
            thread.start_new_thread(abcd.start, ())
            sent = 0

            while True:
                if abcd.done:
                    print "done"
                    break
                output = abcd.statusMessages

                if len(output) == sent + 1:
                    print abcd.statusMessages[-1]
                    self.wfile.write(json.dumps(abcd.statusMessages)) 
                    self.wfile.write("".join([" " for x in range(1,1000)]))
                    sent = len(output)           


        else:
            self.send_response(403)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
    else:
        self.do_AUTHHEAD()
        self.wfile.write(self.headers.getheader('Authorization'))
        self.wfile.write('not authenticated')
        pass


    return

1 个回答

0

你真的需要修改 moduleXYZ,让它不要只通过 stdout 来输出信息。这样做会让这个模块不适合在 多线程 的服务器中使用。例如,如果两个不同的线程同时调用 moduleXYZ,那么它们的输出会混在一起,变得不可预测。

不过,这里并没有进行 流缓冲。你实际上是把所有的 stdout 信息都捕获到一个 cStringIO 对象中,只有当你在捕获的字符串中看到 "finished" 这个词时,才会输出结果。你应该做的是持续输出这个值,并跟踪你已经发送了多少内容:

self.moduleXYZ.start()
sent = 0
while True:
    output = stream.getvalue()
    self.wfile.write(output[sent:])
    sent = len(output)
    if "finished" in output:
        sys.stdout = stdout_
        break

更好的方法是直接把 stdout 连接到 self.wfile,让模块直接写入响应;在这种情况下,你需要用不同的方法来检测模块线程是否完成:

old_stdout = sys.stdout
sys.stdout = self.wfile
self.moduleXYZ.start()
while True:
    if self.moduleXYZ.done():
        sys.stdout = old_stdout
        break

撰写回答