Tornado入门学习

学习Tornado,主要就是整理查询出别人整理的资料,感谢大佬们的分享~~

简介

    Tornado全称Tornado Web Server,是一个用Python语言写成的Web服务器兼Web应用框架,由FriendFeed公司在自己的网站FriendFeed中使用,被Facebook收购以后框架在2009年9月以开源软件形式开放给大众。

1.    特点:

    作为Web框架,是一个轻量级的Web框架,其拥有异步非阻塞IO的处理方式。作为Web服务器,Tornado有较为出色的抗负载能力,官方用nginx反向代理的方式部署Tornado和其它Python web应用框架进行对比,结果最大浏览量超过第二名近40%。

2.    性能:

    Tornado有着优异的性能。它试图解决C10k问题,即处理大于或等于一万的并发。

    Tornado框架和服务器一起组成一个WSGI的全栈替代品。单独在WSGI容器中使用tornado网络框架或者tornaod http服务器,有一定的局限性,为了最大化的利用tornado的性能,推荐同时使用tornaod的网络框架和HTTP服务器。

3.    应用场景

    用户量大,高并发。如秒杀抢购、双十一某宝购物、春节抢火车票

    大量的HTTP持久连接。使用同一个TCP连接来发送和接收多个HTTP请求/应答,而不是为每一个新的请求/应答打开新的连接的方法。对于HTTP 1.0,可以在请求的包头(Header)中添加Connection: Keep-Alive。对于HTTP 1.1,所有的连接默认都是持久连接。

4.    Tornado与Django

Django

    Django是走大而全的方向,注重的是高效开发,它最出名的是其全自动化的管理后台:只需要使用起ORM,做简单的对象定义,它就能自动生成数据库结构、以及全功能的管理后台。

    Django提供的方便,也意味着Django内置的ORM跟框架内的其他模块耦合程度高,应用程序必须使用Django内置的ORM,否则就不能享受到框架内提供的种种基于其ORM的便利。

    session功能

    后台管理

    ORM

Tornado

    Tornado走的是少而精的方向,注重的是性能优越,它最出名的是异步非阻塞的设计方式。

    HTTP服务器

    异步编程

    WebSockets

5.    简单的Tornado案例

```

import tornado.web

```

import tornado.ioloop

class IndexHandler(tornado.web.RequestHandler):

    """主路由处理类"""

    def get(self):

        """对应http的get请求方式"""

        self.write("Hello Python!")

if __name__ == "__main__":

    app = tornado.web.Application([

        (r"/", IndexHandler),

    ])

    app.listen(8000)

    tornado.ioloop.IOLoop.current().start()

    以上代码解释说明:

    tornado的基础web框架模块

    RequestHandler:封装了对应一个请求的所有信息和方法,write(响应信息)就是写响应信息的一个方法;对应每一种http请求方式(get、post等),把对应的处理逻辑写进同名的成员方法中(如对应get请求方式,就将对应的处理逻辑写在get()方法中),当没有对应请求方式的成员方法时,会返回“405: Method Not Allowed”错误。

    Application:Tornado Web框架的核心应用类,是与服务器对接的接口,里面保存了路由信息表,其初始化接收的第一个参数就是一个路由信息映射元组的列表;其listen(端口)方法用来创建一个http服务器实例,并绑定到给定端口(注意:此时服务器并未开启监听)

    Tornado核心IOLoop循环模块:tornado的核心io循环模块,封装了Linux的epoll和BSD的kqueue,tornado高性能的基石。

    IOLoop.current()返回当前线程的IOLoop实例。

    IOLoop.start()启动IOLoop实例的I/O循环,同时服务器监听被打开。

6.    Tornado Web程序编写思路

    创建web应用实例对象,第一个初始化参数为路由映射列表。

    定义实现路由映射列表中的handler类。

    创建服务器实例,绑定服务器端口。

    启动当前线程的IOLoop。

7.    引入httpserver模块

    修改上篇文章简单tornado案例的代码如下:

import tornado.web

import tornado.ioloop

import tornado.httpserver # 新引入httpserver模块

class IndexHandler(tornado.web.RequestHandler):

    """主路由处理类"""

    def get(self):

        """对应http的get请求方式"""

        self.write("Hello Itcast!")

if __name__ == "__main__":

    app = tornado.web.Application([

        (r"/", IndexHandler),

    ])

    # ------------------------------

    # 我们修改这个部分

    # app.listen(8000)

    http_server = tornado.httpserver.HTTPServer(app)

    http_server.listen(8000)

    # ------------------------------

    tornado.ioloop.IOLoop.current().start()

    在这一修改版本中,我们引入了tornado.httpserver模块,顾名思义,它就是tornado的HTTP服务器实现。

    我们创建了一个HTTP服务器实例http_server,因为服务器要服务于我们刚刚建立的web应用,将接收到的客户端请求通过web应用中的路由映射表引导到对应的handler中,所以在构建http_server对象的时候需要传出web应用对象app。http_server.listen(8000)将服务器绑定到8000端口。

    实际上一版代码中app.listen(8000)正是对这一过程的简写。

    关于app.listen()这个方法只能在单进程模式中使用。

    对于app.listen()与手动创建HTTPServer实例

    http_server = tornado.httpserver.HTTPServer(app)

    http_server.listen(8000)

    这两种方式,建议大家先使用后者即创建HTTPServer实例的方式,因为其对于理解tornado web应用工作流程的完整性有帮助,便于大家记忆tornado开发的模块组成和程序结构;在熟练使用后,可以改为简写。

8.    单进程

    修改上面httpserver版本代码如下:

import tornado.web

import tornado.ioloop

import tornado.httpserver

class IndexHandler(tornado.web.RequestHandler):

    """主路由处理类"""

    def get(self):

        """对应http的get请求方式"""

        self.write("Hello Itcast!")

if __name__ == "__main__":

    app = tornado.web.Application([

        (r"/", IndexHandler),

    ])

    http_server = tornado.httpserver.HTTPServer(app)

    # -----------修改----------------

    http_server.bind(8000)

    http_server.start(0)

    # ------------------------------

    tornado.ioloop.IOLoop.current().start()

    http_server.bind(port)方法是将服务器绑定到指定端口。

    http_server.start(num_processes=1)方法指定开启几个进程,参数num_processes默认值为1,即默认仅开启一个进程;如果num_processes为None或者<=0,则自动根据机器硬件的cpu核芯数创建同等数目的子进程;如果num_processes>0,则创建num_processes个子进程。

    我们在前面写的http_server.listen(8000)实际上就等同于:

http_server.bind(8000)

http_server.start(1)

9.    多进程

    虽然tornado给我们提供了一次开启多个进程的方法,但是由于:

    每个子进程都会从父进程中复制一份IOLoop实例,如果在创建子进程前我们的代码动了IOLoop实例,那么会影响到每一个子进程,势必会干扰到子进程IOLoop的工作;

    所有进程是由一个命令一次开启的,也就无法做到在不停服务的情况下更新代码;

    所有进程共享同一个端口,想要分别单独监控每一个进程就很困难。

    不建议使用这种多进程的方式,而是建议手动开启多个进程,并且绑定不同的端口。

10.    引入options模块

    在前面的示例中我们都是将服务端口的参数写死在程序中,很不灵活。tornado为我们提供了一个便捷的工具,tornado.options模块——全局参数定义、存储、转换。

    name选项变量名,须保证全局唯一性,否则会报“Option 'xxx' already defined in ...”的错误;

    default 选项变量的默认值,如不传默认为None;

    type选项变量的类型,从命令行或配置文件导入参数的时候tornado会根据这个类型转换输入的值,转换不成功时会报错,可以是str、float、int、datetime、timedelta中的某个,若未设置则根据default的值自动推断,若default也未设置,那么不再进行转换。可以通过利用设置type类型字段来过滤不正确的输入。

    multiple选项变量的值是否可以为多个,布尔类型,默认值为False,如果multiple为True,那么设置选项变量时值与值之间用英文逗号分隔,而选项变量则是一个list列表(若默认值和输入均未设置,则为空列表[])。

    help选项变量的帮助提示信息,在命令行启动tornado时,通过加入命令行参数--help 可以查看所有选项变量的信息(注意,代码中需要加入tornado.options.parse_command_line())。

    关于tornado.options.options:全局的options对象,所有定义的选项变量都会作为该对象的属性。

    关于tornado.options.parse_command_line():转换命令行参数,并将转换后的值对应的设置到全局options对象相关属性上。追加命令行参数的方式是--myoption=myvalue

import tornado.web

import tornado.ioloop

import tornado.httpserver

import tornado.options # 新导入的options模块

tornado.options.define("port", default=8000, type=int, help="run server on the given port.") # 定义服务器监听端口选项

class IndexHandler(tornado.web.RequestHandler):

    """主路由处理类"""

    def get(self):

        """对应http的get请求方式"""

        self.write("Hello Python Tornado!")

if __name__ == "__main__":

    tornado.options.parse_command_line()


    app = tornado.web.Application([

        (r"/", IndexHandler),

    ])


    http_server = tornado.httpserver.HTTPServer(app)

    http_server.listen(tornado.options.options.port)

    tornado.ioloop.IOLoop.current().start()

    关于tornado.options.parse_config_file(path):

    从配置文件导入option,配置文件中的选项格式如下:

myoption = "myvalue"

myotheroption = "myothervalue"

    在使用prase_config_file()的时候,配置文件的书写格式仍需要按照python的语法要求,其优势是可以直接将配置文件的参数转换设置到全局对象tornado.options.options中;然而,其不方便的地方在于需要在代码中调用tornado.options.define()来定义选项,而且不支持字典类型,故而在实际应用中大都不使用这种方法。

    在使用配置文件的时候,通常会新建一个python文件(如config.py),然后在里面直接定义python类型的变量(可以是字典类型);在需要配置文件参数的地方,将config.py作为模块导入,并使用其中的变量参数。

    关于日志:

    当我们在代码中调用parse_command_line()或者parse_config_file()的方法时,tornado会默认为我们配置标准logging模块,即默认开启了日志功能,并向标准输出(屏幕)打印日志信息。

    如果想关闭tornado默认的日志功能,可以在命令行中添加--logging=none或者在代码中执行如下操作:

from tornado.options import options, parse_command_line

options.logging = None

parse_command_line()

11.    引入Application模块

    settings:在创建tornado.web.Application的对象时,传入了第一个参数——路由映射列表。实际上Application类的构造函数还接收很多关于tornado web应用的配置参数。

    debug:设置tornado是否工作在调试模式,默认为False即工作在生产模式。当设置debug=True后,tornado会工作在调试/开发模式,在此种模式下,tornado为方便我们开发而提供了几种特性:

    自动重启,tornado应用会监控我们的源代码文件,当有改动保存后便会重启程序,这可以减少我们手动重启程序的次数。需要注意的是,一旦我们保存的更改有错误,自动重启会导致程序报错而退出,从而需要我们保存修正错误后手动启动程序。这一特性也可单独通过autoreload=True设置;

    取消缓存编译的模板,可以单独通过compiled_template_cache=False来设置;

    取消缓存静态文件hash值,可以单独通过static_hash_cache=False来设置;

    提供追踪信息,当RequestHandler或者其子类抛出一个异常而未被捕获后,会生成一个包含追踪信息的页面,可以单独通过serve_traceback=True来设置。

    使用debug参数的方法:

import tornado.web

app = tornado.web.Application([], debug=True)

    路由映射:先前我们在构建路由映射列表的时候,使用的是二元元组,如:

[(r"/", IndexHandler),]

    对于这个映射列表中的路由,实际上还可以传入多个信息,如:

[

    (r"/", Indexhandler),

    (r"/python", XiaokeHandler, {"subject":"python"}),

    url(r"/python", XiaokeHandler, {"subject":"python"}, name="python_url")

]

        对于路由中的字典,会传入到对应的RequestHandler的initialize()方法中:

from tornado.web import RequestHandler

class XiaokeHandler(RequestHandler):

    def initialize(self, subject):

        self.subject = subject

    def get(self):

        self.write(self.subject)

    对于路由中的name字段,注意此时不能再使用元组,而应使用tornado.web.url来构建。name是给该路由起一个名字,可以通过调用RequestHandler.reverse_url(name)来获取该名子对应的url。

    小案例:

import tornado.web

import tornado.ioloop

import tornado.httpserver

import tornado.options

from tornado.options import options, define

from tornado.web import url, RequestHandler

define("port", default=8000, type=int, help="run server on the given port.")

class XiaokeHandler(RequestHandler):

    def get(self):

        python_url = self.reverse_url("python_url")

        self.write('xiaoke  python' %

                   python_url)

if __name__ == "__main__":

    tornado.options.parse_command_line()


    app = tornado.web.Application([

      url(r"/python", XiaokeHandler, {"subject":"python"}, name="python_url")

        ],

        debug = True)


    http_server = tornado.httpserver.HTTPServer(app)

    http_server.listen(options.port)

    tornado.ioloop.IOLoop.current().start()

12.    引入RequestHandler模块

    利用HTTP协议向服务器传参几种途径:

    查询字符串(query string),形如key1=value1&key2=value2;

    请求体(body)中发送的数据,比如表单数据、json、xml;

    提取uri的特定部分,如/blogs/2016/09/0001,可以在服务器端的路由中用正则表达式截取;

    在http报文的头(header)中增加自定义字段,如X-XSRFToken=xiaoke

    获取查询字符串参数:

    get_query_argument(name, default=_ARG_DEFAULT, strip=True):从请求的查询字符串中返回指定参数name的值,如果出现多个同名参数,则返回最后一个的值。default为设值未传name参数时返回的默认值,如若default也未设置,则会抛出tornado.web.MissingArgumentError异常。strip表示是否过滤掉左右两边的空白字符,默认为过滤。

    get_query_arguments(name, strip=True):从请求的查询字符串中返回指定参数name的值,注意返回的是list列表(即使对应name参数只有一个值)。若未找到name参数,则返回空列表[]。strip同前,不再赘述。

    获取请求体参数:

    get_body_argument(name, default=_ARG_DEFAULT, strip=True):从请求体中返回指定参数name的值,如果出现多个同名参数,则返回最后一个的值。default与strip同前,不再赘述。

    get_body_arguments(name, strip=True):从请求体中返回指定参数name的值,注意返回的是list列表(即使对应name参数只有一个值)。若未找到name参数,则返回空列表[]。strip同前,不再赘述。

    说明:对于请求体中的数据要求为字符串,且格式为表单编码格式(与url中的请求字符串格式相同),即key1=value1&key2=value2,HTTP报文头Header中的"Content-Type"为application/x-www-form-urlencoded或multipart/form-data。对于请求体数据为json或xml的,无法通过这两个方法获取。

    前两类方法的整合:

    get_argument(name, default=_ARG_DEFAULT, strip=True):从请求体和查询字符串中返回指定参数name的值,如果出现多个同名参数,则返回最后一个的值。default与strip同前,不再赘述。

    get_arguments(name, strip=True):从请求体和查询字符串中返回指定参数name的值,注意返回的是list列表(即使对应name参数只有一个值)。若未找到name参数,则返回空列表[]。strip同前,不再赘述。

    说明:对于请求体中数据的要求同前。 这两个方法最常用。注意:以上方法返回的都是unicode字符串

    关于requestHandler.request请求信息:RequestHandler.request对象存储了关于请求的相关信息,具体属性有:

    method HTTP的请求方式,如GET或POST;

    host被请求的主机名;

    uri请求的完整资源标示,包括路径和查询字符串;

    path请求的路径部分;

    query请求的查询字符串部分;

    version使用的HTTP版本;

    headers请求的协议头,是类字典型的对象,支持关键字索引的方式获取特定协议头信息,例如:request.headers["Content-Type"]

    body请求体数据;

    remote_ip客户端的IP地址;

    files用户上传的文件,为字典类型,型如:

{

  "form_filename1":[, ],

  "form_filename2":[,],

  ...

}

关于tornado.httputil.HTTPFile:是接收到的文件对象,它有三个属性:

    filename文件的实际名字,与form_filename1不同,字典中的键名代表的是表单对应项的名字;

    body文件的数据实体;

    content_type文件的类型。

    这三个对象属性可以像字典一样支持关键字索引,如request.files["form_filename1"][0]["body"]。

    关于正则提取uri:

    tornado中对于路由映射也支持正则提取uri,提取出来的参数会作为RequestHandler中对应请求方式的成员方法参数。若在正则表达式中定义了名字,则参数按名传递;若未定义名字,则参数按顺序传递。提取出来的参数会作为对应请求方式的成员方法的参数。

    关于write方法:write方法是写到缓冲区的,我们可以像写文件一样多次使用write方法不断追加响应内容,最终所有写到缓冲区的内容一起作为本次请求的响应输出。

    想一想,如何利用write方法写json数据?

import json

class IndexHandler(RequestHandler):

    def get(self):

        stu = {

            "name":"zhangsan",

            "age":24,

            "gender":1,

        }

        stu_json = json.dumps(stu)

        self.write(stu_json)

    实际上,我们可以不用自己手动去做json序列化,当write方法检测到我们传入的chunk参数是字典类型后,会自动帮我们转换为json字符串。

class IndexHandler(RequestHandler):

    def get(self):

        stu = {

            "name":"zhangsan",

            "age":24,

            "gender":1,

        }

        self.write(stu)

    两种方式有什么差异?

    对比一下两种方式的响应头header中Content-Type字段,自己手动序列化时为Content-Type:text/html; charset=UTF-8,而采用write方法时为Content-Type:application/json; charset=UTF-8。write方法除了帮我们将字典转换为json字符串之外,还帮我们将Content-Type设置为application/json; charset=UTF-8。

    关于set_header(name, value)属性:利用set_header(name, value)方法,可以手动设置一个名为name、值为value的响应头header字段。

    例如:

self.set_header("Content-Type", "application/json; charset=UTF-8")

    关于set_default_headers()方法:该方法会在进入HTTP处理方法前先被调用,可以重写此方法来预先设置默认的headers。

    注意:在HTTP处理方法中使用set_header()方法会覆盖掉在set_default_headers()方法中设置的同名header。

class IndexHandler(RequestHandler):

    def set_default_headers(self):

        print "执行了set_default_headers()"

        # 设置get与post方式的默认响应体格式为json

        self.set_header("Content-Type", "application/json; charset=UTF-8")

        # 设置一个名为name、值为python的header

        self.set_header("name", "python")

    def get(self):

        print "执行了get()"

        stu = {

            "name":"zhangsan",

            "age":24,

            "gender":1,

        }

        stu_json = json.dumps(stu)

        self.write(stu_json)

        self.set_header("name", "i love python") # 注意此处重写了header中的name字段,会覆盖掉在set_default_headers()方法中设置的同名header。

    关于set_status(status_code, reason=None):为响应设置状态码。

    status_code int类型,状态码

    reason string类型,描述状态码的词组,若为None,则会被自动填充为标准的状态码。

    非标准状态码:self.set_status(210, "python error") #非标准状态码,必须设置了reason。否则报错

    关于redirect(url):告知浏览器重定向到url。

    关于send_error(status_code=500, **kwargs):抛出HTTP错误状态码status_code,默认为500,kwargs为可变命名参数。使用send_error抛出错误后tornado会调用write_error()方法进行处理,并返回给浏览器处理后的错误页面。

class IndexHandler(RequestHandler):

    def get(self):

        self.write("主页")

        self.send_error(404, content="出现404错误")

    注意:默认的write_error()方法不会处理send_error抛出的kwargs参数,即上面的代码中content="出现404错误"是没有意义的。要想处理这个错误信息,必须重写write_error()方法

    使用send_error()方法后就不要再使用self.write(),再向输出缓冲区写内容了!

    关于write_error(status_code, **kwargs):用来处理send_error抛出的错误信息并返回给浏览器错误信息页面。可以重写此方法来定制自己的错误显示页面。

class IndexHandler(RequestHandler):

    def get(self):

        err_code = self.get_argument("code", None) # 注意返回的是unicode字符串,下同

        err_title = self.get_argument("title", "")

        err_content = self.get_argument("content", "")


        if err_code:

            self.send_error(err_code, title=err_title, content=err_content)

        else:

            self.write("主页")

    def write_error(self, status_code, **kwargs):

        self.write(u"

出错了,程序员GG正在赶过来!

")

        self.write(u"

错误名:%s

" % kwargs["title"])

        self.write(u"

错误详情:%s

" % kwargs["content"])

关于接口与调用顺序:

class IndexHandler(RequestHandler):

    # 对应每个请求的处理类Handler在构造一个实例后首先执行initialize()方法。

    #路由映射中的第三个字典型参数会作为该方法的命名参数传递,

    #作用:初始化参数(对象属性)

    def initialize(self):

        print "调用了initialize()"


    # 预处理,即在执行对应请求方式的HTTP方法(如get、post等)前先执行,

    #注意:不论以何种HTTP方式请求,都会执行prepare()方法。

    def prepare(self):

        print "调用了prepare()"

    def set_default_headers(self):

        print "调用了set_default_headers()"

    def write_error(self, status_code, **kwargs):

        print "调用了write_error()"

    def get(self):

        print "调用了get()"

    def post(self):

        print "调用了post()"

        self.send_error(200)  # 注意此出抛出了错误


    # 在请求处理结束后调用,即在调用HTTP方法后调用。

    #通常该方法用来进行资源清理释放或处理日志等。

    #注意:请尽量不要在此方法中进行响应输出。

    def on_finish(self):

        print "调用了on_finish()"

在正常情况未抛出错误时,调用顺序为:

set_defautl_headers()

initialize()

prepare()

HTTP方法(get或post方法)

on_finish()

在有错误抛出时,调用顺序为:

set_default_headers()

initialize()

prepare()

HTTP方法

set_default_headers()

write_error()

on_finish()

你可能感兴趣的:(Tornado入门学习)