首页 > 解决方案 > 在 Jupyter Notebook 中运行 Tornado 服务器

问题描述

采用标准 Tornado 演示并将 IOLoop 推送到后台线程中,可以在单个脚本中查询服务器。当 Tornado 服务器是交互式对象时,这很有用(请参阅 Dask 或类似内容)。

import asyncio
import requests
import tornado.ioloop
import tornado.web

from concurrent.futures import ThreadPoolExecutor

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

pool = ThreadPoolExecutor(max_workers=2)
loop = tornado.ioloop.IOLoop()

app = make_app()
app.listen(8888)
fut = pool.submit(loop.start)

print(requests.get("https://localhost:8888"))

以上在标准 python 脚本中工作得很好(尽管它缺少安全关闭)。Jupyter notebook 是这些交互式 Tornado 服务器环境的最佳环境。然而,当涉及到 Jupyter 时,这个想法就被打破了,因为已经有一个活跃的运行循环:

>>> import asyncio
>>> asyncio.get_event_loop()
<_UnixSelectorEventLoop running=True closed=False debug=False>

在 Jupyter 笔记本中运行上述脚本时会出现这种情况,服务器和请求客户端都试图在同一个线程中打开连接并且代码挂起。构建一个新的 Asyncio 循环和/或 Tornado IOLoop 似乎没有帮助,我怀疑我在 Jupyter 本身中遗漏了一些东西。

问题:是否可以在 Jupyter 笔记本的后台运行实时 Tornado 服务器,以便标准 pythonrequests或类似的可以从主线程连接到它?如果可能的话,我宁愿在呈现给用户的代码中避免使用 Asyncio,因为它对于新手用户来说相对复杂。

标签: pythontornadojupyterpython-asynciodask

解决方案


根据我最近对 ​​streamz 的 PR,这里有一些可行的方法,类似于你的想法:

class InNotebookServer(object):
    def __init__(self, port):
        self.port = port
        self.loop = get_ioloop()
        self.start()

    def _start_server(self):
        from tornado.web import Application, RequestHandler
        from tornado.httpserver import HTTPServer
        from tornado import gen

        class Handler(RequestHandler):
            source = self

            @gen.coroutine
            def get(self):
                self.write('Hello World')

        application = Application([
            ('/', Handler),
        ])
        self.server = HTTPServer(application)
        self.server.listen(self.port)

    def start(self):
        """Start HTTP server and listen"""
        self.loop.add_callback(self._start_server)


_io_loops = []

def get_ioloop():
    from tornado.ioloop import IOLoop
    import threading
    if not _io_loops:
        loop = IOLoop()
        thread = threading.Thread(target=loop.start)
        thread.daemon = True
        thread.start()
        _io_loops.append(loop)
    return _io_loops[0]

在笔记本中调用

In [2]: server = InNotebookServer(9005)
In [3]: import requests
        requests.get('http://localhost:9005')
Out[3]: <Response [200]>

推荐阅读