Code example for asynchronous processing of Python Web framework Tornado
1. What is Tornado
Tornado is a lightweight but high-performance Python web framework. Compared with another popular Python web framework Django, tornado does not provide ORM interfaces for database operations and strict MVC development modes, but it can provide basic web server functions, so it is lightweight. It uses the non-blocking and event-driven I/O model (epoll or kqueue) it implements a set of Asynchronous Network Libraries, so it is high-performance.
Tornado's lightweight and high-performance features make it especially suitable for providing web APIs. if used properly, its non-blocking and asynchronous capabilities can address the C10K problem.
Note that due to Python GIL, multithreading is always a "characteristic" of Single-core execution. When tornado processes http requests, if the backend response of a request is blocked (for example, reading data from the database or disk causes a long processing time), other http requests will also be blocked, this will seriously drag down tornado's performance in high concurrency scenarios.
Fortunately, tornado provides the ability to process requests asynchronously. In asynchronous mode, we can pass in callback functions or use tornado. gen. the coroutine modifier allows tornado's internal io loop to still accept other http requests while waiting for the response results of the current request. This avoids the impact of a time-consuming operation On tornado's processing capability.
2. How to Write asynchronous processing code under tornado framework
The Tornado official documentation provides several simple examples of asynchronous code, but to be honest, the code is too simple (the Basic asynchronous syntax is displayed in the get or post function of the handler class of a uri), which has little practical significance.
In actual projects, complicated processing logic cannot be heap in the get or post functions, but is encapsulated in other classes for the get or post function calls of the handler class. Therefore, this article provides a slightly complex example to illustrate how to Implement Asynchronous processing logic in functions of other classes to achieve asynchronous processing of http requests.
Assume that the current requirement is to use tornado to implement a web server and support the uri method named cityhotel. When the client accesses the uri through an http GET request, the web server accesses the city specified by the query parameter, another back-end api that requests to store hotel detailed data. After business processing, it returns all the hotels in the city of a chain hotel to the client.
Assume that the url format of the client GET request is http: // host/api/hotel/cityhotel? City = xxx
Assume that the backend api interface for storing hotel detailed data is: http: // pai_backend/getCityHotels? City = xxx
According to the above scenario, since the web server implemented by tornado receives a request from the client, it also needs to request basic data through another API interface, and the latter will block tornado before returning, in this scenario, tornado is best to asynchronously request the API that provides the basic data to avoid uncontrollable backend dragging tornado's response performance.
Based on the business requirements described above, the following code demonstrates how to process services asynchronously.
Module entry file (main. py ):
#!/bin/env pythonimport tornado.ioloopimport tornado.webimport tornado.genimport hotelcoreclass CityHotelHandler(tornado.web.RequestHandler): @tornado.gen.coroutine def get(self): ## parse query params params = {} keys = ['city'] for key in keys: value = self.get_query_argument(key) params[key] = value (status, rsp) = yield hotelcore.HotelApiHandler.get_city_hotel(params['city']) if 200 == status: self.set_header('content-type', 'application/json') self.finish(rsp) else: self.set_status(404) self.finish()def main(): app_inst = tornado.web.Application([ (r'/api/hotel/cityhotel', CityHotelHandler), ], compress_response = True) app_inst.listen(8218) tornado.ioloop.IOLoop.current().start()if '__main__' == __name__: main()
The module that processes the business logic is encapsulated in the javascore. py file. The Code is as follows:
#!/bin/env python#-*- encoding: utf-8 -*-import jsonfrom tornado import genfrom tornado import httpclientclass HotelApiHandler(object): _cfg_dict = { 'api_host' : 'api.hotelbackend.com', } @classmethod @gen.coroutine def get_city_hotel(cls, city): ret = yield cls._parallel_fetch_city_hotel(city) raise gen.Return((200, ret)) @classmethod @gen.coroutine def _parallel_fetch_city_hotel(cls, city): base_url = 'http://%s/v1/getCityHotel' % (cls._cfg_dict['api_host']) ## hote type: 1=normal room; 2=deluxe room hotel_type = {'normal': 1, 'deluxe': 2} urls = [] for v in hotel_type.values(): api_url = '%s?city=%s&level=%s' % (base_url, city, v) urls.append(api_url) ## issue async http request http_clt = httpclient.AsyncHTTPClient() rsps_dict = yield dict(normal_room = http_clt.fetch(urls[0]), deluxe_room = http_clt.fetch(urls[1])) city_hotel_info = cls._parse_city_hotel(rsps_dict, city) ret = { } if len(city_hotel_info): ret['errno'] = 0 ret['errmsg'] = 'SUCCESS' ret['data'] = city_hotel_info else: ret['errno'] = 1 ret['errmsg'] = 'Service Not Found at This City' ret['data'] = '' raise gen.Return(ret) @classmethod def _parse_city_hotel(cls, rsp_dict, city): city_hotel_info = {} for hotel_level, rsp in rsp_dict.items(): rsp_json = json.loads(rsp.body) datas = rsp_json['data'] for city_id, city_detail in datas.items(): name = city_detail['name'] if city in name: city_hotel_info[hotel_level] = city_detail break return city_hotel_info
Some additional instructions on the above Code:
To compile tornado asynchronous processing code, you must be familiar with the decorator @ gen provided by tornado for Python's decorator syntax and generator/yield syntax. coroutine indicates that the decorated function is an asynchronous processing function. Calling this function will not block the main thread of tornado being @ gen. in the function decorated by coroutine, the time-consuming function that needs to be asynchronously executed is called using yield. yield itself returns a generator, combined with @ gen. after coroutine, it returns a Future-type object defined by tornado. during the execution of the function called by yield, the process control is returned to the main thread, so even if the function requires a longer running time, the main thread of tornado can continue to process other requests in Python 2. in the syntax of Version x, generator does not allow returning the return value of the function. The raise gen provided by tornado must be used. return (ret) to achieve the returned goal, which is a comparison of tri The cky ure object returned by the method yield of cky can obtain the return value of the function called by yield by calling the body attribute. As long as the above several points are understood, @ gen is returned. coroutine and yield have syntax meanings in tornado asynchronous programming. Therefore, writing complicated asynchronous calling code is different from coding synchronous calling code that implements the same functions but tornado's overall performance cannot be guaranteed, the implementation difficulty almost does not exist.
Many of the above Code's syntax details are not expanded, and we hope the Implementation ideas can help people. Pai_^