Tornado

一、Tornado 入门

1、前戏

(1) 什么是 Tornado

Tornado 是一个基于Python的Web服务框架和 异步网络库, 最早开发与 FriendFeed 公司. 通过利用非阻塞网络 I/O, Tornado 可以承载成千上万的活动连接, 完美的实现了 长连接WebSockets) 和其他对于每一位用户来说需要长连接的程序.

Tornado和现在的主流 Web 框架(包括大多数python框架)有着明显的区别:

他是非阻塞式服务器,而且速度相当快,的利于其非阻塞的方式和对 epoll 的运用,Tornado每秒可以处理数以千计的连接,因此Tornado是实时 Web 服务的一个理想框架,我们开发这个 Web 服务器的主要目的就是问了处理 FrienFeed 的实时功能——在 FrienFeed 的应用里每一个活动用户都会保持着一个服务器连接。

(2) Tornado 应用场景

  • 用户量大,高并发

    如秒杀抢购,双十一某宝购物,春节抢火车票

  • 大量的HTTP持久连接

    使用同一个TCP连接来发送和接收多个HTTP请求/应答,而不是为了每一个新的请求/应答打开新的连接的方法

    对于HTTP1.0可以在请求的包头(Header)中添加Connection: Keep-Alive

    对于HTTP1.1所有的连接默认都是持久连接

  • 对于这两种场景,通常基于多线程的服务器

(3) C10K

上面的高并发问题,通常C10K这一概念来描述。C10K-Conncurrently handing ten thousand connections, 即并发10000个连接。对于单台服务器而言,根本无法承担,而采用多台服务器分布式有意味着高昂的成本

Tornado在设计之初就考虑到了性能因素,旨在解决C10K问题,这样设计使得其成为一个拥有非常高性能的解决方案(服务器与框架的集合体)

(4) Tornado&Django

4.1 Djando

  • 注重高效开发
  • 全自动化的管理后台(只需要使用ORM,做简单的定义,就能自动生成数据库结构,全功能的管理平台)
  • session功能

4.2 Tornado

  • 注重性能优越,速度快
  • 解决高并发
  • 异步非阻塞
  • websockets 长链接
  • 内嵌HTTP服务器
  • 单线程的异步网络程序,默认启动时根据CPU数量运行多个实例;利用CPU多核的优势。

(5) Epoll

Epoll百度百科

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

(6) Tornado 安装

pip install tornado

2、第一个 Tornado 代码

(1) 方式一

import tornado.web
import tornado.ioloop


# 视图
class HelloHandler(tornado.web.RequestHandler):
    def get(self, *args, **kwargs):
        self.write('hello word')


# 路由
app = tornado.web.Application([
    (r'/hello', HelloHandler)
])

# 绑定端口
app.listen(8888)

# 启动监听
tornado.ioloop.IOLoop.instance().start()

(2) 方式二

import tornado.web
import tornado.ioloop

# 视图
class HelloHandler(tornado.web.RequestHandler):
    # 接收GET请求
    def get(self):
        self.write("Hello word!")

# 路由
app = tornado.web.Application([
    (r"/hello", HelloHandler),
])

if __name__ == '__main__':
    # 绑定端口
    app.listen(8888)
    # 启动监听
    tornado.ioloop.IOLoop.current().start()

(3) 方式三(官方)

import tornado.ioloop
import tornado.web

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

def make_app():
    return tornado.web.Application([
        (r"/hello", HelloHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

(4) 代码执行流程

  • 第一步:执行脚本,监听 8888 端口
  • 第二步:浏览器客户端访问 /hello --> http://127.0.0.1:8888/hello
  • 第三步:服务器接受请求,并交由对应的类处理该请求
  • 第四步:类接受到请求之后,根据请求方式(post / get / delete ...)的不同调用并执行相应的方法
  • 第五步:方法返回值的字符串内容发送浏览器

二、请求与响应

1、httpserver

一个非阻塞的单线程 HTTP 服务器。

(1) listen:简单的单进程

import tornado.ioloop
import tornado.web
import tornado.httpserver


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


def make_app():
    return tornado.web.Application([
        (r"/hello", HelloHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    server = tornado.httpserver.HTTPServer(app)
    server.listen(8888)
    tornado.ioloop.IOLoop.current().start()

(2) bind / start:简单的多进程

import tornado.ioloop
import tornado.web
import tornado.httpserver


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


def make_app():
    return tornado.web.Application([
        (r"/hello", HelloHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    server = tornado.httpserver.HTTPServer(app)
    server.bind(8888)
    server.start(1)
    tornado.ioloop.IOLoop.current().start()

(3) add_sockets:先进的多进程

# 注意 windows 上不支持
import tornado.ioloop
import tornado.web
import tornado.httpserver
import tornado.netutil
import tornado.process

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


def make_app():
    return tornado.web.Application([
        (r"/hello", HelloHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    sockets = tornado.netutil.bind_sockets(8888)
    tornado.process.fork_processes(0)
    server = tornado.httpserver.HTTPServer(app)
    server.add_sockets(sockets)
    tornado.ioloop.IOLoop.current().start()

2、options


3、路由

路由系统其实就是 url 和 类 的对应关系,这里不同于其他框架,其他很多框架均是 url 对应 函数,Tornado中每个url对应的是一个类。

import tornado.ioloop
import tornado.web
   
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")
   
class StoryHandler(tornado.web.RequestHandler):
    def get(self, story_id):
        self.write("You requested the story " + story_id)
   
class BuyHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("www.xingxing.com")
 
# 默认路由系统, 根据url的不容调用不同的类
application = tornado.web.Application([
    (r"/index", MainHandler),
    (r"/story/([0-9]+)", StoryHandler),
])

# 二级路由匹配
application.add_handlers('www.xingxing.com$', [
    (r'/index',BuyHandler),
])
   
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

3、请求和响应

3.1 请求相关

(1) 获取查询字符串参数

app.py

import tornado.web
import tornado.ioloop
import tornado.options

# 定义处理类
class LoginHandler(tornado.web.RequestHandler):
    # 接收GET请求
    def get(self, *args, **kwargs):
        """
        语法:get_query_argument(name, default='', strip=True)
        参数:
            name: 参数名,如果出现多个同名参数,则返回最后一个的值
            default: 默认值,为路由中未传name参数时返回的默认值,如default未设置,则会抛出tornado.web.MissingArgumentError异常;
            strip: 是否过滤掉左右两边的空白字符,默认为过滤。
        返回值:字符串

        语法:get_query_arguments(name, strip=True)
        从请求的查询字符串中返回指定参数name的值,注意返回的是list列表(即使对应name参数只有一个值),若未找到name参数,则返回空列表。
        返回值:列表
        """
        print(self.get_query_argument('name', default='', strip=True))
        print(self.get_query_argument('pwd', default='', strip=True))

        print(self.get_query_arguments('name', strip=True))
        print(self.get_query_arguments('pwd', strip=True))
        self.render("templates/login.html")


# 设置路由
application = tornado.web.Application([
    (r"/login", LoginHandler),
])

if __name__ == '__main__':
    # 打印请求信息(日志信息),不加这一行后端不会有日志信息
    tornado.options.parse_command_line()
    # 绑定端口
    application.listen(8888)
    # 启动监听
    tornado.ioloop.IOLoop.current().start()

templates/login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="get">
    <p>
        用户名:<input type="text" name="name">
    </p>
     <p>
        密码:<input type="password" name="pwd">
    </p>
     <p>
         <input type="submit" value="登录">
    </p>
</form>
</body>
</html>

(2) 获取请求体数据

app.py

import tornado.web
import tornado.ioloop
import tornado.options

# 定义处理类
class LoginHandler(tornado.web.RequestHandler):

    # 接收GET请求
    def get(self, *args, **kwargs):
        self.render("templates/login.html")

    def post(self, *args, **kwargs):
        """
        get_body_argument(name, default=_ARG_DEFAULT, strip=True)
            从请求体中返回指定参数name的值,如出现多个同名参数,则返回最后一个的值;
            default与strip同上。

        get_body_arguments(name, strip=True)
            从请求体中返回指定参数name的值,注意返回的是list列表(即使对应name参数只有一个值),若未找到name参数,则返回空列表。
            说明:对于请求体中数据为json或xml的,无法通过这两个方法获取。
        """
        print(self.get_body_argument('name', default='', strip=True))
        print(self.get_body_argument('pwd', default='', strip=True))

        print(self.get_body_arguments('name', strip=True))
        print(self.get_body_arguments('pwd', strip=True))
        self.write("登录成功")

# 设置路由
application = tornado.web.Application([
    (r"/login", LoginHandler),
])

if __name__ == '__main__':
    # 打印请求信息(日志信息),不加这一行后端不会有日志信息
    tornado.options.parse_command_line()
    # 绑定端口
    application.listen(8888)
    # 启动监听
    tornado.ioloop.IOLoop.current().start()

templates/login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    <p>
        用户名:<input type="text" name="name">
    </p>
     <p>
        密码:<input type="password" name="pwd">
    </p>
     <p>
         <input type="submit" value="登录">
    </p>
</form>
</body>
</html>

(3) 前两类方法的整合

app.py

import tornado.web
import tornado.ioloop
import tornado.options

# 定义处理类
class LoginHandler(tornado.web.RequestHandler):

    # 接收GET请求
    def get(self, *args, **kwargs):
        self.render("templates/login.html")

    def post(self, *args, **kwargs):
        """
        get_argument(name, default=_ARG_DEFAULT, strip=True)
        get_arguments(name, strip=True)
        说明:对于请求体中数据为json或xml的,无法通过这两个方法获取。
        这两个方法最常用
        """
        print(self.get_argument('name', default='', strip=True))
        print(self.get_argument('pwd', default='', strip=True))

        print(self.get_arguments('name', strip=True))
        print(self.get_arguments('pwd', strip=True))
        self.write("登录成功")

# 设置路由
application = tornado.web.Application([
    (r"/login", LoginHandler),
])

if __name__ == '__main__':
    # 打印请求信息(日志信息),不加这一行后端不会有日志信息
    tornado.options.parse_command_line()
    # 绑定端口
    application.listen(8888)
    # 启动监听
    tornado.ioloop.IOLoop.current().start()

templates/login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    <p>
        用户名:<input type="text" name="name">
    </p>
     <p>
        密码:<input type="password" name="pwd">
    </p>
     <p>
         <input type="submit" value="登录">
    </p>
</form>
</body>
</html>

(4) Tornado的Request对象属性

属性 说明
self.request.method http的请求头;get, post等
self.reuqest.uri 客户端请求完整的uri
self.request.path uri的路径名,不包含查询的字符串
self.request.query uri中的查询字符串
self.request.version http/1.1
self.request.headers 请求头
self.requset.body 字符串的消息
self.request.remote_ip 客户端请求的ip
self.request.protocol http的协议http or https
self.request.host 请求消息的主机名
self.request.files 以字典的方式表达客户端上传的文件
self.request.cookies 客户端的cookies字典
self.request.arguments 客户端提交的参数
import tornado.web
import tornado.ioloop
import tornado.options

# 定义处理类
class LoginHandler(tornado.web.RequestHandler):
    # 接收GET请求
    def get(self, *args, **kwargs):
        print("\n-------method:\n", self.request.method)
        print("\n-------uri:\n", self.request.uri)
        print("\n-------path:\n", self.request.path)
        print("\n-------query:\n", self.request.query)
        print("\n-------version:\n", self.request.version)
        print("\n-------headers:\n", self.request.headers)
        print("\n-------body:\n", self.request.body)
        print("\n-------remote_ip:\n", self.request.remote_ip)
        print("\n-------protocol:\n", self.request.protocol)
        print("\n-------host:\n", self.request.host)
        print("\n-------arguments:\n", self.request.arguments)
        print("\n-------query_arguments:\n", self.request.query_arguments)
        print("\n-------body_arguments:\n", self.request.body_arguments)
        print("\n-------files:\n", self.request.files)
        print("\n-------cookies:\n", self.request.cookies)
        self.render("templates/login.html")

# 设置路由
app = tornado.web.Application([
    (r"/login", LoginHandler
     
if __name__ == '__main__':
    # 打印请求信息(日志信息),不加这一行后端不会有日志信息
    tornado.options.parse_command_line()
    # 绑定端口
    app.listen(8888)
    # 启动监听
    tornado.ioloop.IOLoop.current().start()

(5) 文件上传

app.py

import os
import json
import tornado.web
import tornado.ioloop
import tornado.options

class UploadHandler(tornado.web.RequestHandler):
    # 接收GET请求
    def get(self):
        self.render('templates/upload.html')

    def post(self, *args):
        ret = {'result': 'OK'}
        file_metas = self.request.files["img"]  # 获取文件信息
        for meta in file_metas:
            file_name = meta['filename']  # 获取它的文件名
            with open(os.path.join("static", "upload", file_name), 'wb') as up:  # 打开本地一个文件
                up.write(meta['body'])  # body就是文件内容,把它写到本地
        self.write(json.dumps(ret))


# 设置路由
app = tornado.web.Application([
    (r"/upload", UploadHandler)
])

if __name__ == '__main__':
    tornado.options.parse_command_line()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

templates/upload.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传文件</title>
</head>
<body>
<form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="img">
    <input type="submit" value="上传">
</form>
</body>
</html>

3.2 响应相关

(1) write(chunk)

作用:将chunk数据写到输出缓冲区,我们可以在同一个视图中多次使用write方法, 对应的响应socket将缓冲区的数据返回给浏览器。

缓冲区刷新条件:1.程序结束;2.手动刷新;3.缓冲区满了;遇到/n

import json
import tornado.web
import tornado.ioloop
import tornado.options


# 返回普通文本
class TextHandler(tornado.web.RequestHandler):
    # 接收GET请求
    def get(self):
        self.write("Hello world!")


# 返回json数据
class JsonHandler(tornado.web.RequestHandler):
    """
    实际上,我们可以不用自己手动去做json序列化,当write方法检测到传入的chunk参数是字典类型后,会自动转换成json字符串,
    并在响应头header中设置Content-Type: application/json;charset=UTF-8
    
    自己手动序列化时为:Content-Type: text/html; charset=UTF-8
    """
    # 接收GET请求
    def get(self):
        data = {
            "name": "Tornado",
            "age": 20,
            "type": "Python Web 框架"
        }
        data_json = json.dumps(data)
        self.write(data_json)


# 设置路由
app = tornado.web.Application([
    (r"/text", TextHandler),
    (r"/json", JsonHandler),
])

if __name__ == '__main__':
    # 打印请求信息(日志信息),不加这一行后端不会有日志信息
    tornado.options.parse_command_line()
    # 绑定端口
    app.listen(8888)
    # 启动监听
    tornado.ioloop.IOLoop.current().start()

(2) finish(chunck=None)

作用:刷新缓冲区,并关闭当次请求通道。

因此,self.finish()后面的self.write('asdf')内容虽然会写入到缓冲区,但是请求通道已关闭,不会返回给用户,没有意义了。

import tornado.web
import tornado.ioloop
import tornado.options

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.finish("Hello world!")
        self.write("Hello world!")	# 这一行不会返回给用户

# 设置路由
app = tornado.web.Application([
    (r"/index", IndexHandler)
])

if __name__ == '__main__':
    # 打印请求信息(日志信息),不加这一行后端不会有日志信息
    tornado.options.parse_command_line()
    # 绑定端口
    app.listen(8888)
    # 启动监听
    tornado.ioloop.IOLoop.current().start()

(3) *_header(name, value)

作用:如,手动设置一个名为username,值为xxxx的响应头字段

参数:name,字段名称;value字段值

import json
import tornado.web
import tornado.ioloop
import tornado.options

# 定义处理类
class IndexHandler(tornado.web.RequestHandler):
    # 接收GET请求
    def get(self):
        data = {
            "name": "Tornado",
            "age": 20,
            "type": "Python Web 框架"
        }
        data_json = json.dumps(data)
        self.write(data_json)
        # 设置header
        self.set_header("Content-Type", "application/json;charset=UTF-8")
        self.set_header("Tornado", "xxxxxxxxx")
        # 添加header
        self.add_header("Django", "zzzzzzzzzz")
        # 清除键为Django的响应头
        self.clear_header("Django")

# 设置路由
app = tornado.web.Application([
    (r"/index", IndexHandler)
])

if __name__ == '__main__':
    tornado.options.parse_command_line()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

(4) set_default_headers()

通常,要修改响应头,重写此方法,而不是在随处任写;在此方法中,调用self.set_header()去设置响应头。

作用:此方法,在进入HTTP响应处理之前调用,可以重写该 方法来预先设置响应头。

注意:如果在多处,写了self.set_header()方法;后调用的方法会覆盖先调用的方法。

import json
import tornado.web
import tornado.ioloop
import tornado.options

# 定义处理类
class IndexHandler(tornado.web.RequestHandler):
    def set_default_headers(self):
        self.set_header("Content-Type", "application/json;charset=UTF-8")
        self.set_header("Tornado", "xxxxxxxxx")

    # 接收GET请求
    def get(self):
        data = {
            "name": "Tornado",
            "age": 20,
            "type": "Python Web 框架"
        }
        data_json = json.dumps(data)
        self.write(data_json)

# 设置路由
app = tornado.web.Application([
    (r"/index", IndexHandler)
])

if __name__ == '__main__':
    # 打印请求信息(日志信息),不加这一行后端不会有日志信息
    tornado.options.parse_command_line()
    # 绑定端口
    app.listen(8888)
    # 启动监听
    tornado.ioloop.IOLoop.current().start()

(5) set_status(status_code, reson=None)

作用:为响应设置状态码

参数:status_code,状态码值,为int整型;reason,描述状态码的词组,string类型。

如果reason的值为None,则状态码必须为系统定义的值。

状态码 Enum Name Details
100 CONTINUE HTTP/1.1 RFC 7231, Section 6.2.1
101 SWITCHING_PROTOCOLS HTTP/1.1 RFC 7231, Section 6.2.2
102 PROCESSING WebDAV RFC 2518, Section 10.1
200 OK HTTP/1.1 RFC 7231, Section 6.3.1
201 CREATED HTTP/1.1 RFC 7231, Section 6.3.2
202 ACCEPTED HTTP/1.1 RFC 7231, Section 6.3.3
203 NON_AUTHORITATIVE_INFORMATION HTTP/1.1 RFC 7231, Section 6.3.4
204 NO_CONTENT HTTP/1.1 RFC 7231, Section 6.3.5
205 RESET_CONTENT HTTP/1.1 RFC 7231, Section 6.3.6
206 PARTIAL_CONTENT HTTP/1.1 RFC 7233, Section 4.1
207 MULTI_STATUS WebDAV RFC 4918, Section 11.1
208 ALREADY_REPORTED WebDAV Binding Extensions RFC 5842, Section 7.1 (Experimental)
226 IM_USED Delta Encoding in HTTP RFC 3229, Section 10.4.1
300 MULTIPLE_CHOICES HTTP/1.1 RFC 7231, Section 6.4.1
301 MOVED_PERMANENTLY HTTP/1.1 RFC 7231, Section 6.4.2
302 FOUND HTTP/1.1 RFC 7231, Section 6.4.3
303 SEE_OTHER HTTP/1.1 RFC 7231, Section 6.4.4
304 NOT_MODIFIED HTTP/1.1 RFC 7232, Section 4.1
305 USE_PROXY HTTP/1.1 RFC 7231, Section 6.4.5
307 TEMPORARY_REDIRECT HTTP/1.1 RFC 7231, Section 6.4.7
308 PERMANENT_REDIRECT Permanent Redirect RFC 7238, Section 3 (Experimental)
400 BAD_REQUEST HTTP/1.1 RFC 7231, Section 6.5.1
401 UNAUTHORIZED HTTP/1.1 Authentication RFC 7235, Section 3.1
402 PAYMENT_REQUIRED HTTP/1.1 RFC 7231, Section 6.5.2
403 FORBIDDEN HTTP/1.1 RFC 7231, Section 6.5.3
404 NOT_FOUND HTTP/1.1 RFC 7231, Section 6.5.4
405 METHOD_NOT_ALLOWED HTTP/1.1 RFC 7231, Section 6.5.5
406 NOT_ACCEPTABLE HTTP/1.1 RFC 7231, Section 6.5.6
407 PROXY_AUTHENTICATION_REQUIRED HTTP/1.1 Authentication RFC 7235, Section 3.2
408 REQUEST_TIMEOUT HTTP/1.1 RFC 7231, Section 6.5.7
409 CONFLICT HTTP/1.1 RFC 7231, Section 6.5.8
410 GONE HTTP/1.1 RFC 7231, Section 6.5.9
411 LENGTH_REQUIRED HTTP/1.1 RFC 7231, Section 6.5.10
412 PRECONDITION_FAILED HTTP/1.1 RFC 7232, Section 4.2
413 REQUEST_ENTITY_TOO_LARGE HTTP/1.1 RFC 7231, Section 6.5.11
414 REQUEST_URI_TOO_LONG HTTP/1.1 RFC 7231, Section 6.5.12
415 UNSUPPORTED_MEDIA_TYPE HTTP/1.1 RFC 7231, Section 6.5.13
416 REQUEST_RANGE_NOT_SATISFIABLE HTTP/1.1 Range Requests RFC 7233, Section 4.4
417 EXPECTATION_FAILED HTTP/1.1 RFC 7231, Section 6.5.14
422 UNPROCESSABLE_ENTITY WebDAV RFC 4918, Section 11.2
423 LOCKED WebDAV RFC 4918, Section 11.3
424 FAILED_DEPENDENCY WebDAV RFC 4918, Section 11.4
426 UPGRADE_REQUIRED HTTP/1.1 RFC 7231, Section 6.5.15
428 PRECONDITION_REQUIRED Additional HTTP Status Codes RFC 6585
429 TOO_MANY_REQUESTS Additional HTTP Status Codes RFC 6585
431 REQUEST_HEADER_FIELDS_TOO_LARGE Additional HTTP Status Codes RFC 6585
500 INTERNAL_SERVER_ERROR HTTP/1.1 RFC 7231, Section 6.6.1
501 NOT_IMPLEMENTED HTTP/1.1 RFC 7231, Section 6.6.2
502 BAD_GATEWAY HTTP/1.1 RFC 7231, Section 6.6.3
503 SERVICE_UNAVAILABLE HTTP/1.1 RFC 7231, Section 6.6.4
504 GATEWAY_TIMEOUT HTTP/1.1 RFC 7231, Section 6.6.5
505 HTTP_VERSION_NOT_SUPPORTED HTTP/1.1 RFC 7231, Section 6.6.6
506 VARIANT_ALSO_NEGOTIATES Transparent Content Negotiation in HTTP RFC 2295, Section 8.1 (Experimental)
507 INSUFFICIENT_STORAGE WebDAV RFC 4918, Section 11.5
508 LOOP_DETECTED WebDAV Binding Extensions RFC 5842, Section 7.2 (Experimental)
510 NOT_EXTENDED An HTTP Extension Framework RFC 2774, Section 7 (Experimental)
511 NETWORK_AUTHENTICATION_REQUIRED Additional HTTP Status Codes RFC 6585, Section 6
import json
import tornado.web
import tornado.ioloop
import tornado.options


# 使用已有的(系统定义的)状态码:
class Demo1Handler(tornado.web.RequestHandler):
    def get(self):
        data = {
            "name": "Tornado",
            "age": 20,
            "type": "Python Web 框架"
        }
        data_json = json.dumps(data)
        self.write(data_json)
        self.set_status(404)


# 使用自定义的状态码:此时,reason不能为None,否则将抛出异常。
class Demo2Handler(tornado.web.RequestHandler):
    def get(self):
        data = {
            "name": "Tornado",
            "age": 20,
            "type": "Python Web 框架"
        }
        data_json = json.dumps(data)
        self.write(data_json)
        self.set_status(999, '原因')


# 设置路由
app = tornado.web.Application([
    (r"/demo_1", Demo1Handler),
    (r"/demo_2", Demo2Handler)
])

if __name__ == '__main__':
    tornado.options.parse_command_line()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

(6) redirect(url)

作用:重定向到url网址

import json
import tornado.web
import tornado.ioloop
import tornado.options


# 登录
class LoginHandler(tornado.web.RequestHandler):
    def get(self):
        self.redirect('/index')


# 首页
class IndexHandler(tornado.web.RequestHandler):
    # 接收GET请求
    def get(self):
        data = {
            "name": "Tornado",
            "age": 20,
            "type": "Python Web 框架"
        }
        data_json = json.dumps(data)
        self.write(data_json)


# 设置路由
app = tornado.web.Application([
    (r"/login", LoginHandler),
    (r"/index", IndexHandler)
])

if __name__ == '__main__':
    # 打印请求信息(日志信息),不加这一行后端不会有日志信息
    tornado.options.parse_command_line()
    # 绑定端口
    app.listen(8888)
    # 启动监听
    tornado.ioloop.IOLoop.current().start()

(7) send_error(status_code=500, **kwargs)

作用:抛出HTTP错误状态码,默认为500。抛出错误后tornado会调用writer_error()方法进行处理,并返回给浏览器错误界面。

import tornado.web
import tornado.ioloop
import tornado.options

# 首页
class IndexHandler(tornado.web.RequestHandler):
    # 接收GET请求
    def get(self):
        self.send_error(404)

# 设置路由
app = tornado.web.Application([
    (r"/index", IndexHandler)
])

if __name__ == '__main__':
    tornado.options.parse_command_line()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

(8) write_error(status_code, **kwargs)

作用:用来处理send_error抛出的错误信息,并并返回给浏览器错误界面。同上面的方法一起使用。

import tornado.web
import tornado.ioloop
import tornado.options

class IndexHandler(tornado.web.RequestHandler):
    def write_error(self, status_code, **kwargs):
        if status_code == 500:
            self.set_status(500)
            # 或者返回自定义的页面
            self.write("服务器内部错误")

        elif status_code == 404:
            self.set_status(404)
            self.write("资源不存在")

        else:
            self.set_status(999, "what")
            self.write("未知错误")

    # 接收GET请求
    def get(self):
        self.send_error(500)


# 设置路由
app = tornado.web.Application([
    (r"/index", IndexHandler)
])

if __name__ == '__main__':
    tornado.options.parse_command_line()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

(9) render

app.py

import tornado.web
import tornado.ioloop
import tornado.options

# 首页
class IndexHandler(tornado.web.RequestHandler):
    # 接收GET请求
    def get(self):
        self.render('templates/index.html')

# 设置路由
app = tornado.web.Application([
    (r"/index", IndexHandler)
])

if __name__ == '__main__':
    tornado.options.parse_command_line()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页页面</title>
</head>
<body>
    <h1>好好学习 Tornado</h1>
</body>
</html>

4、模板

Tornao中的模板语言和django中类似,模板引擎将模板文件载入内存,然后将数据嵌入其中,最终获取到一个完整的字符串,再将字符串返回给请求者。

Tornado 的模板支持“控制语句”和“表达语句”,控制语句是使用 {% 和 %} 包起来的 例如 {% if len(items) > 2 %}。表达语句是使用 {{ 和 }} 包起来的,例如 {{ items[0] }}。

控制语句和对应的 Python 语句的格式基本完全相同。我们支持 if、for、while 和 try,这些语句逻辑结束的位置需要用 {% end %} 做标记。还通过 extends 和 block 语句实现了模板继承。这些在 template 模块的代码文档中有着详细的描述。

未完待续

posted @ 2023-05-31 09:37  菜鸟程序员_python  阅读(286)  评论(0)    收藏  举报