Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tornado Async HTTP returning results incrementally

From what I understand from tornado.gen module docs is that tornado.gen.Task comprises of tornado.gen.Callback and tornado.gen.Wait with each Callback/Wait pair associated with unique keys ...

  @tornado.web.asynchronous
  @tornado.gen.engine
  def get(self):
      http_client = AsyncHTTPClient()
      http_client.fetch("http://google.com",
                        callback=(yield tornado.gen.Callback("google")))

      http_client.fetch("http://python.org",
                        callback=(yield tornado.gen.Callback("python")))

      http_client.fetch("http://tornadoweb.org",
                        callback=(yield tornado.gen.Callback("tornado")))
      response = yield [tornado.gen.Wait("google"), tornado.gen.Wait("tornado"), tornado.gen.Wait("python")]

      do_something_with_response(response)
      self.render("template.html")

So the above code will get all responses from the different URLs. Now what I actually need to accomplish is to return the response as soon as one http_client returns the data. So if 'tornadoweb.org' returns the data first, it should do a self.write(respose) and a loop in def get() should keep waiting for other http_clients to complete. Any ideas on how to write this using tornado.gen interface.

Very vague implementation(and syntactically incorrect) of what I am trying to do would be like this

class GenAsyncHandler2(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.engine
    def get(self):
        http_client = AsyncHTTPClient()
        http_client.fetch("http://google.com",
                          callback=(yield tornado.gen.Callback("google")))

        http_client.fetch("http://python.org",
                          callback=(yield tornado.gen.Callback("python")))

        http_client.fetch("http://tornadoweb.org",
                          callback=(yield tornado.gen.Callback("tornado")))

        while True:
            response = self.get_response()
            if response:
                self.write(response)
                self.flush()
            else:
                break
        self.finish()


    def get_response(self):
        for key in tornado.gen.availableKeys():
            if key.is_ready:
                value = tornado.gen.pop(key)
                return value
        return None
like image 561
pranjal Avatar asked Aug 10 '12 11:08

pranjal


2 Answers

It's case, when you shouldn't use inline callbacks, i.e gen. Also self.render will be called after all callbacks finished. If you want to return response from server partially - render it partially.

Think this way(it's only idea with big room of improvement):

  response = []
  @tornado.web.asynchronous
  def get(self):
      self.render('head.html')
      http_client = AsyncHTTPClient()

      http_client.fetch("http://google.com",
                        callback=self.mywrite)

      http_client.fetch("http://python.org",
                        callback=self.mywrite)

      http_client.fetch("http://tornadoweb.org",
                        callback=self.mywrite)

      self.render('footer.html')
      self.finish()


  def mywrite(self, result):
      self.render('body_part.html')
      self.response.add(result)
      if len(self.response) == 3:
        do_something_with_response(self.response)
like image 158
Nikolay Fominyh Avatar answered Nov 14 '22 22:11

Nikolay Fominyh


In addition to this, actually there is a method WaitAll which waits for all results and returns when all HTTPCliens have completed giving responses. I have submitted the diff in my tornado branch (https://github.com/pranjal5215/tornado). I have added a class WaitAny which is async WaitAll and returns result as soon as one HTTPClient has returned result.

Diff is at (https://github.com/pranjal5215/tornado/commit/dd6902147ab2c5cbf2b9c7ee9a35b4f89b40790e), (https://github.com/pranjal5215/tornado/wiki/Add-WaitAny-to-make-WaitAll-return-results-incrementally)

Sample usage:

class GenAsyncHandler2(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.engine
    def get(self):
        http_client = AsyncHTTPClient()
        http_client.fetch("http://google.com",
                          callback=(yield tornado.gen.Callback("google")))

        http_client.fetch("http://python.org",
                          callback=(yield tornado.gen.Callback("python")))

        http_client.fetch("http://tornadoweb.org",
                          callback=(yield tornado.gen.Callback("tornado")))
        keys = set(["google", "tornado", "python"])
        while keys:
            key, response = yield tornado.gen.WaitAny(keys)
            keys.remove(key)
            # do something with response
            self.write(str(key)+"        ")
            self.flush()
        self.finish() 
like image 21
pranjal Avatar answered Nov 14 '22 23:11

pranjal