【python】web应用——Tornado

文章目录

1 Intro

1.1 一些概念

1.1.1 基本特性/优势

web框架和异步网络库,由于使用了异步网络I/O,Tornado可以维持数以万计的开放连接,这适合长轮循,WebSocket和其他需要对客户保持长期活跃连接的应用。

1.1.2 Tornado三个组件

• web 框架(RequestHandler)
• HTTP客户端和服务端的实现(HTTPServer和AsyncHTTPClient)
• 异步网络库,包括IOLoop和IOStream,可以作为实现HTTP组件的构建模块,也可以用于实现其他协议。

1.1.3 Tornado和WSGI

WSGI是一个同步接口,而Tornado是一个基于单线程的异步执行的并发模型,许多Tornado突出的特性在WSGI模型中不可用(长轮循和websockets)。不过,HTTP Server和web 框架一起实现了一种全栈的WSGI,可以使用Tornado的作为容器(WSGIContainer)的HTTP Server来搭配其他WSGI框架。Tornado提供WSGIContainer在单线程中支持WSGI应用和原生的Tornado RequestHandlers。WSGI应用最好搭配专门的WSGI服务器(gunicorn,uwsgi)使用。

1.2 安装

• tornado为类Unix系统设计,最好在类Unix环境中执行。Windows环境不支持Tornado部分特性

pip install tornado

1.3 hello world

# handler/hello.py
import tornado

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world!")


# routing.py
from handler.hello import MainHandler

routing = [
    (r"/", MainHandler),
]


# web.py
import asyncio
import tornado

from routing import routing

def make_app():
    return tornado.web.Application(
        routing
    )

async def main():
    app = make_app()
    app.listen(8888)
    await asyncio.Event().wait()

if __name__ == "__main__":
    asyncio.run(main())

1.4 补充

  • 实时web特性要求对每个用户维持一个几乎空闲的长连接,在传统的同步web server中,为实现这个特性将为每个用户单独开一个线程,开销很大。Tornado使用单线程事件循环来最小化并发连接的开销。
  • 函数阻塞的原因:网络I/O,硬盘I/O,互斥量。
  • 异步的接口:回调参数,返回占位符(Future,Promise,Deferred),投递到队列,回调注册。Tornado中的异步操作主要使用两种接口:一种是占位符(Futures),一种是IOLoop使用的回调。

2 web 框架

2.1 主协程

主协程main由asyncio.run()启动,且主协程mian运行结束则整个web应用退出执行。注意主协程中用asyncio.Event()创建了一个事件shutdown_event,并在在shutdown_event上一直阻塞,直到事件循环中某个协程主动调用asyncio.Event.set(shutdown_event),那么主协程main退出,整个事件循环终止。

import asyncio
import tornado

from routing import routing

def make_app():
    return tornado.web.Application(
        routing
    )

async def fun(shutdown_event):
    await asyncio.sleep(3)
    asyncio.Event.set(shutdown_event)

async def main():
    app = make_app()
    app.listen(8888)
    shutdown_event = asyncio.Event()
    asyncio.create_task(fun(shutdown_event))
    await shutdown_event.wait()

if __name__ == "__main__":
    asyncio.run(main())

2.2 Application类

tornado.web.Application对象负责全局配置,如路由表(映射请求和handler)。路由表是一个列表,列表每一项包含URLSpec对象和一个RequestHandler对象。
初始化URLSpec对象,第一个参数是路径正则表达式(正则表达式中的捕获的分组将作为路径参数传递给RequestHadnler中定义的HTTP方法),第二个参数是RequestHandler,第三个参数是用于给RequestHandler对象初始化的字典,第四个参数是名称。

from tornado.web import url

app = Application([
    url(r"/", MainHandler),
    url(r"/story/([0-9]+)", StoryHandler, dict(db=db), name="story")
    ])

2.3 RequestHandler类

RequestHandler类的使用方式是继承它并重写以HTTP方法命名的成员函数,如get(),post()。在handler中,调用RequestHandler.render或者 RequestHandler.write来产生响应,注意在请求处理的全流程中,write方法可以多次调用来产生响应内容,并非是调用一次就终止全部处理流程。render()通过名称加载一个模板并且用给定参数渲染它。write()用于非模板输出(字符串,字节,JSON格式的字典)。
比较通用的方法是继承RequestHandler定义一个BaseHandler来重写诸如write_error,get_current_user等共用方法,然后再继承你自己的BaseHandler来构建应用。

2.3.1 请求处理的调用过程

initialize() -> prepare() -> HTTP method -> on_finish,在prepare中调用finish或者redirect将终止进一步处理。

2.3.2 一些成员(待补充)

RequestHandler.request来表示当前请求,RequestHandler.current_user表示当前用户。

2.3.3 一些方法(待补充)

  • write_error:为错误页面产出HTML
  • on_connection_close:当客户端失去连接时调用,应用可能探测连接情况并在失去连接后终止进一步处理,同时一些释放资源的操作也可以放在这里来做。不保证客户端一失去连接就检测出来,然后调用之。
  • get_current_user:查看用户权限
  • get_user_locale:返回当前用户的Locale对象(用于为用户管理当前语言和地区设置)
  • set_default_headers:可用于设置额外的响应头

2.3.4 例子:重写prepare

tornado本身不解析JSON请求体,如果Content-Type为application/json,则可以在self.prepare方法中预先使用json内置库处理self.request.body

def prepare(self):
    if self.request.headers.get("Content-Type", "").startswith("application/json"):
        self.json_args = json.loads(self.request.body)
    else:
        self.json_args = None

2.4 获取请求参数

2.4.1 参数

http://localhost:8080/info/:uid?subject=Math&examType=其中
uid是路径参数(path argument),http://localhost:8080/info/:uid 定位了资源位置。整个url中,? 后面的部分是查询参数(query argument),查询参数subject的值为"Math",查询参数examType的值为"期中",查询参数之间用&分隔。

2.4.2 获取参数列表

使用get_query_arguments(name)获取查询参数列表,使用get_body_arguments()获取放在请求体中的参数列表。比如,http://localhost:8080/info/:uid?subject=Math&subject=English,调用get_query_arguments(“subject”),返回[‘Math’, ‘English’]。

2.4.3 获取某个参数

使用self.get_query_argument(name)获取名为name的查询参数,使用self.get_body_argument(name)获取名为name的请求体参数。下面的handler例子中,浏览器访问localhost:8888/my/form,get()方法先被调用,将html写入响应。响应页面中呈现一个简单的输入框和submit按钮,在输入框输入文本后点击submit按钮,则浏览器发起一个post请求,然后post()方法被调用。通过self.get_body_argument(“message”)来获取post请求的参数。"message"是form标签中的属性名。

routing = [
    (r"/", MainHandler),
    (r"/my/form", MyFormHandler),
]

class MyFormHandler(tornado.web.RequestHandler):
    def get(self):
        self.write('<html><body><form action="/my/form" method="POST">'
                   '<input type="text" name="message">'
                   '<input type="submit" value="Submit">'
                   '</form></body></html>')

    def post(self):
        print('xxx')
        self.set_header("Content-Type", "text/plain")
        self.write("You wrote " + self.get_body_argument("message"))

2.5 文件上传

2.5.1 方式一:通过表单参数上传文件

self.request.files为字典,键为请求参数名,值为文件列表,文件列表的每项是一个字典,结构为{“filename”:…, “content_type”:…, “body”:…}。如上图,self.request.files={‘file’: [{‘filename’: ‘file0’, ‘content-type’: ‘xxx’, ‘body’: ‘xxx’}, {‘filename’: ‘file1’, ‘content-type’: ‘xxx’, ‘body’: ‘xxx’}]}。注意,只有请求头的Content-Type设置为multipart/form时,才可用。

2.5.2 方式二:通过请求体上传文件

如果文件通过self.request.body来保存上载的数据,则上载的文件将默认全部缓存在内存中。如果需要处理大文件,可以考虑使用stream_request_body类装饰器。

import asyncio
import logging
from urllib.parse import unquote

import tornado
from tornado import options

@tornado.web.stream_request_body
class PUTHandler(tornado.web.RequestHandler):
    def initialize(self):
        self.bytes_read = 0

    def data_received(self, chunk):
        self.bytes_read += len(chunk)

    def put(self, filename):
        filename = unquote(filename)
        mtype = self.request.headers.get("Content-Type")
        logging.info('PUT "%s" "%s" %d bytes', filename, mtype, self.bytes_read)
        self.write("OK")

2.6 错误处理

2.6.1 方式一:直接raise tornado.web.HTTPError

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        raise tornado.web.HTTPError(404, "Not Found!")

2.6.2 方式二:重写write_error来自定义error page

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        raise tornado.web.HTTPError(404, "Not Found!")

    def write_error(self, status_code, **kwargs):
        if status_code == 404:
            self.write("<h1>Oops! Page not found.</h1>")
        elif status_code == 500:
            self.write("<h1>Internal Server Error. Please try again later.</h1>")
        else:
            self.write("<h1>An error occurred.</h1>")         

2.6.3 方式三:设置status,写并返回响应

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        # Simulate an error condition
        self.set_status(404)  # Set HTTP status to 404 (Not Found)
        self.write("<h1>Oops! Page not found.</h1>") # Custom error message

2.6.4 tornado.web.Finish()

通常使用在请求生命周期中尽早返回响应的场景,比如鉴权,重定向等。

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        # Check for some condition
        user_authenticated = 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值