Tornado 实践:简介及初步使用Tornado

1、Tornado 是什么

Tornado 是一个 Python Web 框架和异步网络库,最初由 FriendFeed 开发。 通过使用非阻塞网络 I/O,Tornado 可以扩展到成千上万的开放连接,非常适合长时间轮询,WebSocket 和需要与每个用户建立长期连接的其他应用程序。Tornado 出现的目的在于解决高并发场景下的稳定性和性能问题。Tornado 提供了一个 HttpServer,在 Tornado 6.x 版本中,使用了最新的异步 IO 库,使该服务器的性能大大提高。它使用非阻塞网络 I/O 并解决 C10k 问题(意思就是,单机下它可以处理 10,000 多个并发连接)。

Tornado 和 Django、Flask 称为 Python Web 开发三大框架,Tornado 和其他两个框架存在明显的区别,就是它的非阻塞网络 IO。Tornado 和 Flask 存在一些共同点,首选就是它们都很“微”、很轻量,符合现在的微服务架构设计,在 Tornado 中没有默认提供模板引擎,这就意味着你更多的时候是使用 Tornado API 接口,也就是常说的 RESTfulAPI。当然你可以使用第三方的模板引擎来扩展你的应用。但我还是建议你用 Tornado 开发纯后端应用更好一些。

日常工作中我们使用 Tornado 可以开发哪些类型的应用呢?以下是笔者常用的场景:

  1. 作为异步 Http 客户端来发送请求或写异步爬虫。
  2. 作为 WebSocket 网络框架来开发 Websocket 服务端程序,就是常说的聊天室应用。
  3. 作为高并发、高性能的 HttpServer 来提供服务,结合 uwsgi 来构建更优秀的服务器。
  4. 作为 Web 框架,来开发高并发、高性能的 Web 应用或接口。
  5. 作为一个协程库来使用,构建协程并发的应用。

2、小 Demo 快速上手 Tornado

本节将使用几个简单的小例子带你上手进行 Tornado 开发,笔者的开发环境是 Linux 系统、Python 3.6+、MySQL 5.7、Redis 6.0 。注意,本节中 Python 3.6+ 开发环境是必须的,至于开发系统使用 Mac 或 Windows 都可以。MySQL、Redis 本节暂时不需要,后边的开发中需要使用,因此可以后面再安装,注意版本号尽量一致。如果因为版本不同导致的异常,请小伙伴们自行百度或谷歌解决。

安装 Tornado

pip install tornado==6.0.4

使用 Tornado 异步 HTTP 客户端写一个小爬虫
主要代码如下:

# async_http_cilent.py
import time
from datetime import timedelta
from html.parser import HTMLParser
from urllib.parse import urljoin, urldefrag
from tornado import gen, httpclient, ioloop, queues


base_url = "http://www.tornadoweb.org/en/stable/"
concurrency = 10


async def get_links_from_url(url):
    """Download the page at `url` and parse it for links.

    Returned links have had the fragment after `#` removed, and have been made
    absolute so, e.g. the URL 'gen.html#tornado.gen.coroutine' becomes
    'http://www.tornadoweb.org/en/stable/gen.html'.
    """
    response = await httpclient.AsyncHTTPClient().fetch(url)
    print("fetched %s" % url)

    html = response.body.decode(errors="ignore")
    return [urljoin(url, remove_fragment(new_url)) for new_url in get_links(html)]


def remove_fragment(url):
    pure_url, frag = urldefrag(url)
    return pure_url


def get_links(html):
    class URLSeeker(HTMLParser):
        def __init__(self):
            HTMLParser.__init__(self)
            self.urls = []

        def handle_starttag(self, tag, attrs):
            href = dict(attrs).get("href")
            if href and tag == "a":
                self.urls.append(href)

    url_seeker = URLSeeker()
    url_seeker.feed(html)
    return url_seeker.urls


async def main():
    q = queues.Queue()
    start = time.time()
    fetching, fetched = set(), set()

    async def fetch_url(current_url):
        if current_url in fetching:
            return

        print("fetching %s" % current_url)
        fetching.add(current_url)
        urls = await get_links_from_url(current_url)
        fetched.add(current_url)

        for new_url in urls:
            # Only follow links beneath the base URL
            if new_url.startswith(base_url):
                await q.put(new_url)

    async def worker():
        async for url in q:
            if url is None:
                return
            try:
                await fetch_url(url)
            except Exception as e:
                print("Exception: %s %s" % (e, url))
            finally:
                q.task_done()

    await q.put(base_url)

    # Start workers, then wait for the work queue to be empty.
    workers = gen.multi([worker() for _ in range(concurrency)])
    await q.join(timeout=timedelta(seconds=300))
    assert fetching == fetched
    print("Done in %d seconds, fetched %s URLs." % (time.time() - start, len(fetched)))

    # Signal all the workers to exit.
    for _ in range(concurrency):
        await q.put(None)
    await workers


if __name__ == "__main__":
    io_loop = ioloop.IOLoop.current()
    io_loop.run_sync(main)

运行服务(注意:如果使用了虚拟环境,要在虚拟环境内运行命令):

python async_http_cilent.py

3、使用 Tornado Web 框架写一个小 Web 应用

本节将使用 Tornado 的 Web 框架,编写一个小服务端应用,这也是 RESTfulAPI 接口的雏形,本节会实现一个接口,路径是 /test/ 请求该路径时,应用会返回一串 JSON 字符串,主要的 massage 是 Hello World。

# tornado_server.py
import tornado.ioloop
import tornado.web
import asyncio
import time


class BaseHandler(tornado.web.RequestHandler):
    '''
    请求的基类,继承自 Tornado 的 web 框架的 RequestHandler
    '''
    def __init__(self, application, request, **kwargs):
        super().__init__(application, request, **kwargs)
        print('[%s]' % (time.strftime("%Y-%m-%d %H:%M:%S")),request.version,request.remote_ip,request.method,request.uri)


class TestHandler(BaseHandler):
    async def get(self, *args, **kwargs):
        res_format = {
    
    "message": "ok", "errorCode": 0, "data": {
    
    }}
        try:
            await asyncio.sleep(2) # 会将程序暂停两秒
            res = mul.delay(3,5)
            res_format['message'] = 'Hello World'
            return self.finish(res_format)
        except Exception as e:
            print('出现异常:%s' % str(e))
            return self.finish({
    
    "message": "出现无法预料的异常:{}".format(str(e)), "errorCode": 1, "data": {
    
    }})


def make_app():
    return tornado.web.Application([
        (r"/test/", TestHandler),
    ], debug=True)


if __name__ == "__main__":
    print("""Wellcome...
Starting development server at http://%s:%s/       
Quit the server with CTRL+C.""" % ('127.0.0.1', '8080'))
    app = make_app()
    app.listen(8080) # 设置监听的端口号,默认监听地址是 localhost
    tornado.ioloop.IOLoop.current().start()

运行服务:

python tornado_server.py

使用 curl 命令进行测试:

$ curl -H "Content-Type: application/json" http://127.0.0.1:8080/test/
$ {
    
    "message": "Hello World", "errorCode": 0, "data": {
    
    }}

使用 Postman 进行测试:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/haeasringnar/article/details/108296238
今日推荐