Tornado потокового HTTP-ответа, поскольку AsyncHTTPClient получает куски

Я пытаюсь написать обработчик запросов Tornado, который делает асинхронные HTTP-запросы, и возвращает данные клиенту, когда он получает его от асинхронных запросов. К сожалению, я не могу заставить Tornado возвращать любые данные клиенту до тех пор, пока не завершится все его HTTP-запросы Async.

Ниже приведено описание моего обработчика запросов.

class StreamingHandler(web.RequestHandler):

    all_requested = False
    requests = []

    @web.asynchronous
    def get(self):

        http_client = httpclient.AsyncHTTPClient()
        self.write('some opening')

        big_request = httpclient.HTTPRequest(url='[some_big_request]', streaming_callback=self.on_chunk)
        small_request = httpclient.HTTPRequest(url='[some_small_request]', streaming_callback=self.on_chunk)

        self.requests.append(http_client.fetch(big_request, callback=self.on_response_complete))
        self.requests.append(http_client.fetch(small_request, callback=self.on_response_complete))

        self.all_requested = True

    def on_chunk(self, chunk):
        self.write('some chunk')
        self.flush()

    def on_response_complete(self, response):
        if self.all_requested and all(request.done() for request in self.requests):
            self.write('some closing')
            self.finish()

Я бы ожидал запроса GET этому обработчику, чтобы изначально вернуть текст "некоторое открытие", а затем довольно быстро вернуть "некоторый кусок" для небольшого запроса и позже вернуть "некоторый кусок" (потенциально несколько раз) для большего запрос, прежде чем, наконец, вернуть "некоторое закрытие" и закрыть соединение. Вместо этого, после подключения, клиент ждет несколько секунд для завершения всех запросов, а затем получает все HTTPResponse сразу, перед закрытием.

Как я могу получить желаемое поведение от Tornado?

Спасибо заранее!

Ответ 1

Украсьте свой метод с помощью gen.coroutine и создайте список фьючерсов. Вот простой пример:

from tornado import gen, web, httpclient

class StreamingHandler(web.RequestHandler):
    @web.asynchronous
    @gen.coroutine
    def get(self):
        client = httpclient.AsyncHTTPClient()

        self.write('some opening')
        self.flush()

        requests = [
            httpclient.HTTPRequest(
                url='http://httpbin.org/delay/' + str(delay),
                streaming_callback=self.on_chunk
            ) for delay in [5, 4, 3, 2, 1]
        ]

        # `map()` doesn't return a list in Python 3
        yield list(map(client.fetch, requests))

        self.write('some closing')
        self.finish()

    def on_chunk(self, chunk):
        self.write('some chunk')
        self.flush()

Обратите внимание, что даже если запросы получаются "назад", первый кусок все равно будет получен примерно через секунду. Если вы отправили их синхронно, вам понадобится 15 секунд. Когда вы запрашиваете их асинхронно, это занимает всего 5.