2013-11-16 3 views
10

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

Демонстрация моего обработчика запросов ниже.

 
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?

Заранее благодарен!

ответ

22

Украсьте свой метод 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.

+0

Привет, спасибо. Несколько вопросов: объясните, почему мой код не работает, а ваш? Кажется, что до некоторой мистической пользы «урожайности» с tornado.gen.coroutine. Как вы можете гарантировать, что после того, как вы представили список фьючерсов, чтобы запросы были полными, и безопасно писать «закрытие»? – majackson

+2

@majackson: То, как Торнадо реализует сопрограммы. Используя 'yield', ваша функция становится генератором, поэтому вы можете эффективно запускать и останавливать ее в тех точках, где вы« выполняете »задачи. Когда вы выполняете задание (или список задач, которые Tornado выполняет сразу), вы сообщаете IOLoop Tornado, что он может «остановить» выполнение вашей функции и сделать другие вещи. Когда он «вернется» к вашей функции, он начнется там, где он остановился. Код вашей функции по-прежнему будет выполняться сверху вниз, но Tornado также перейдет к другим функциям. Это похоже на то, как это делает Node.js. – Blender

Смежные вопросы