Comment puis-je arrêter Tornado Web server?
J'ai joué un peu avec le serveur web Tornado et je suis arrivé à un point où je veux arrêter le serveur web (par exemple pendant les tests unitaires). L'exemple simple suivant existe sur la page Web de Tornado:
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()
Une fois que tornado.ioloop.IOLoop.instance().start()
est appelé, il bloque le programme (ou le thread actuel). La lecture du code source pour l'objet IOLoop
Donne cet exemple dans la documentation de la fonction 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.
Cependant, je n'ai pas idée comment intégrer cela dans mon programme. J'ai en fait une classe qui encapsule le serveur web (ayant ses propres fonctions start
et stop
), mais dès que j'appelle start, le programme (ou les tests) va bien sûr bloquer de toute façon.
J'ai essayé de démarrer le serveur web dans un autre processus (en utilisant le paquet multiprocessing
). C'est la classe qui enveloppe le serveur 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)
Cependant, stop ne semble pas arrêter complètement le serveur web car il est toujours en cours d'exécution dans le test suivant, même avec cette configuration de test:
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)
Comment puis-je démarrer et arrêter un serveur web Tornado à partir d'un programme Python?
9 réponses
Je viens de rencontrer ceci et j'ai trouvé ce problème moi-même, et en utilisant les informations de ce fil est venu avec ce qui suit. J'ai simplement pris mon code Tornado autonome de travail (copié à partir de tous les exemples) et déplacé le code de départ réel dans une fonction. J'ai ensuite appelé la fonction comme un fil de filetage. Mon cas différent que l'appel de threading a été fait à partir de mon code existant où je viens d'importer les routines startTornado et stopTornado.
La suggestion ci-dessus semblait fonctionner très bien, donc je j'ai pensé que je fournirais l'exemple de code manquant. J'ai testé ce code sous Linux sur un système FC16 (et corrigé mon type-o initial).
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()
J'espère que cela aidera la prochaine personne.
Voici la solution pour arrêter Torando d'un autre thread. Schildmeijer a fourni un bon indice, mais il m'a fallu un certain temps pour comprendre l'exemple final qui fonctionne.
Voir ci-dessous:
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()
Au cas où vous ne voudriez pas vous embêter avec les threads, vous pourriez attraper un signal d'interruption du clavier:
try:
tornado.ioloop.IOLoop.instance().start()
# signal : CTRL + BREAK on windows or CTRL + C on linux
except KeyboardInterrupt:
tornado.ioloop.IOLoop.instance().stop()
Pour arrêter l'ioloop entier, vous appelez simplement l'ioloop.arrêter la méthode Lorsque vous avez terminé le test unitaire. (Rappelez-vous que la seule méthode sécurisée de thread (documentée) est ioloop.add_callback, c'est à dire. si les tests unitaires sont exécutés par un autre thread, vous pouvez envelopper l'appel stop dans un rappel)
Si c'est suffisant pour arrêter le serveur Web http, vous appelez le httpserver .méthode stop ()
Si vous avez besoin de ce comportement pour les tests unitaires, jetez un oeil à tornade.test.AsyncTestCase .
Par défaut, un nouvel IOLoop est construit pour chaque test et est disponible en tant que self. io_loop. cet IOLoop doit être utilisé dans la construction de clients/serveurs HTTP, etc. Si le code testé nécessite un ioloop global, les sous-classes doivent remplacer get_new_ioloop pour le renvoyer.
Si vous devez démarrer et arrêter un IOLoop à d'autres fins et que vous ne pouvez pas appeler ioloop.stop() à partir d'un rappel pour une raison quelconque, une implémentation multi-thread est possible. Pour éviter les conditions de course, cependant, vous devez synchroniser l'accès à l'ioloop, ce qui est en fait impossible. Quelque chose comme ce qui suit entraînera un blocage:
Fil 1:
with lock:
ioloop.start()
Fil 2:
with lock:
ioloop.stop()
Parce que le thread 1 ne relâchera jamais le verrou (start () bloque) et le thread 2 attendra que le verrou soit relâché pour arrêter l'ioloop.
La seule façon de le faire est pour que le thread 2 appelle ioloop.add_callback (ioloop.stop), qui appellera stop() sur le thread 1 dans la prochaine itération de la boucle d'événement. Rassurez-vous, ioloop.add_callback() est thread-safe.
Il y a un problème, avec la solution de Zaar Hai, à savoir qu'il laisse le socket ouvert. La raison pour laquelle je cherchais une solution pour arrêter Tornado est que j'exécute des tests unitaires sur mon serveur d'applications et que j'avais besoin d'un moyen de démarrer / arrêter le serveur entre les tests pour avoir un état clair (session vide, etc.). En laissant le socket ouvert, le deuxième test a toujours rencontré une erreur Address already in use
. Donc, je suis venu avec ce qui suit:
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()
, Donc l'idée principale ici est de garder une référence à la HTTPServer
exemple et appelez sa méthode stop()
. Et create_app()
renvoie simplement une instance Application
configurée avec des gestionnaires. Maintenant, vous pouvez utiliser ces méthodes dans vos tests unitaires comme ceci:
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
Ioloop de Tornado.instance () a du mal à s'arrêter à partir d'un signal externe lorsqu'il est exécuté sous multiprocessing.Processus.
La seule solution que j'ai trouvée qui fonctionne de manière cohérente , est en utilisant le processus.terminez ():
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'
Ajoutez simplement ceci avant le début ():
IOLoop.instance().add_timeout (10, IOLoop.instance().stop)
Il enregistrera la fonction stop en tant que rappel dans la boucle et la lancera 10 secondes après le début
Un multiprocessing.Process
avec tornado.ioloop.IOLoop
pour contourner le disponible GIL pour la performance et de l'indépendance. Pour accéder à L'IOLoop, nous devons utiliser Queue
pour passer le signal d'arrêt.
Voici un exemple minimaliste:
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()
Le Processus
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()
Cheers!