我一直在玩Tornado web server并且已经到了我想要停止Web服务器的地步(例如在单元测试期间)。以下简单示例exists on the Tornado web page:
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
调用tornado.ioloop.IOLoop.instance().start()
后,它会阻止程序(或当前线程)。阅读IOLoop
对象的source code会在stop
函数的文档中提供此示例:
To use asynchronous methods from otherwise-synchronous code (such as
unit tests), you can start and stop the event loop like this:
ioloop = IOLoop()
async_method(ioloop=ioloop, callback=ioloop.stop)
ioloop.start()
ioloop.start() will return after async_method has run its callback,
whether that callback was invoked before or after ioloop.start.
但是,我不知道如何将其整合到我的程序中。我实际上有一个封装Web服务器的类(具有自己的start
和stop
函数),但是一旦我调用start,程序(或测试)当然会阻塞。
我尝试在另一个进程中启动Web服务器(使用multiprocessing
包)。这是包装Web服务器的类:
class Server:
def __init__(self, port=8888):
self.application = tornado.web.Application([ (r"/", Handler) ])
def server_thread(application, port):
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(port)
tornado.ioloop.IOLoop.instance().start()
self.process = Process(target=server_thread,
args=(self.application, port,))
def start(self):
self.process.start()
def stop(self):
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.add_callback(ioloop.stop)
然而,停止似乎并没有完全停止Web服务器,因为它仍在下一次测试中运行,即使使用此测试设置:
def setup_method(self, _function):
self.server = Server()
self.server.start()
time.sleep(0.5) # Wait for web server to start
def teardown_method(self, _function):
self.kstore.stop()
time.sleep(0.5)
如何在Python程序中启动和停止Tornado Web服务器?
答案 0 :(得分:25)
我刚刚遇到这个并且自己发现了这个问题,并且使用来自此主题的信息提出了以下内容。我只是单独使用了我的工作站Tornado代码(从所有示例中复制)并将实际的起始代码移动到一个函数中。然后我将该函数称为线程线程。我的情况不同,因为线程调用是从我现有的代码完成的,我刚刚导入了startTornado和stopTornado例程。
上面的建议看起来效果很好,所以我想我会提供缺少的示例代码。我在Linux下在FC16系统上测试了这段代码(并修复了我的初始类型-o)。
import tornado.ioloop, tornado.web
class Handler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([ (r"/", Handler) ])
def startTornado():
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
def stopTornado():
tornado.ioloop.IOLoop.instance().stop()
if __name__ == "__main__":
import time, threading
threading.Thread(target=startTornado).start()
print "Your web server will self destruct in 2 minutes"
time.sleep(120)
stopTornado()
希望这有助于下一个人。
答案 1 :(得分:18)
以下是如何从另一个线程中阻止Torando的解决方案。 Schildmeijer提供了一个很好的提示,但我花了一段时间才真正想出最终的例子。
请参阅以下内容:
import threading
import tornado.ioloop
import tornado.web
import time
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world!\n")
def start_tornado(*args, **kwargs):
application = tornado.web.Application([
(r"/", MainHandler),
])
application.listen(8888)
print "Starting Torando"
tornado.ioloop.IOLoop.instance().start()
print "Tornado finished"
def stop_tornado():
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.add_callback(ioloop.stop)
print "Asked Tornado to exit"
def main():
t = threading.Thread(target=start_tornado)
t.start()
time.sleep(5)
stop_tornado()
t.join()
if __name__ == "__main__":
main()
答案 2 :(得分:8)
如果你不想打扰线程,你可以捕获键盘中断信号:
try:
tornado.ioloop.IOLoop.instance().start()
# signal : CTRL + BREAK on windows or CTRL + C on linux
except KeyboardInterrupt:
tornado.ioloop.IOLoop.instance().stop()
答案 3 :(得分:3)
要停止整个ioloop,只需在完成单元测试后调用ioloop.stop方法即可。 (请记住,唯一(记录在案的)线程安全方法是ioloop.add_callback,即如果单元测试由另一个线程执行,则可以在回调中包含stop调用)
如果足以停止http网络服务器,则调用httpserver。stop()方法
答案 4 :(得分:2)
如果您需要此行为进行单元测试,请查看tornado.testing.AsyncTestCase。
默认情况下,为每个测试构建一个新的IOLoop,并以self.io_loop的形式提供。此IOLoop应该用于构建HTTP客户端/服务器等。如果要测试的代码需要全局IOLoop,则子类应覆盖get_new_ioloop以返回它。
如果出于某种其他目的需要启动和停止IOLoop,并且由于某种原因无法从回调中调用ioloop.stop(),则可以实现多线程实现。但是,为避免竞争条件,您需要同步访问ioloop,这实际上是不可能的。类似以下内容将导致死锁:
主题1:
with lock:
ioloop.start()
主题2:
with lock:
ioloop.stop()
因为线程1永远不会释放锁定(start()阻塞),线程2将等到释放锁定以停止ioloop。
唯一的方法是让线程2调用ioloop.add_callback(ioloop.stop),它将在事件循环的下一次迭代中调用线程1上的stop()。请放心,ioloop.add_callback()是thread-safe。
答案 5 :(得分:2)
Zaar Hai的解决方案存在问题,即它使插座保持打开状态。我正在寻找停止Tornado的解决方案的原因是我正在对我的应用服务器运行单元测试,我需要一种方法来在测试之间启动/停止服务器以具有清除状态(空会话等)。通过使套接字保持打开状态,第二个测试总是遇到Address already in use
错误。所以我想出了以下内容:
import logging as log
from time import sleep
from threading import Thread
import tornado
from tornado.httpserver import HTTPServer
server = None
thread = None
def start_app():
def start():
global server
server = HTTPServer(create_app())
server.listen(TEST_PORT, TEST_HOST)
tornado.ioloop.IOLoop.instance().start()
global thread
thread = Thread(target=start)
thread.start()
# wait for the server to fully initialize
sleep(0.5)
def stop_app():
server.stop()
# silence StreamClosedError Tornado is throwing after it is stopped
log.getLogger().setLevel(log.FATAL)
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.add_callback(ioloop.stop)
thread.join()
所以这里的主要思想是保持对HTTPServer
实例的引用并调用其stop()
方法。 create_app()
只返回配置了处理程序的Application
实例。现在,您可以在单元测试中使用这些方法,如下所示:
class FoobarTest(unittest.TestCase):
def setUp(self):
start_app()
def tearDown(self):
stop_app()
def test_foobar(self):
# here the server is up and running, so you can make requests to it
pass
答案 6 :(得分:1)
Tornado的IOloop.instance()在multiprocessing.Process下运行时无法停止外部信号。
我提出的唯一能够始终的解决方案是使用Process.terminate():
import tornado.ioloop, tornado.web
import time
import multiprocessing
class Handler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([ (r"/", Handler) ])
class TornadoStop(Exception):
pass
def stop():
raise TornadoStop
class worker(multiprocessing.Process):
def __init__(self):
multiprocessing.Process.__init__(self)
application.listen(8888)
self.ioloop = tornado.ioloop.IOLoop.instance()
def run(self):
self.ioloop.start()
def stop(self, timeout = 0):
self.ioloop.stop()
time.sleep(timeout)
self.terminate()
if __name__ == "__main__":
w = worker()
print 'starting server'
w.start()
t = 2
print 'waiting {} seconds before stopping'.format(t)
for i in range(t):
time.sleep(1)
print i
print 'stopping'
w.stop(1)
print 'stopped'
答案 7 :(得分:0)
只需在start()之前添加:
IOLoop.instance()。add_timeout(10,IOLoop.instance()。停止)
它会将停止功能注册为循环中的回调,并在启动后10秒钟
答案 8 :(得分:0)
我们希望使用multiprocessing.Process
和tornado.ioloop.IOLoop
来解决cPython GIL的性能和独立性问题。要访问IOLoop,我们需要使用Queue
来传递关闭信号。
这是一个简约的例子:
class Server(BokehServer)
def start(self, signal=None):
logger.info('Starting server on http://localhost:%d'
% (self.port))
if signal is not None:
def shutdown():
if not signal.empty():
self.stop()
tornado.ioloop.PeriodicCallback(shutdown, 1000).start()
BokehServer.start(self)
self.ioloop.start()
def stop(self, *args, **kwargs): # args important for signals
logger.info('Stopping server...')
BokehServer.stop(self)
self.ioloop.stop()
过程
import multiprocessing as mp
import signal
from server import Server # noqa
class ServerProcess(mp.Process):
def __init__(self, *args, **kwargs):
self.server = Server(*args, **kwargs)
self.shutdown_signal = _mp.Queue(1)
mp.Process.__init__(self)
signal.signal(signal.SIGTERM, self.server.stop)
signal.signal(signal.SIGINT, self.server.stop)
def run(self):
self.server.start(signal=self.shutdown_signal)
def stop(self):
self.shutdown_signal.put(True)
if __name__ == '__main__':
p = ServerProcess()
p.start()
干杯!