Запуск сервера Tornado в ноутбуке Jupyter - PullRequest
6 голосов
/ 17 марта 2019

Взяв стандартную демонстрацию 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 - оптимальная среда для этих интерактивных серверных сред Tornado.Тем не менее, когда дело доходит до Jupyter, эта идея рушится, так как уже существует активный рабочий цикл:

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

Это видно при запуске вышеуказанного скрипта в записной книжке Jupyter, и сервер, и клиент запросапытается открыть соединение в том же потоке и код зависает.Создание нового цикла Asyncio и / или IOLoop Tornado, похоже, не помогает, и я подозреваю, что я что-то упускаю в самом Jupyter.

Вопрос: можно ли в фоновом режиме запустить работающий сервер Tornado в фоновом режиме?Ноутбук Jupyter, чтобы стандартный питон requests или аналогичный мог подключиться к нему из основного потока?Я бы предпочел избегать Asyncio в коде, представленном пользователям, если это возможно, из-за его относительной сложности для начинающих пользователей.

Ответы [ 3 ]

1 голос
/ 20 марта 2019

Часть 1: Пусть получат вложенные торнадо (ы)

Чтобы найти нужную вам информацию, следуйте приведенным ниже инструкциям, начните с того, что описано в примечаниях к выпуску IPython 7 В частности, он укажет вам больше информации об асинхронных и ожидающих разделах в документации и этом обсуждении , которые предлагают использовать nest_asyncio .

Суть заключается в следующем:

  • A) либо вы обманываете python для запуска двух вложенных циклов событий. (что делает nest_asyncio)
  • B) Вы запланируете сопрограммы в уже существующем цикле событий. (Я не уверен, как это сделать с торнадо)

Я почти уверен, что вы все это знаете, но я уверен, что другие читатели это оценят.

К сожалению, нет способов сделать его полностью прозрачным для пользователей - хорошо, если вы не управляете развертыванием, как на jupyterhub, и не можете добавить эти строки в сценарии запуска IPython, которые автоматически загружаются. Но я думаю, что следующее достаточно просто.

import nest_asyncio
nest_asyncio.apply()


# rest of your tornado setup and start code.

Часть 2: Gotcha Синхронный кодовый блок eventloop.

Предыдущий раздел посвящен только запуску приложения торнадо. Но обратите внимание, что любой синхронный код заблокирует EventLop ; таким образом, при запуске print(requests.get("http://localhost:8000")) сервер будет работать не так, как вы блокируете цикл событий, который будет перезапущен только после завершения выполнения кода, который ожидает перезапуска цикла событий ... (понимание, что это упражнение, оставленное читателю ). Вам нужно либо выпустить print(requests.get("http://localhost:8000")) из другого ядра , либо использовать aiohttp.

Вот как использовать aiohttp аналогично запросам.

import aiohttp
session =  aiohttp.ClientSession()
await session.get('http://localhost:8889')

В этом случае, поскольку aiohttp не блокирует, все будет работать правильно. Здесь вы можете увидеть дополнительную магию IPython, где мы автоматически определяем асинхронный код и запускаем его в текущем цикле событий.

Классным упражнением может быть запуск request.get в цикле в другом ядре и запуск sleep(5) в ядре, где работает торнадо, и мы видим, что мы прекращаем обработку запросов ...

Часть 3: Отказ от ответственности и другие маршруты:

Это довольно хитроумно , и я бы посоветовал не использовать его в производстве и предупредить ваших пользователей, что это не рекомендуемый способ действий.

Это не полностью решает ваш случай, вам нужно будет запустить вещи не в главном потоке, что, я не уверен, возможно.

Вы также можете попробовать поиграть с другими бегунами петель, такими как trio и curio ; они могут позволить вам делать вещи, которые вы не можете по умолчанию с помощью asyncio, например, вложения, но здесь будут драгуны . Я настоятельно рекомендую трио и несколько постов в блоге о его создании , особенно если вы учите асинхронности.

Наслаждайтесь, надеюсь, что это помогло, и, пожалуйста, сообщайте об ошибках, а также о том, что сработало.

1 голос
/ 23 марта 2019

Основываясь на моем недавнем пиаре на streamz , вот что работает, похоже на вашу идею:

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]>
1 голос
/ 19 марта 2019

Вы можете запустить сервер торнадо в фоновом режиме, используя магическую команду %%script --bg.Опция --bg указывает jupyter запускать код текущей ячейки в фоновом режиме.

Просто создайте сервер торнадо в одной ячейке вместе с магической командой и запустите эту ячейку.

Пример:

%%script python --bg

import tornado.ioloop
import tornado.web

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

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

loop = tornado.ioloop.IOLoop.current()

app = make_app()
app.listen(8000) # 8888 was being used by jupyter in my case

loop.start()

И затем вы можете использовать requests в отдельной ячейке для подключения к серверу:

import requests

print(requests.get("http://localhost:8000"))

# prints <Response [200]>

Здесь следует отметить, что если вы остановитесь / прервитесьядро в любой ячейке, фоновый скрипт также остановится.Поэтому вам придется снова запустить эту ячейку, чтобы запустить сервер.

...