如何停止Tornado Web服务器?

时间:2011-03-21 08:16:56

标签: python tornado

我一直在玩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服务器的类(具有自己的startstop函数),但是一旦我调用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服务器?

9 个答案:

答案 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.Processtornado.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()

干杯!