私は Tornado Webサーバー で少し遊んでいて、Webサーバーを停止したいところまで来ました(たとえば、単体テスト中)。次の簡単な例 トルネードWebページに存在 :
_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
オブジェクトの ソースコード を読み取ると、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)
_
ただし、次のテスト設定でも、次のテストで実行されているため、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サーバーを起動および停止するにはどうすればよいですか?
私はこれに遭遇し、この問題を自分で見つけました。このスレッドの情報を使用すると、次のことがわかりました。私は単に作業中のスタンドアロンのTornadoコード(すべての例からコピーしたもの)を取り、実際の開始コードを関数に移動しました。次に、その関数をスレッドスレッドとして呼び出しました。スレッド呼び出しが、startTornadoルーチンとstopTornadoルーチンをインポートした既存のコードから行われたため、私のケースは異なります。
上記の提案はうまく機能しているようだったので、足りないコード例を提供しようと思いました。 FC16システムのLinuxでこのコードをテストしました(そして最初のtype-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()
これが次の人に役立つことを願っています。
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()
スレッドに煩わされたくない場合は、キーボードの割り込み信号をキャッチできます。
try:
tornado.ioloop.IOLoop.instance().start()
# signal : CTRL + BREAK on windows or CTRL + C on linux
except KeyboardInterrupt:
tornado.ioloop.IOLoop.instance().stop()
Zaar Haiのソリューションには問題があります。つまり、ソケットが開いたままになります。トルネードを停止するソリューションを探していた理由は、アプリサーバーに対してユニットテストを実行していて、テスト間でサーバーを開始/停止して状態をクリアにする(空のセッションなど)必要があるためです。ソケットを開いたままにすると、2番目のテストで常に_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
_
Ioloop全体を停止するには、単体テストが終了したときにioloop.stopメソッドを呼び出すだけです。 (唯一の(文書化された)スレッドセーフメソッドはioloop.add_callbackであることを思い出してください。つまり、ユニットテストが別のスレッドによって実行される場合、stop呼び出しをコールバックにラップできます)
Http Webサーバーを停止するのに十分であれば、 httpserver 。stop()メソッドを呼び出します
単体テストでこの動作が必要な場合は、 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 です。
TornadoのIOloop.instance()は、multiprocessing.Processの下で実行すると、外部信号から停止するのに問題があります。
私が思いついた唯一の解決策consistentlyは、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'
これをstart()の前に追加するだけです:
IOLoop.instance()。add_timeout(10、IOLoop.instance()。stop)
ループ内で停止関数をコールバックとして登録し、開始から10秒後に起動します
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()
乾杯!