Search code examples
pythontornado

tornado use AsyncHTTPClient and gen to request a url, use raise gen.Return gets Exceptions


I'm new to tornado, so I follow the guide of tornado to practice, when I come to use Coroutines, the example says: from tornado import gen

@gen.coroutine
def fetch_coroutine(url):
    http_client = AsyncHTTPClient()
    response = yield http_client.fetch(url)
    # In Python versions prior to 3.3, returning a value from
    # a generator is not allowed and you must use
    #   raise gen.Return(response.body)
    # instead.
    return response.body

when I run this test, it's raise SyntaxError 'return' with argument inside generator, so I uncomment the advice,like this: import tornado.httpserver import tornado.ioloop import tornado.options import tornado.web

from tornado.options import define, options

define("port", default=8888, help="run on the given port", type=int)
from tornado import gen
from tornado.httpclient import AsyncHTTPClient

@gen.coroutine
def fetch_coroutine(url):
    http_client = AsyncHTTPClient()
    response = yield http_client.fetch(url)
    #return response.body
    raise gen.Return(response.body)

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")
        data = fetch_coroutine(url = "http://www.baidu.com")
        self.write(data)
        print data


def main():
    tornado.options.parse_command_line()
    application = tornado.web.Application([
        (r"/", MainHandler),
    ])
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()


if __name__ == "__main__":
    main()

but it raise exception like this:

[E 140925 17:35:53 web:1407] Uncaught exception GET / (::1)
    HTTPServerRequest(protocol='http', host='localhost:8888', method='GET', uri='/', version='HTTP/1.1', remote_ip='::1', headers={'Accept-Language': 'zh-TW,zh;q=0.8,zh-CN;q=0.6,en;q=0.4', 'Accept-Encoding': 'gzip,deflate,sdch', 'Host': 'localhost:8888', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36', 'Connection': 'keep-alive', 'Cache-Control': 'max-age=0', 'If-None-Match': '"e02aa1b106d5c7c6a98def2b13005d5b84fd8dc8"'})
    Traceback (most recent call last):
      File "/Library/Python/2.7/site-packages/tornado-4.0.2-py2.7-macosx-10.9-intel.egg/tornado/web.py", line 1332, in _execute
        result = method(*self.path_args, **self.path_kwargs)
      File "helloworld.py", line 39, in get
        self.write(data)
      File "/Library/Python/2.7/site-packages/tornado-4.0.2-py2.7-macosx-10.9-intel.egg/tornado/web.py", line 656, in write
        raise TypeError("write() only accepts bytes, unicode, and dict objects")
    TypeError: write() only accepts bytes, unicode, and dict objects
[E 140925 17:35:53 web:1811] 500 GET / (::1) 3.94ms

Solution

  • Documentation has a very similar example:

    class GenAsyncHandler(RequestHandler):
        @gen.coroutine
        def get(self):
            http_client = AsyncHTTPClient()
            response = yield http_client.fetch("http://example.com")
            do_something_with_response(response)
            self.render("template.html")
    

    In summary:

    • In python 2.x you can't return something inside block, decorated with @gen.coroutine, only single return is allowed
    • In python 2.x, if you need to return something inside @gen.coroutine block, then use raise gen.Return, as you correctly understand
    • You don't need to return anything in get method to respond some data to client. You need to write something (in example above it is done by calling self.render) for it.

    So, replace this code

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

    with this:

            self.write(response.body)
            self.finish()
    

    self.finish() will finish response, ending the HTTP request. It is called automatically in self.render method.

    Final request handler:

    from tornado import gen
    from tornado.web import RequestHandler
    from tornado.httpclient import AsyncHTTPClient
    
    
    class GenAsyncHandler(RequestHandler):
        @gen.coroutine
        def get(self):
            http_client = AsyncHTTPClient()
            response = yield http_client.fetch("http://google.com")
            self.write(response.body)
            self.finish()