tornado_tornado 获取get - CSDN
精华内容
参与话题
  • 项目将应用tornado、pymongo、ip2region、dlib、pillow、opencv等技术打造一个完整的人脸识别微信小程序。我将在实战过程中带大家快速掌握python mtv的设计模式、API接口设计、人脸特征|框选|截取|勾勒|化妆功能封装...
  • Tornado——入门篇

    2019-05-02 00:02:19
    习惯用python2,所以安装6.0一下版本的tornado(6.0以上最低3.5) pip install tornado==5.1.1 demo跑起来 执行python ./test1.py 测试一下 curl是基于URL语法在命令行方式下工作的文件传输工具,它支持FTP,FTPS...

    第一天(hello tornado)

    环境

    习惯用python2,所以安装6.0以下版本的tornado(6.0以上最低3.5)
    pip install tornado==5.1.1

    demo跑起来

    在这里插入图片描述
    执行python ./test1.py

    测试一下

    curl是基于URL语法在命令行方式下工作的文件传输工具,它支持FTP,FTPS,HTTP,HTTPS,GOPHER,TELNET,DICT,FILE及LDAP等协议。curl支持HTTPS认证,并且支持HTTP的POST,PUT等方法,FTP上传,kerberos认证,HTTP上传,代理服务器,cookies,用户名/密码认证,通过http代理服务器上传文件到FTP服务器等等,功能十分强大。

    • -A/–user-agent 设置用户代理发送给服务器,即告诉服务器浏览器为什么
    • -basic 使用HTTP基本验证
    • –tcp-nodelay 使用TCP_NODELAY选项
    • -e/–referer 来源网址,跳转过来的网址
    • –cacert 指定CA证书 (SSL)
    • –compressed 要求返回是压缩的形势,如果文件本身为一个压缩文件,则可以下载至本地
    • -H/–header 自定义头信息传递给服务器
    • -I/–head 只显示响应报文首部信息
    • –limit-rate 设置传输速度
    • -u/–user <user[:password]>设置服务器的用户和密码
    • -0/–http1.0 使用HTTP 1.0

    curl -X PUT www.baidu.com
    curl -X DELETE www.baidu.com
    curl -X POST www.baidu.com -d “key=value&key1=value1”
    curl -X GET www.baidu.com
    -X 指定请求方式 -d 添加参数

    curl localhost:8000/
    Hello, Welcome to the world of tornado!
    curl -X POST localhost:8000/
    My name is tornado !
    

    关于代码

    上述的一个简单web服务主要包含了两个模块

    • tornado.web 这是一个tornado中的web模块

      • RequestHandler
        不同于django,tornado将request与response都封装在了requesthandler中,它封装了对应一个请求的所有信息和方法,write方法是写入响应信息;对于请求方式不同,将不同的逻辑写入到与方法同名的成员方法中,当未重写同名的成员方法时,将会返回 405 方法不被准许错误。
      • Application
        它是tornado web框架的核心应用类,是与服务器对接的接口,保存有路由信息表,其初始化接收的第一个参数就是一个路由信息映射元组的列表;其listen(端口)方法用来创建一个http服务器实例,并绑定到给定端口(注意:此时服务器并未开启监听)。
    • tornado.ioloop tornado的核心io循环模块
      它封装了Linux的epoll和BSD的kqueue,tornado高性能的基石。
      在这里插入图片描述

      • IOLoop.current() 返回当前线程的IOLoop实例。
      • IOLoop.start() 启动IOLoop实例的I/O循环,同时服务器监听被打开。
    • tornado.options 从命令行中读取设置
      它的用法如下:

    from tornado.options import define, options
    define("port", default=8000, help="run on the given port", type=int)
    .
    .
    .
    tornado.options.parse_command_line()
    .
    http_server.listen(options.port)
    .
    

    如果一 个与 define 语句中同名的设置在命令行中被给出,那么它将成为全局 options 的一个 属性。如果用户运行程序时使用了–help 选项,程序将打印出所有你定义的选项以及 你在 define 函数的 help 参数中指定的文本。如果用户没有为这个选项指定值,则使 用default的值进行代替。Tornado使用type参数进行基本的参数类型验证,当不合

    适的类型被给出时抛出一个异常。因此,我们允许一个整数的 port 参数作为 options.port 来访问程序。如果用户没有指定值,则默认为 8000。

    总结

    1 创建请求处理类,继承handler类,重写相应方法
    2 创建web应用实例对象,定义路由映射列表
    3 绑定端口
    4 开启监听服务

    Tornado——第二天(关于端口绑定)

    回顾

    在创建完一个基础的web应用后,我们使用 app.listen() 方法来将 web服务与端口绑定。
    这个地方的listen() 方法只是一个封装后的简写形式。
    这个绑定过程的原始形式如下:

    # app.listen(8000)
    http_server = tornado.httpserver.HTTPServer(app) 
    http_server.listen(8000)
    

    首先使用了tornado中的http模块为 app 创建了一个 http服务实例。然后将这个服务与8000端口绑定

    开启多进程

    我们上述都是为tornado开启了一个进程,如果想开启多个线程的话,需要做以下操作:

    http_server = tornado.httpserver.HTTPServer(app) 
    http_server.bind(8000)
    http_server.start(0)
    

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

    http_server.bind(8000)
    http_server.start(1)
    

    关于多进程形式,因为子进程是从父进程中复制的ioloop实例,所以在创建子进程前如果更改了父进程的ioloop实例,那么每一个子进程都将受影响。

    Tornado——第三天(参数传递)

    tornado获取参数大致有三种形式:

    • 路由表中正则获取
    # _*_ coding:utf-8 _*_
    
    import tornado.web
    import tornado.ioloop
    import tornado.httpserver
    from tornado.options import define,options
    
    # 定义初始端口(默认8000)
    define("port", default=8000,help="run on the given port", type=int)
    
    class IndexHandler(tornado.web.RequestHandler):
    	
    	def get(self,params1):
    		self.write("i get a params: %s"%params1)
    
    
    if __name__ == "__main__":
    	tornado.options.parse_command_line()
    	app = tornado.web.Application(
                    handlers=[
                        (r"/(\w*)", IndexHandler),
                        ]
                    )
            http_server = tornado.httpserver.HTTPServer(app)
            http_server.listen(options.port)
            tornado.ioloop.IOLoop.current().start()
    

    在这里插入图片描述

    • 通过内置方法获取
    # _*_ coding:utf-8 _*_
    import sys
    reload(sys)
    sys.setdefaultencoding("utf-8")
    
    class IndexHandler(tornado.web.RequestHandler):
       def get(self,params1):
       		self.write("i get a params: %s"%params1)
            params2 = self.get_query_argument("params2","None",True)
            self.write("get_query_argument获取参数是:%s"%params2)
            params3 = self.get_query_arguments("params2",True)
            self.write("get_query_arguments获取参数是:%s"%params3)
                   
    # 获取get参数
    # get_query_argument(name, default=_ARG_DEFAULT, strip=True)
    # get_query_arguments(name, strip=True)
    # 区别在于如果有多个同名参数,第一个方法返回最后一个值,未传参数的情况且未设置默认值将会抛出异常。第二个方法返回一个列表,未传参数将返回一个空列表。
    

    在这里插入图片描述
    在这里插入图片描述

    • 从request.body中获取
      与获取get参数方式类似,有以下两种方法
    get_body_argument(name, default=_ARG_DEFAULT, strip=True)
    get_body_arguments(name, strip=True)
    

    ** 对于请求体中的数据要求为字符串,且格式为表单编码格式(与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)
    # 相当于 get_query_argument 与 get_body_argument
    get_arguments(name, strip=True)
    # 相当于 get_query_arguments 与 get_body_arguments
    

    Tornado——第四天(页面模版与静态文件)

    #_*_coding: utf-8 _*_
    import os
    import tornado.web
    import tornado.httpserver
    import tornado.ioloop
    from tornado.options import define,options
    import sys
    reload(sys)
    sys.setdefaultencoding("utf-8")
    
    define("port", default=8000,help="run on the given port", type=int)
    
    class IndexHandler(tornado.web.RequestHandler):
    
      def get(self):
          name = self.get_argument("name","python",True)
          self.render('index1.html',name=name)
    
    if __name__ == "__main__":
      tornado.options.parse_command_line()
      base_dir = os.path.dirname(__file__)
      handlers = [(r"/", IndexHandler),]
      settings = {
              "template_path": os.path.join(base_dir, "templates"),
              "static_path": os.path.join(base_dir, "static"),
              "debug":True
              }
      app = tornado.web.Application(handlers,**settings)
      http_server = tornado.httpserver.HTTPServer(app)
      http_server.listen(options.port)
      tornado.ioloop.IOLoop.current().start()
    
    # index1.html
    <!DOCTYPE html>
    <html>
    <head>
    	<title>首页</title>
    	<link rel="stylesheet" href="{{static_url("index1.css")}}">
    </head>
    <body>
    	<h1>Hello world !</h1>
    	<h3>hello {{name}}</h3>
    </body>
    </html>
    
    # index1.css
    h1 {
    	color:red
    }
    
    h3 {
    	color:green
    }
    
    

    在这里插入图片描述
    关于模版方面,tornado使用的是 jinja2 , 使用起来和django是很相似的。

    Tornado——第五天(文件上传与下载)

    继续编写昨天的代码

    class IndexHandler(tornado.web.RequestHandler):
    
        def initialize(self, upload, download):
            self.upload = upload
            self.download = download
    
        def get(self):
            name = self.get_argument("name","python",True)
            get_img = self.get_argument("get_img","false",True)
            if get_img != "true":
                self.render('index2.html',name=name)
            else:
            	# set_header方法设置header头
                self.set_header('Content-Type','application/octet-stream')
                self.set_header('Content-Disposition', 'attachment; filename="随机图片.png"')
                img_list = os.listdir(self.download)
                img_path = random.choice(img_list)
                with open(os.path.join(self.download,img_path),'rb') as f:
                    while True:
                        data = f.read(4096)
                        if not data:
                            break
                        self.write(data)
    
        def post(self):
            print "已接收到请求"
            files = self.request.files
            # {"head_img":[{"filename":"..","content_type":"...","body":"..."}]}
            head_img_obj = files.get('head_img')[0]
            if head_img_obj:
                head_img_path = os.path.join(self.upload, head_img_obj['filename'])
                with open(head_img_path,'wb') as f:
                    f.write(head_img_obj["body"])
            self.write("success !")
    
    if __name__ == "__main__":
        tornado.options.parse_command_line()
        base_dir = os.path.dirname(__file__)
        settings = {
                "template_path": os.path.join(base_dir, "templates"),
                "static_path": os.path.join(base_dir, "static"),
                "debug":True
                }
        handlers = [
                (r"/", IndexHandler, dict(
                    upload=os.path.join(settings["static_path"],"upload"), download=os.path.join(settings["static_path"],"download")
                    )),
                ]
    
    

    Tornado——第六天(同步与异步)

    此本分主要实现一个同步与异步的请求功能

    # index_asyn.html
    <!DOCTYPE html>
    <html>
    	<head>
    		<title>异步测试页面</title>
    
    	</head>
    	<body>
    		<input id="search" type="text" placeholder="请输入检索关键字" />
    		<input  type="button" onclick="request()" value="检索" />
    		<div id="result">
    			<p>检索结果</p>
    			<table id="res_list"></table>
    		</div>
    		<script type="text/javascript">
    			function request(){
    				var result = document.getElementById("res_list");
    				var search = document.getElementById("search").value;
    				var ajax = new XMLHttpRequest();
    				ajax.open("post","/", true)
    				ajax.setRequestHeader("Content-Type", "application/json");
    				ajax.onreadystatechange = function () {
    					if(ajax.readyState == 4){
    						if(ajax.status == 200){
    							res_obj=JSON.parse(ajax.responseText).result;
    							result.innerHTML = "";
    							for(var i=0; i<res_obj.length;i++){
    								html_text = "<tr>"+
    									"<td>"+res_obj[i][0]+"<td/>"+
    									"<td>"+res_obj[i][1]+"<td/>"+
    									"</tr>"
    								result.innerHTML = result.innerHTML + html_text							};
    						};
    					};
    				};
    				ajax.send(JSON.stringify({"search":search}));
    			};
    		</script> 
    	</body>
    </html>  
    
    #_*_coding: utf-8 _*_
    import os
    import json
    import tornado.web
    import tornado.httpserver
    import tornado.ioloop
    from tornado.httpclient import HTTPClient,AsyncHTTPClient
    from tornado.options import define,options
    import sys
    reload(sys)
    sys.setdefaultencoding("utf-8")
    
    define("port", default=8000,help="run on the given port", type=int)
    
    class IndexHandler(tornado.web.RequestHandler):
        def get(self):
            self.render('index_asyn.html')
    
        # 同步版
       # def post(self):
       #     if self.get_argument("search",False):
       #         search_text = self.get_argument("search")
       #     else:
       #         search_text = json.loads(self.request.body)["search"]
       #     client = HTTPClient()
       #     res = client.fetch("https://suggest.taobao.com/sug?code=utf-8&q=%s"%search_text)
       #     context = res.body.decode("utf8")
       #     self.write(context)
    
        # 异步版
       # @tornado.web.asynchronous
       # def post(self):
       #     if self.get_argument("search",False):
       #         search_text = self.get_argument("search")
       #     else:
       #         search_text = json.loads(self.request.body)["search"]
       #     client = AsyncHTTPClient()
       #     res = client.fetch("https://suggest.taobao.com/sug?code=utf-8&q=%s"%search_text, callback=self.deal_response)
       #     
       # def deal_response(self,response):
       #     content = response.body.decode("utf8")
       #     self.write(content)
       #     self.finish()
        
        @tornado.web.asynchronous
        @tornado.gen.engine
        def post(self):
            if self.get_argument("search",False):
                search_text = self.get_argument("search")
            else:
                search_text = json.loads(self.request.body)["search"]
            client = AsyncHTTPClient()
            res = yield tornado.gen.Task(client.fetch,"https://suggest.taobao.com/sug?code=utf-8&q=%s"%search_text)
            content = res.body.decode("utf8")
            self.write(content)
            self.finish()
    
    if __name__ == "__main__":
        tornado.options.parse_command_line()
        base_dir = os.path.dirname(__file__)
        handlers = [(r"/", IndexHandler),]
        settings = {
                "template_path": os.path.join(base_dir, "templates"),
                "static_path": os.path.join(base_dir, "static"),
                "debug":True
                }
        app = tornado.web.Application(handlers,**settings)
        http_server = tornado.httpserver.HTTPServer(app)
        http_server.listen(options.port)
        tornado.ioloop.IOLoop.current().start()
    

    在这里插入图片描述
    压力测试:
    siege 'http://localhost:8000/ POST search=af' -c10 -t10s

    • 同步在这里插入图片描述
    • 异步
      在这里插入图片描述
      结果:
      在10并发的情况下测试10s中
      同步处理了 47 个请求 异步处理了408个请求
      同步平均响应时间为1.74s,异步平均响应时间为0.24s

    总结:

    • tornado提供了一个相当成熟的异步web编程解决方案,从耗时的操作中解放出来,转而去处理更多的请求。
    • 关于self.write()
      • 此方法是将数据写入缓冲区中,并不会直接作为相应返回。同步http对应方法会自动调用self.finish()方法。
      • 此方法会自动检测数据类型,如dict将会转换为json返回。并自动设置content-type header头

    Tornado——第六天(websocket)

    关于轮询 长轮询 长链接
    https://www.cnblogs.com/AloneSword/p/3517463.html

    轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
    优点:后端程序编写比较容易。
    缺点:请求中有大半是无用,浪费带宽和服务器资源。
    实例:适于小型应用。

    长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
    优点:在无消息的情况下不会频繁的请求,耗费资源小。
    缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。
    实例:WebQQ、Hi网页版、Facebook IM。

    长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求或是采用xhr请求,服务器端就能源源不断地往客户端输入数据。
    优点:消息即时到达,不发无用请求;管理起来也相对方便。
    缺点:服务器维护一个长连接会增加开销。
    实例:Gmail聊天

    Flash Socket:在页面中内嵌入一个使用了Socket类的 Flash 程序JavaScript通过调用此Flash程序提供的Socket接口与服务器端的Socket接口进行通信,JavaScript在收到服务器端传送的信息后控制页面的显示。
    优点:实现真正的即时通信,而不是伪即时。
    缺点:客户端必须安装Flash插件;非HTTP协议,无法自动穿越防火墙。
    实例:网络互动游戏。

    展开全文
  • tornado框架开发与应用

    千人学习 2019-10-30 14:58:28
    本系列视频从Web框架本质入手...视频内容不局限于Tornado本身并且涉及Web开发中知识,如:Ajax、JSONP、CORS、图片验证码、分页组件、自定义Form验证组件、自定义Session框架等,后通过示例应用Tornado以及自定义组件。
  • Tornado介绍和开始使用Tornado

    万次阅读 2018-09-16 17:36:00
    Tornado简介 Tornado全称Tornado Web Server,是一个用Python语言写成的Web服务器兼Web应用框架,由FriendFeed公司在自己的网站FriendFeed中使用,被Facebook收购以后框架在2009年9月以开源软件形式开放给大众。 ...

    Tornado简介

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

    特点:

    • 作为Web框架,是一个轻量级的Web框架,类似于另一个Python web框架Web.py,其拥有异步非阻塞IO的处理方式。

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

     

    Tornado的特性

    HTTP服务器

    Tornado为了高效实现Comet/后端异步调用HTTP接口,是直接内嵌了HTTP服务器。

    前端无需加apache / lighttpd / nginx等也可以供浏览器访问;但它并没有完整实现HTTP 1.1的协议,所以官方文档是推荐用户在生产环境下在前端使用nginx,后端反向代理到多个Tornado实例。

    Tornado本身是单线程的异步网络程序,它默认启动时,会根据CPU数量运行多个实例;充分利用CPU多核的优势。

     

    单线程异步

    网站基本都会有数据库操作,而Tornado是单线程的,这意味着如果数据库查询返回过慢,整个服务器响应会被堵塞。

    数据库查询,实质上也是远程的网络调用;理想情况下,是将这些操作也封装成为异步的;但Tornado对此并没有提供任何支持。

    这是Tornado的设计,而不是缺陷。

    一个系统,要满足高流量;是必须解决数据库查询速度问题的!

    数据库若存在查询性能问题,整个系统无论如何优化,数据库都会是瓶颈,拖慢整个系统!

    异步并不能从本质上提到系统的性能;它仅仅是避免多余的网络响应等待,以及切换线程的CPU耗费。

    如果数据库查询响应太慢,需要解决的是数据库的性能问题;而不是调用数据库的前端Web应用。

    对于实时返回的数据查询,理想情况下需要确保所有数据都在内存中,数据库硬盘IO应该为0;这样的查询才能足够快;而如果数据库查询足够快,那么前端web应用也就无将数据查询封装为异步的必要。

    就算是使用协程,异步程序对于同步程序始终还是会提高复杂性;需要衡量的是处理这些额外复杂性是否值得。

    如果后端有查询实在是太慢,无法绕过,Tornaod的建议是将这些查询在后端封装独立封装成为HTTP接口,然后使用Tornado内置的异步HTTP客户端进行调用。

     

     

    开始 tornado 项目

      # -*-coding: utf-8 -*-
      ​
      ​
      import tornado.web
      import tornado.ioloop
      ​
      # 引入 httpserver 模块
      import tornado.httpserver
      ​
      ​
      class IndexHandler(tornado.web.RequestHandler):
      ​
          def get(self, *args, **kwargs):
              self.write("Hello Tornado!")
      ​
      ​
      if __name__ == '__main__':
          app = tornado.web.Application([
              (r'/', IndexHandler),
          ])
          # 直接使用 app 监听端口,最简单的写法,只能在单进程模式中使用
          # app.listen(8000)
      ​
          # 实例化一个 http 服务器对象, 匹配 app 中的路由
          httpServer = tornado.httpserver.HTTPServer(app)
          # 绑定端口,默认启动一个进程
          # httpServer.listen(8000)
      ​
          # 绑定端口
          httpServer.bind(8000)
          # 启动的进程的个数,默认开启一个进程,为 None 或者负数,也会开启对应的cpu核数个进程
          httpServer.start(num_processes=5)
          # 一般不使用 tornado 提供的方法启动多个进程,使用手动方法启动进程,绑定不同的端口
          # 1.每个子进程都会复制一份 ioloop 的实列,如果创建子进程前修改了 ioloop 会影响到多有的子进程
          # 2.所有的进程都是一个命令启动的,无法做到在不停止服务的情况下修改代码
          # 3.所有进程共享一个端口,很难进行分别监控
      ​
          # 开始监听, 监听 epoll 中的请求
          tornado.ioloop.IOLoop.current().start()
      ​
    展开全文
  • Tornado入门教程

    万次阅读 2017-06-20 10:12:19
    Overview FriendFeed是一款使用 Python 编写的,相对简单的 非阻塞式 Web 服务器。其应用程序使用的 Web 框架看起来有些像 web.py 或者 Google ...Tornado 就是我们在 FriendFeed 的 Web 服务器及其常用工具的开

    Overview

    FriendFeed是一款使用 Python 编写的,相对简单的 非阻塞式 Web 服务器。其应用程序使用的 Web 框架看起来有些像 web.py 或者 Google 的 webapp, 不过为了能有效利用非阻塞式服务器环境,这个 Web 框架还包含了一些相关的有用工具 和优化。

    Tornado 就是我们在 FriendFeed 的 Web 服务器及其常用工具的开源版本。

    Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll的运用,Tornado 每秒可以处理数以千计的连接,因此 Tornado 是实时 Web 服务的一个 理想框架。我们开发这个 Web 服务器的主要目的就是为了处理 FriendFeed 的实时功能 ——在 FriendFeed 的应用里每一个活动用户都会保持着一个服务器连接。(关于如何扩容 服务器,以处理数以千计的客户端的连接的问题,请参阅 The C10K problem )

    以下是经典的 “Hello, world” 示例:

    import tornado.ioloop
    import tornado.web
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("Hello, world")
    
    application = tornado.web.Application([
        (r"/", MainHandler),
    ])
    
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    

    查看下面的 Tornado 攻略以了解更多关于 tornado.web 包 的细节。

    我们清理了 Tornado 的基础代码,减少了各模块之间的相互依存关系,所以理论上讲, 你可以在自己的项目中独立地使用任何模块,而不需要使用整个包。

    下载和安装

    自动安装: Tornado 已经列入 PyPI ,因此可以通过 pip 或者 easy_install 来安装。如果你没有安装 libcurl 的话,你需要将其单独安装到系统中。请参见下面的安装依赖一节。注意一点,使用 pip 或 easy_install 安装的 Tornado 并没有包含源代码中的 demo 程序。

    手动安装: 下载 tornado-2.0.tar.gz

    tar xvzf tornado-2.0.tar.gz
    cd tornado-2.0
    python setup.py build
    sudo python setup.py install

    Tornado 的代码托管在 GitHub 上面。对于 Python 2.6 以上的版本,因为标准库中已经包括了对 epoll 的支持,所以你可以不用 setup.py 编译安装,只要简单地将 tornado 的目录添加到 PYTHONPATH 就可以使用了。

    安装需求

    Tornado 在 Python 2.5, 2.6, 2.7 中都经过了测试。要使用 Tornado 的所有功能,你需要安装 PycURL (7.18.2 或更高版本) 以及 simplejson (仅适用于Python 2.5,2.6 以后的版本标准库当中已经包含了对 JSON 的支持)。为方便起见,下面将列出 Mac OS X 和 Ubuntu 中的完整安装方式:

    Mac OS X 10.6 (Python 2.6+)

    sudo easy_install setuptools pycurl

    Ubuntu Linux (Python 2.6+)

    sudo apt-get install python-pycurl

    Ubuntu Linux (Python 2.5)

    sudo apt-get install python-dev python-pycurl python-simplejson

    模块索引

    最重要的一个模块是web, 它就是包含了 Tornado 的大部分主要功能的 Web 框架。其它的模块都是工具性质的, 以便让 web 模块更加有用 后面的 Tornado 攻略 详细讲解了 web 模块的使用方法。

    主要模块

    • web - FriendFeed 使用的基础 Web 框架,包含了 Tornado 的大多数重要的功能
    • escape - XHTML, JSON, URL 的编码/解码方法
    • database - 对 MySQLdb 的简单封装,使其更容易使用
    • template - 基于 Python 的 web 模板系统
    • httpclient - 非阻塞式 HTTP 客户端,它被设计用来和 web 及 httpserver 协同工作
    • auth - 第三方认证的实现(包括 Google OpenID/OAuth、Facebook Platform、Yahoo BBAuth、FriendFeed OpenID/OAuth、Twitter OAuth)
    • locale - 针对本地化和翻译的支持
    • options - 命令行和配置文件解析工具,针对服务器环境做了优化

    底层模块

    • httpserver - 服务于 web 模块的一个非常简单的 HTTP 服务器的实现
    • iostream - 对非阻塞式的 socket 的简单封装,以方便常用读写操作
    • ioloop - 核心的 I/O 循环

    Tornado 攻略

    请求处理程序和请求参数

    Tornado 的 Web 程序会将 URL 或者 URL 范式映射到 tornado.web.RequestHandler 的子类上去。在其子类中定义了get() 或 post() 方法,用以处理不同的 HTTP 请求。

    下面的代码将 URL 根目录 / 映射到 MainHandler,还将一个 URL 范式 /story/([0-9]+) 映射到 StoryHandler。正则表达式匹配的分组会作为参数引入 的相应方法中:

    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("You requested the main page")
    
    class StoryHandler(tornado.web.RequestHandler):
        def get(self, story_id):
            self.write("You requested the story " + story_id)
    
    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/story/([0-9]+)", StoryHandler),
    ])
    

    你可以使用 get_argument() 方法来获取查询字符串参数,以及解析 POST 的内容:

    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.write('<html><body><form action="/" method="post">'
                       '<input type="text" name="message">'
                       '<input type="submit" value="Submit">'
                       '</form></body></html>')
    
        def post(self):
            self.set_header("Content-Type", "text/plain")
            self.write("You wrote " + self.get_argument("message"))
    

    上传的文件可以通过 self.request.files 访问到,该对象将名称(HTML元素 <input type="file">的 name 属性)对应到一个文件列表。每一个文件都以字典的形式 存在,其格式为 {"filename":..., "content_type":..., "body":...}

    如果你想要返回一个错误信息给客户端,例如“403 unauthorized”,只需要抛出一个 tornado.web.HTTPError 异常:

    if not self.user_is_logged_in():
        raise tornado.web.HTTPError(403)
    

    请求处理程序可以通过 self.request 访问到代表当前请求的对象。该 HTTPRequest 对象包含了一些有用的属性,包括:

    • arguments - 所有的 GET 或 POST 的参数
    • files - 所有通过 multipart/form-data POST 请求上传的文件
    • path - 请求的路径( ? 之前的所有内容)
    • headers - 请求的开头信息

    你可以通过查看源代码 httpserver 模组中 HTTPRequest 的定义,从而了解到它的 所有属性。

    重写 RequestHandler 的方法函数

    除了 get()/post()等以外,RequestHandler 中的一些别的方法函数,这都是 一些空函数,它们存在的目的是在必要时在子类中重新定义其内容。对于一个请求的处理 的代码调用次序如下:

    1. 程序为每一个请求创建一个 RequestHandler 对象
    2. 程序调用 initialize() 函数,这个函数的参数是 Application 配置中的关键字 参数定义。(initialize 方法是 Tornado 1.1 中新添加的,旧版本中你需要 重写 __init__ 以达到同样的目的) initialize 方法一般只是把传入的参数存 到成员变量中,而不会产生一些输出或者调用像 send_error 之类的方法。
    3. 程序调用 prepare()。无论使用了哪种 HTTP 方法,prepare 都会被调用到,因此 这个方法通常会被定义在一个基类中,然后在子类中重用。prepare可以产生输出 信息。如果它调用了finish(或send_error` 等函数),那么整个处理流程 就此结束。
    4. 程序调用某个 HTTP 方法:例如 get()post()put() 等。如果 URL 的正则表达式模式中有分组匹配,那么相关匹配会作为参数传入方法。

    下面是一个示范 initialize() 方法的例子:

    class ProfileHandler(RequestHandler):
        def initialize(self, database):
            self.database = database
    
        def get(self, username):
            ...
    
    app = Application([
        (r'/user/(.*)', ProfileHandler, dict(database=database)),
        ])
    

    其它设计用来被复写的方法有:

    • get_error_html(self, status_code, exception=None, **kwargs) - 以字符串的形式 返回 HTML,以供错误页面使用。
    • get_current_user(self) - 查看下面的用户认证一节
    • get_user_locale(self) - 返回 locale 对象,以供当前用户使用。
    • get_login_url(self) - 返回登录网址,以供 @authenticated 装饰器使用(默认位置 在 Application 设置中)
    • get_template_path(self) - 返回模板文件的路径(默认是 Application 中的设置)

    重定向(redirect)

    Tornado 中的重定向有两种主要方法:self.redirect,或者使用 RedirectHandler

    你可以在使用 RequestHandler (例如 get)的方法中使用 self.redirect,将用户 重定向到别的地方。另外还有一个可选参数 permanent,你可以用它指定这次操作为永久性重定向。

    该参数会激发一个 301 Moved Permanently HTTP 状态,这在某些情况下是有用的, 例如,你要将页面的原始链接重定向时,这种方式会更有利于搜索引擎优化(SEO)。

    permanent 的默认值是 False,这是为了适用于常见的操作,例如用户端在成功发送 POST 请求 以后的重定向。

    self.redirect('/some-canonical-page', permanent=True)
    

    RedirectHandler 会在你初始化 Application 时自动生成。

    例如本站的下载 URL,由较短的 URL 重定向到较长的 URL 的方式是这样的:

    application = tornado.wsgi.WSGIApplication([
        (r"/([a-z]*)", ContentHandler),
        (r"/static/tornado-0.2.tar.gz", tornado.web.RedirectHandler,
         dict(url="http://github.com/downloads/facebook/tornado/tornado-0.2.tar.gz")),
    ], **settings)
    

    RedirectHandler 的默认状态码是 301 Moved Permanently,不过如果你想使用 302 Found 状态码,你需要将permanent 设置为 False

    application = tornado.wsgi.WSGIApplication([
        (r"/foo", tornado.web.RedirectHandler, {"url":"/bar", "permanent":False}),
    ], **settings)
    

    注意,在 self.redirect 和 RedirectHandler 中,permanent 的默认值是不同的。 这样做是有一定道理的,self.redirect 通常会被用在自定义方法中,是由逻辑事件触发 的(例如环境变更、用户认证、以及表单提交)。而 RedirectHandler 是在每次匹配到请求 URL 时被触发。

    模板

    你可以在 Tornado 中使用任何一种 Python 支持的模板语言。但是相较于其它模板而言, Tornado 自带的模板系统速度更快,并且也更灵活。具体可以查看 template 模块的源码。

    Tornado 模板其实就是 HTML 文件(也可以是任何文本格式的文件),其中包含了 Python 控制结构和表达式,这些控制结构和表达式需要放在规定的格式标记符(markup)中:

    <html>
       <head>
          <title>{{ title }}</title>
       </head>
       <body>
         <ul>
           {% for item in items %}
             <li>{{ escape(item) }}</li>
           {% end %}
         </ul>
       </body>
     </html>
    

    如果你把上面的代码命名为 "template.html",保存在 Python 代码的同一目录中,你就可以 这样来渲染它:

    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            items = ["Item 1", "Item 2", "Item 3"]
            self.render("template.html", title="My title", items=items)
    

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

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

    表达语句可以是包括函数调用在内的任何 Python 表述。模板中的相关代码,会在一个单独 的名字空间中被执行,这个名字空间包括了以下的一些对象和方法。(注意,下面列表中 的对象或方法在使用 RequestHandler.render 或者render_string 时才存在的 ,如果你在 RequestHandler 外面直接使用 template 模块,则它们中的大部分是不存在的)。

    • escapetornado.escape.xhtml_escape 的別名
    • xhtml_escapetornado.escape.xhtml_escape 的別名
    • url_escapetornado.escape.url_escape 的別名
    • json_encodetornado.escape.json_encode 的別名
    • squeezetornado.escape.squeeze 的別名
    • linkifytornado.escape.linkify 的別名
    • datetime: Python 的 datetime 模组
    • handler: 当前的 RequestHandler 对象
    • requesthandler.request 的別名
    • current_userhandler.current_user 的別名
    • localehandler.locale 的別名
    • _handler.locale.translate 的別名
    • static_url: for handler.static_url 的別名
    • xsrf_form_htmlhandler.xsrf_form_html 的別名
    • reverse_urlApplication.reverse_url 的別名
    • Application 设置中 ui_methods 和 ui_modules 下面的所有项目
    • 任何传递给 render 或者 render_string 的关键字参数

    当你制作一个实际应用时,你会需要用到 Tornado 模板的所有功能,尤其是 模板继承功能。所有这些功能都可以在template 模块 的代码文档中了解到。(其中一些功能是在 web 模块中实现的,例如 UIModules

    从实现方式来讲,Tornado 的模板会被直接转成 Python 代码。模板中的语句会逐字复制到一个 代表模板的函数中去。我们不会对模板有任何限制,Tornado 模板模块的设计宗旨就是要比 其他模板系统更灵活而且限制更少。所以,当你的模板语句里发生了随机的错误,在执行模板时 你就会看到随机的 Python 错误信息。

    所有的模板输出都已经通过 tornado.escape.xhtml_escape 自动转义(escape),这种默认行为, 可以通过以下几种方式修改:将 autoescape=None 传递给 Application 或者 TemplateLoader、 在模板文件中加入 {% autoescape None %}、或者在简单表达语句 {{ ... }} 写成 {% raw ...%}。另外你可以在上述位置将 autoescape 设为一个自定义函数,而不仅仅是 None

    你可以使用 set_cookie 方法在用户的浏览中设置 cookie:

    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            if not self.get_cookie("mycookie"):
                self.set_cookie("mycookie", "myvalue")
                self.write("Your cookie was not set yet!")
            else:
                self.write("Your cookie was set!")
    

    Cookie 很容易被恶意的客户端伪造。加入你想在 cookie 中保存当前登陆用户的 id 之类的信息,你需要对 cookie 作签名以防止伪造。Tornado 通过 set_secure_cookie 和 get_secure_cookie 方法直接支持了这种功能。 要使用这些方法,你需要在创建应用时提供一个密钥,名字为 cookie_secret。 你可以把它作为一个关键词参数传入应用的设置中:

    application = tornado.web.Application([
        (r"/", MainHandler),
    ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
    

    签名过的 cookie 中包含了编码过的 cookie 值,另外还有一个时间戳和一个 HMAC 签名。如果 cookie 已经过期或者 签名不匹配,get_secure_cookie 将返回 None,这和没有设置 cookie 时的 返回值是一样的。上面例子的安全 cookie 版本如下:

    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            if not self.get_secure_cookie("mycookie"):
                self.set_secure_cookie("mycookie", "myvalue")
                self.write("Your cookie was not set yet!")
            else:
                self.write("Your cookie was set!")
    

    用户认证

    当前已经认证的用户信息被保存在每一个请求处理器的 self.current_user 当中, 同时在模板的 current_user 中也是。默认情况下,current_user 为 None

    要在应用程序实现用户认证的功能,你需要复写请求处理中 get_current_user() 这 个方法,在其中判定当前用户的状态,比如通过 cookie。下面的例子让用户简单地使用一个 nickname 登陆应用,该登陆信息将被保存到 cookie 中:

    class BaseHandler(tornado.web.RequestHandler):
        def get_current_user(self):
            return self.get_secure_cookie("user")
    
    class MainHandler(BaseHandler):
        def get(self):
            if not self.current_user:
                self.redirect("/login")
                return
            name = tornado.escape.xhtml_escape(self.current_user)
            self.write("Hello, " + name)
    
    class LoginHandler(BaseHandler):
        def get(self):
            self.write('<html><body><form action="/login" method="post">'
                       'Name: <input type="text" name="name">'
                       '<input type="submit" value="Sign in">'
                       '</form></body></html>')
    
        def post(self):
            self.set_secure_cookie("user", self.get_argument("name"))
            self.redirect("/")
    
    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/login", LoginHandler),
    ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
    

    对于那些必须要求用户登陆的操作,可以使用装饰器 tornado.web.authenticated。 如果一个方法套上了这个装饰器,但是当前用户并没有登陆的话,页面会被重定向到 login_url(应用配置中的一个选项),上面的例子可以被改写成:

    class MainHandler(BaseHandler):
        @tornado.web.authenticated
        def get(self):
            name = tornado.escape.xhtml_escape(self.current_user)
            self.write("Hello, " + name)
    
    settings = {
        "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
        "login_url": "/login",
    }
    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/login", LoginHandler),
    ], **settings)
    

    如果你使用 authenticated 装饰器来装饰 post() 方法,那么在用户没有登陆的状态下, 服务器会返回 403 错误。

    Tornado 内部集成了对第三方认证形式的支持,比如 Google 的 OAuth 。参阅 auth 模块 的代码文档以了解更多信息。 for more details. Checkauth 模块以了解更多的细节。在 Tornado 的源码中有一个 Blog 的例子,你也可以从那里看到 用户认证的方法(以及如何在 MySQL 数据库中保存用户数据)。

    跨站伪造请求的防范

    跨站伪造请求(Cross-site request forgery), 简称为 XSRF,是个性化 Web 应用中常见的一个安全问题。前面的链接也详细讲述了 XSRF 攻击的实现方式。

    当前防范 XSRF 的一种通用的方法,是对每一个用户都记录一个无法预知的 cookie 数据,然后要求所有提交的请求中都必须带有这个 cookie 数据。如果此数据不匹配 ,那么这个请求就可能是被伪造的。

    Tornado 有内建的 XSRF 的防范机制,要使用此机制,你需要在应用配置中加上 xsrf_cookies 设定:

    settings = {
        "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
        "login_url": "/login",
        "xsrf_cookies": True,
    }
    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/login", LoginHandler),
    ], **settings)
    

    如果设置了 xsrf_cookies,那么 Tornado 的 Web 应用将对所有用户设置一个 _xsrf 的 cookie 值,如果 POST PUTDELET 请求中没有这 个 cookie 值,那么这个请求会被直接拒绝。如果你开启了这个机制,那么在所有 被提交的表单中,你都需要加上一个域来提供这个值。你可以通过在模板中使用 专门的函数 xsrf_form_html() 来做到这一点:

    <form action="/new_message" method="post">
      {{ xsrf_form_html() }}
      <input type="text" name="message"/>
      <input type="submit" value="Post"/>
    </form>
    

    如果你提交的是 AJAX 的 POST 请求,你还是需要在每一个请求中通过脚本添加上 _xsrf 这个值。下面是在 FriendFeed 中的 AJAX 的 POST 请求,使用了 jQuery 函数来为所有请求组东添加 _xsrf 值:

    function getCookie(name) {
        var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
        return r ? r[1] : undefined;
    }
    
    jQuery.postJSON = function(url, args, callback) {
        args._xsrf = getCookie("_xsrf");
        $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
            success: function(response) {
            callback(eval("(" + response + ")"));
        }});
    };
    

    对于 PUT 和 DELETE 请求(以及不使用将 form 内容作为参数的 POST 请求) 来说,你也可以在 HTTP 头中以 X-XSRFToken 这个参数传递 XSRF token。

    如果你需要针对每一个请求处理器定制 XSRF 行为,你可以重写 RequestHandler.check_xsrf_cookie()。例如你需要使用一个不支持 cookie 的 API, 你可以通过将 check_xsrf_cookie() 函数设空来禁用 XSRF 保护机制。然而如果 你需要同时支持 cookie 和非 cookie 认证方式,那么只要当前请求是通过 cookie 进行认证的,你就应该对其使用 XSRF 保护机制,这一点至关重要。

    静态文件和主动式文件缓存

    你能通过在应用配置中指定 static_path 选项来提供静态文件服务:

    settings = {
        "static_path": os.path.join(os.path.dirname(__file__), "static"),
        "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
        "login_url": "/login",
        "xsrf_cookies": True,
    }
    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/login", LoginHandler),
        (r"/(apple-touch-icon\.png)", tornado.web.StaticFileHandler, dict(path=settings['static_path'])),
    ], **settings)
    

    这样配置后,所有以 /static/ 开头的请求,都会直接访问到指定的静态文件目录, 比如 http://localhost:8888/static/foo.png 会从指定的静态文件目录中访问到 foo.png 这个文件。同时 /robots.txt 和/favicon.ico 也是会自动作为静态文件处理(即使它们不是以 /static/ 开头)。

    在上述配置中,我们使用 StaticFileHandler 特别指定了让 Tornado 从根目录伺服 apple-touch-icon.png 这个文件,尽管它的物理位置还是在静态文件目录中。(正则表达式 的匹配分组的目的是向 StaticFileHandler 指定所请求的文件名称,抓取到的分组会以 方法参数的形式传递给处理器。)通过相同的方式,你也可以从站点的更目录伺服sitemap.xml 文件。当然,你也可以通过在 HTML 中使用正确的 <link /> 标签来避免这样的根目录 文件伪造行为。

    为了提高性能,在浏览器主动缓存静态文件是个不错的主意。这样浏览器就不需要发送 不必要的 If-Modified-Since和 Etag 请求,从而影响页面的渲染速度。 Tornado 可以通过内建的“静态内容分版(static content versioning)”来直接支持这种功能。

    要使用这个功能,在模板中就不要直接使用静态文件的 URL 地址了,你需要在 HTML 中使用 static_url() 这个方法来提供 URL 地址:

    <html>
       <head>
          <title>FriendFeed - {{ _("Home") }}</title>
       </head>
       <body>
         <div><img src="{{ static_url("images/logo.png") }}"/></div>
       </body>
     </html>
    

    static_url() 函数会将相对地址转成一个类似于 /static/images/logo.png?v=aae54 的 URI,v 参数是 logo.png文件的散列值, Tornado 服务器会把它发给浏览器,并以此为依据让浏览器对相关内容做永久缓存。

    由于 v 的值是基于文件的内容计算出来的,如果你更新了文件,或者重启了服务器 ,那么就会得到一个新的 v 值,这样浏览器就会请求服务器以获取新的文件内容。 如果文件的内容没有改变,浏览器就会一直使用本地缓存的文件,这样可以显著提高页 面的渲染速度。

    在生产环境下,你可能会使用nginx这样的更有利于静态文件 伺服的服务器,你可以将 Tornado 的文件缓存指定到任何静态文件服务器上面,下面 是 FriendFeed 使用的 nginx 的相关配置:

    location /static/ {
        root /var/friendfeed/static;
        if ($query_string) {
            expires max;
        }
     }
    

    本地化

    不管有没有登陆,当前用户的 locale 设置可以通过两种方式访问到:请求处理器的 self.locale 对象、以及模板中的locale 值。Locale 的名称(如 en_US)可以 通过 locale.name 这个变量访问到,你可以使用 locale.translate 来进行本地化 翻译。在模板中,有一个全局方法叫 _(),它的作用就是进行本地化的翻译。这个 翻译方法有两种使用形式:

    _("Translate this string")
    

    它会基于当前 locale 设置直接进行翻译,还有一种是:

    _("A person liked this", "%(num)d people liked this", len(people)) % {"num": len(people)}
    

    这种形式会根据第三个参数来决定是使用单数或是复数的翻译。上面的例子中,如果 len(people) 是 1 的话,就使用第一种形式的翻译,否则,就使用第二种形式 的翻译。

    常用的翻译形式是使用 Python 格式化字符串时的“固定占位符(placeholder)”语法,(例如上面的 %(num)d),和普通占位符比起来,固定占位符的优势是使用时没有顺序限制。

    一个本地化翻译的模板例子:

    <html>
       <head>
          <title>FriendFeed - {{ _("Sign in") }}</title>
       </head>
       <body>
         <form action="{{ request.path }}" method="post">
           <div>{{ _("Username") }} <input type="text" name="username"/></div>
           <div>{{ _("Password") }} <input type="password" name="password"/></div>
           <div><input type="submit" value="{{ _("Sign in") }}"/></div>
           {{ xsrf_form_html() }}
         </form>
       </body>
     </html>
    

    默认情况下,我们通过 Accept-Language 这个头来判定用户的 locale,如果没有, 则取 en_US 这个值。如果希望用户手动设置一个 locale 偏好,可以在处理请求的 类中复写 get_user_locale 方法:

    class BaseHandler(tornado.web.RequestHandler):
        def get_current_user(self):
            user_id = self.get_secure_cookie("user")
            if not user_id: return None
            return self.backend.get_user_by_id(user_id)
    
        def get_user_locale(self):
            if "locale" not in self.current_user.prefs:
                # Use the Accept-Language header
                return None
            return self.current_user.prefs["locale"]
    

    如果 get_user_locale 返回 None,那么就会再去取 Accept-Language header 的值。

    你可以使用 tornado.locale.load_translations 方法获取应用中的所有已存在的翻 译。它会找到包含有特定名字的 CSV 文件的目录,如 es_GT.csv fr_CA.csv 这 些 csv 文件。然后从这些 CSV 文件中读取出所有的与特定语言相关的翻译内容。典型的用例 里面,我们会在 Tornado 服务器的 main() 方法中调用一次该函数:

    def main():
        tornado.locale.load_translations(
            os.path.join(os.path.dirname(__file__), "translations"))
        start_server()
    

    你可以使用 tornado.locale.get_supported_locales() 方法得到支持的 locale 列表。Tornado 会依据用户当前的 locale 设置以及已有的翻译,为用户选择 一个最佳匹配的显示语言。比如,用户的 locale 是 es_GT 而翻译中只支持了es, 那么 self.locale 就会被设置为 es。如果找不到最接近的 locale 匹配,self.locale 就会就会取备用值 es_US

    查看 locale 模块 的代码文档以了解 CSV 文件的格式,以及其它的本地化方法函数。

    UI 模块

    Tornado 支持一些 UI 模块,它们可以帮你创建标准的,易被重用的应用程序级的 UI 组件。 这些 UI 模块就跟特殊的函数调用一样,可以用来渲染页面组件,而这些组件可以有自己的 CSS 和 JavaScript。

    例如你正在写一个博客的应用,你希望在首页和单篇文章的页面都显示文章列表,你可以创建 一个叫做 Entry 的 UI 模块,让他在两个地方分别显示出来。首选需要为你的 UI 模块 创建一个 Python 模组文件,就叫 uimodules.py 好了:

    class Entry(tornado.web.UIModule):
        def render(self, entry, show_comments=False):
            return self.render_string(
                "module-entry.html", entry=entry, show_comments=show_comments)
    

    然后通过 ui_modules 配置项告诉 Tornado 在应用当中使用 uimodules.py

    class HomeHandler(tornado.web.RequestHandler):
        def get(self):
            entries = self.db.query("SELECT * FROM entries ORDER BY date DESC")
            self.render("home.html", entries=entries)
    
    class EntryHandler(tornado.web.RequestHandler):
        def get(self, entry_id):
            entry = self.db.get("SELECT * FROM entries WHERE id = %s", entry_id)
            if not entry: raise tornado.web.HTTPError(404)
            self.render("entry.html", entry=entry)
    
    settings = {
        "ui_modules": uimodules,
    }
    application = tornado.web.Application([
        (r"/", HomeHandler),
        (r"/entry/([0-9]+)", EntryHandler),
    ], **settings)
    

    在 home.html 中,你不需要写繁复的 HTML 代码,只要引用 Entry 就可以了:

    {% for entry in entries %}
      {% module Entry(entry) %}
    {% end %}
    

    在 entry.html 里面,你需要使用 show_comments 参数来引用 Entry 模块,用来 显示展开的 Entry 内容:

    {% module Entry(entry, show_comments=True) %}
    

    你可以为 UI 模型配置自己的 CSS 和 JavaScript ,只要复写 embedded_cssembedded_javascriptjavascipt_filescss_files 就可以了:

    class Entry(tornado.web.UIModule):
        def embedded_css(self):
            return ".entry { margin-bottom: 1em; }"
    
        def render(self, entry, show_comments=False):
            return self.render_string(
                "module-entry.html", show_comments=show_comments)
    

    即使一页中有多个相同的 UI 组件,UI 组件的 CSS 和 JavaScript 部分只会被渲染一次。 CSS 是在页面的 <head> 部分,而 JavaScript 被渲染在页面结尾 </body> 之前的位 置。

    在不需要额外 Python 代码的情况下,模板文件也可以当做 UI 模块直接使用。 例如前面的例子可以以下面的方式实现,只要把这几行放到 module-entry.html 中就可以了:

    {{ set_resources(embedded_css=".entry { margin-bottom: 1em; }") }}
    <!-- more template html... -->
    

    这个修改过的模块式模板可以通过下面的方法调用:

    {% module Template("module-entry.html", show_comments=True) %}
    

    set_resources 函数只能在 {% module Template(...) %} 调用的模板中访问到。 和 {% include ... %} 不同,模块式模板使用了和它们的上级模板不同的命名 空间——它们只能访问到全局模板命名空间和它们自己的关键字参数。

    非阻塞式异步请求

    当一个处理请求的行为被执行之后,这个请求会自动地结束。因为 Tornado 当中使用了 一种非阻塞式的 I/O 模型,所以你可以改变这种默认的处理行为——让一个请求一直保持 连接状态,而不是马上返回,直到一个主处理行为返回。要实现这种处理方式,只需要 使用 tornado.web.asynchronous 装饰器就可以了。

    使用了这个装饰器之后,你必须调用 self.finish() 以完成 HTTP 请求,否则 用户的浏览器会一直处于等待服务器响应的状态:

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

    下面是一个使用 Tornado 内置的异步请求 HTTP 客户端去调用 FriendFeed 的 API 的例 子:

    class MainHandler(tornado.web.RequestHandler):
        @tornado.web.asynchronous
        def get(self):
            http = tornado.httpclient.AsyncHTTPClient()
            http.fetch("http://friendfeed-api.com/v2/feed/bret",
                       callback=self.on_response)
    
        def on_response(self, response):
            if response.error: raise tornado.web.HTTPError(500)
            json = tornado.escape.json_decode(response.body)
            self.write("Fetched " + str(len(json["entries"])) + " entries "
                       "from the FriendFeed API")
            self.finish()
    

    例子中,当 get() 方法返回时,请求处理还没有完成。在 HTTP 客户端执行它的回 调函数 on_response() 时,从浏览器过来的请求仍然是存在的,只有在显式调用了 self.finish() 之后,才会把响应返回到浏览器。

    关于更多异步请求的高级例子,可以参阅 demo 中的 chat 这个例子。它是一个使用 long polling 方式 的 AJAX 聊天室。如果你使用到了 long polling,你可能需要复写on_connection_close(), 这样你可以在客户连接关闭以后做相关的清理动作。(请查看该方法的代码文档,以防误用。)

    异步 HTTP 客户端

    Tornado 包含了两种非阻塞式 HTTP 客户端实现:

    SimpleAsyncHTTPClient 和 CurlAsyncHTTPClient

    前者是直接基于 IOLoop 实现的,因此无需外部依赖关系。

    后者作为 Curl 客户端,需要安装 libcurl 和 pycurl 后才能正常工作,但是对于使用 到 HTTP 规范中一些不常用内容的站点来说,它的兼容性会更好。为防止碰到 旧版本中异步界面的 bug,我们建议你安装最近的版本的 libcurl 和 pycurl

    这些客户端都有它们自己的模组(tornado.simple_httpclient 和 tornado.curl_httpclient),你可以通过tornado.httpclient 来指定使用哪一种 客户端,默认情况下使用的是 SimpleAsyncHTTPClient,如果要修改默认值,只要 在一开始调用 AsyncHTTPClient.configure 方法即可:

    AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient')
    

    第三方认证

    Tornado 的 auth 模块实现了现在很多流行站点的用户认证方式,包括 Google/Gmail、Facebook、Twitter、Yahoo 以及 FriendFeed。这个模块可以让用户使用 这些站点的账户来登陆你自己的应用,然后你就可以在授权的条件下访问原站点的一些服 务,比如下载用户的地址薄,在 Twitter 上发推等。

    下面的例子使用了 Google 的账户认证,Google 账户的身份被保存到 cookie 当中,以便 以后的访问使用:

    class GoogleHandler(tornado.web.RequestHandler, tornado.auth.GoogleMixin):
        @tornado.web.asynchronous
        def get(self):
            if self.get_argument("openid.mode", None):
                self.get_authenticated_user(self._on_auth)
                return
            self.authenticate_redirect()
    
        def _on_auth(self, user):
            if not user:
                self.authenticate_redirect()
                return
            # Save the user with, e.g., set_secure_cookie()
    

    请查看 auth 模块的代码文档以了解更多的细节。

    调试模式和自动重载

    如果你将 debug=True 传递给 Application 构造器,该 app 将以调试模式 运行。在调试模式下,模板将不会被缓存,而这个 app 会监视代码文件的修改, 如果发现修改动作,这个 app 就会被重新加载。在开发过程中,这会大大减少 手动重启服务的次数。然而有些问题(例如 import 时的语法错误)还是会让服务器 下线,目前的 debug 模式还无法避免这些情况。

    调试模式和 HTTPServer 的多进程模式不兼容。在调试模式下,你必须将 HTTPServer.start 的参数设为不大于 1 的数字。

    调试模式下的自动重载功能可以通过独立的模块 tornado.autoreload 调用, 作为测试运行器的一个可选项目,tornado.testing.main 中也有用到它。

    性能

    一个 Web 应用的性能表现,主要看它的整体架构,而不仅仅是前端的表现。 和其它的 Python Web 框架相比,Tornado 的速度要快很多。

    我们在一些流行的 Python Web 框架上(Django、 web.pyCherryPy), 针对最简单的 Hello, world 例子作了一个测试。对于 Django 和 web.py,我们使用 Apache/mod_wsgi 的方式来带,CherryPy 就让它自己裸跑。这也是在生产环境中各框架常用 的部署方案。对于我们的 Tornado,使用的部署方案为前端使用 nginx 做反向代理,带动 4 个线程模式的 Tornado,这种方案也是我们推荐的在生产环境下的 Tornado 部署方案(根据具体的硬件情况,我们推荐一个 CPU 核对应一个 Tornado 伺服实例, 我们的负载测试使用的是四核处理器)。

    我们使用 Apache Benchmark (ab),在另外一台机器上使用了如下指令进行负载测试:

    ab -n 100000 -c 25 http://10.0.1.x/
    

    在 AMD Opteron 2.4GHz 的四核机器上,结果如下图所示:


    在我们的测试当中,相较于第二快的服务器,Tornado 在数据上的表现也是它的 4 倍之 多。即使只用了一个 CPU 核的裸跑模式,Tornado 也有 33% 的优势。

    这个测试不见得非常科学,不过从大体上你可以看出,我们开发 Tornado 时对于性能 的注重程度。和其他的 Python Web 开发框架相比,它不会为你带来多少延时。

    生产环境下的部署

    在 FriendFeed 中,我们使用 nginx 做负载均衡和静态文件伺服。 我们在多台服务器上,同时部署了多个 Tornado 实例,通常,一个 CPU 内核 会对应一个 Tornado 线程。

    因为我们的 Web 服务器是跑在负载均衡服务器(如 nginx)后面的,所以需要把 xheaders=True 传到 HTTPServer 的构造器当中去。这是为了让 Tornado 使用 X-Real-IP 这样的的 header 信息来获取用户的真实 IP地址,如果使用传统 的方法,你只能得到这台负载均衡服务器的 IP 地址。

    下面是 nginx 配置文件的一个示例,整体上与我们在 FriendFeed 中使用的差不多。 它假设 nginx 和 Tornado 是跑在同一台机器上的,四个 Tornado 服务跑在 8000-8003 端口上:

    user nginx;
    worker_processes 1;
    
    error_log /var/log/nginx/error.log;
    pid /var/run/nginx.pid;
    
    events {
        worker_connections 1024;
        use epoll;
    }
    
    http {
        # Enumerate all the Tornado servers here
        upstream frontends {
            server 127.0.0.1:8000;
            server 127.0.0.1:8001;
            server 127.0.0.1:8002;
            server 127.0.0.1:8003;
        }
    
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
    
        access_log /var/log/nginx/access.log;
    
        keepalive_timeout 65;
        proxy_read_timeout 200;
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        gzip on;
        gzip_min_length 1000;
        gzip_proxied any;
        gzip_types text/plain text/html text/css text/xml
                   application/x-javascript application/xml
                   application/atom+xml text/javascript;
    
        # Only retry if there was a communication error, not a timeout
        # on the Tornado server (to avoid propagating "queries of death"
        # to all frontends)
        proxy_next_upstream error;
    
        server {
            listen 80;
    
            # Allow file uploads
            client_max_body_size 50M;
    
            location ^~ /static/ {
                root /var/www;
                if ($query_string) {
                    expires max;
                }
            }
            location = /favicon.ico {
                rewrite (.*) /static/favicon.ico;
            }
            location = /robots.txt {
                rewrite (.*) /static/robots.txt;
            }
    
            location / {
                proxy_pass_header Server;
                proxy_set_header Host $http_host;
                proxy_redirect false;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Scheme $scheme;
                proxy_pass http://frontends;
            }
        }
    }
    

    WSGI 和 Google AppEngine

    Tornado 对 WSGI 只提供了有限的支持,即使如此,因为 WSGI 并不支持非阻塞式的请求,所以如果你使用 WSGI 代替 Tornado 自己的 HTTP 服务的话,那么你将无法使用 Tornado 的异步非阻塞式的请求处理方式。 比如@tornado.web.asynchronoushttpclient 模块、auth 模块, 这些将都无法使用。

    你可以通过 wsgi 模块中的 WSGIApplication 创建一个有效的 WSGI 应用(区别于 我们用过的tornado.web.Application)。下面的例子展示了使用内置的 WSGI CGIHandler 来创建一个有效的 Google AppEngine 应用。

    import tornado.web
    import tornado.wsgi
    import wsgiref.handlers
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("Hello, world")
    
    if __name__ == "__main__":
        application = tornado.wsgi.WSGIApplication([
            (r"/", MainHandler),
        ])
        wsgiref.handlers.CGIHandler().run(application)
    

    请查看 demo 中的 appengine 范例,它是一个基于 Tornado 的完整的 AppEngine 应用。

    注意事项和社区支持

    因为 FriendFeed 以及其他 Tornado 的主要用户在使用时都是基于 nginx或者 Apache 代理之后的。所以现在 Tornado 的 HTTP 服务部分并不完整,它无法处理多行的 header 信息,同时对于一 些非标准的输入也无能为力。

    你可以在 Tornado 开发者邮件列表 中讨论和提交 bug。

    展开全文
  • Tornado 开发教程

    万人学习 2019-12-18 15:13:40
    Tornado开发教程,内容包括:Tornado项目开发、Tornado路由、Tornado请求响应、Tornado重定向、Tornado模板、Tornado文件上传、Tornado Ajax、Tornado数据库。
  • tornado.2.2.1

    热门讨论 2020-07-21 10:00:26
    发布声明 2.2.1. 很多年前的事了。
  • 这是从git项目中提取出来的中文PDF版,方便大家使用。字体适应,排版较好。版权原作者所有,用于学习。
  • python笔记(tornado初识)

    千次阅读 2019-08-20 13:51:36
    一、tornado介绍 轻量级web框架 异步非阻塞IO处理方式 出色的抗负载能力 优异的处理性能,不依赖多进程/多线程,一定程度上解决GIL问题 WSGI全栈替代产品,推荐同时使用其web框架和HTTP服务器 二、初步简单使用 ...

    一、tornado介绍

    1. 轻量级web框架

    2. 异步非阻塞IO处理方式

    3. 出色的抗负载能力

    4. 优异的处理性能,不依赖多进程/多线程,一定程度上解决GIL问题

    5. WSGI全栈替代产品,推荐同时使用其web框架和HTTP服务器

    6. socket连接建立过程

    7. WSGI把应用(Application)和服务器(Server)结合起来,Tornado既可以是WSGI应用也可以是WSGI服务。
      既是WebServer也是WebFramework.
      在这里插入图片描述

    8. tornado生命周期
      在这里插入图片描述
      在这里插入图片描述

    二、初步简单使用

    1. httpserver形式

      from tornado import web  # tornado的基础web框架模块
      from tornado import ioloop  # tornado的核心IO循环模块,封装了linux的epoll和BSD的kqueue
      from tornado import httpserver
      
      
      # 相当于Django中的视图
      # 一个业务处理类
      class IndexHandler(web.RequestHandler):
          """处理get请求"""
          def get(self, *args, **kwargs):
              self.write("hello world")  # 做出响应数据
      
      
      if __name__ == '__main__':
      
          app = web.Application([
              (r"/", IndexHandler)
          ])
          # 实例化http服务对象
          http_server = httpserver.HTTPServer(app)
          # 绑定IP和端口
          http_server.listen(8000)
          ioloop.IOLoop.current().start()
      
    2. 多进程形式

      from tornado import web  # tornado的基础web框架模块
      from tornado import ioloop  # tornado的核心IO循环模块,封装了linux的epoll和BSD的kqueue
      from tornado import httpserver
      
      
      # 相当于Django中的视图
      # 一个业务处理类
      class IndexHandler(web.RequestHandler):
          """处理get请求"""
          def get(self, *args, **kwargs):
              self.write("hello world")  # 做出响应数据
      
      
      if __name__ == '__main__':
      
          app = web.Application([
              (r"/", IndexHandler)
          ])
          # 实例化http服务对象
          http_server = httpserver.HTTPServer(app)
          # 启动多个进程, 将服务器绑定到指定的端口
          http_server.bind(8000)
          http_server.start(5)  # 开启5个进程,不写默认启动一个进程
          """
          多进程存在问题:
              1.每个子进程都会从父进程中复制一份IOLoop实例,如果在创建子进程前修改了IOLoop,会影响所有子进程
              2.所有的进程都是由一个命令启动的,无法做到在不停止服务的情况下修改代码
              3.所有进程共享一个端口,想要分别监控很困难
          """
          ioloop.IOLoop.current().start()
      
    3. 常用形式

      import tornado.web  # tornado的基础web框架模块
      import tornado.ioloop  # tornado的核心IO循环模块,封装了linux的epoll和BSD的kqueue
      
      
      # 相当于Django中的视图
      # 一个业务处理类
      class IndexHandler(tornado.web.RequestHandler):
          """处理get请求"""
          def get(self, *args, **kwargs):
              self.write("hello world")  # 做出响应数据
      
      
      if __name__ == '__main__':
          """实例化一个app对象    Application:是tornado web框架的核心应用类,是与服务器对应的接口。
          里面保存了路由映射表,有一个listen方法用来创建一个http服务的实例,并绑定了端口"""
          app = tornado.web.Application([
              (r"/", IndexHandler)
          ])
          app.listen(8000)
          """
          IOLoop.current():返回当前线程IOLoop实例
          IOLoop.start():起动IOLoop实例的I/O循环,同时开启了监听
          """
          tornado.ioloop.IOLoop.current().start()
      
    4. 带options使用形式(三种使用方式)

      server_options.py

      from tornado import web  # tornado的基础web框架模块
      from tornado import ioloop  # tornado的核心IO循环模块,封装了linux的epoll和BSD的kqueue
      from tornado import httpserver
      from tornado import options
      import config
      
      """
      定义变量
      options.define(
          name="port",  # 变量名,唯一性
          default=8000,  # 设置默认值
          type=int,  # 数据类型
          help=None,  # 选项变量的提示信息
          metavar=None,  #
          multiple=False,  # 设置选项变量是否可以为多个值
          group=None,
          callback=None
      )
      options.options: 全局的options对象,所有定义的选项变量都会作为改对象的属性
      """
      
      # options.define(name="port", type=int, default=8000)  # (第一、二种方式)
      # options.define(name="list", type=str, default=[], multiple=True)  # (第一、二种方式)
      
      
      # 相当于Django中的视图
      # 一个业务处理类
      class IndexHandler(web.RequestHandler):
          """处理get请求"""
          def get(self, *args, **kwargs):
              self.write("hello world")  # 做出响应数据
      
      
      if __name__ == '__main__':
          # options.options.logging = None  # 关闭日志
          # options.parse_command_line()  # 转换命令行参数,并保存到options.options(第一种方式)
          # options.parse_config_file("config")  # 去配置文件中取参数(第二种方式.txt文档)
          # print(options.options.list)  #(第二种方式)
          print(config.options.get("list"))  # 直接去py文件中取,(第三种方式)
          app = web.Application([
              (r"/", IndexHandler)
          ])
          http_server = httpserver.HTTPServer(app)
          # 绑定IP和端口
          # http_server.listen(options.options.port)  # (第一种方式)
      
          # http_server.bind(options.options.port)  # 第二种方式
          # http_server.start(1) # 第二种方式
      
          http_server.bind(config.options.get("port"))  # 第三种方式
          http_server.start(1)  # 第三种方式
      
          ioloop.IOLoop.current().start()
      
      
      """
      第一种方式必须在终端启动:
          例如:python server_options.py --port=9000 --list=good,nice,cool
      """
      

      config.txt(方式二)

      port = 7000
      list = ["good", "nice", "well"]
      

      config.py(方式三)

      options = {
          "port": 7000,
          "list": ["good", "nice", "well"]
      }
      

    三、异步IO

    1. 协程实现异步IO

      import time
      import threading
      
      
      # 版本一
      gen = None
      
      def longIo():
          def run():
              global gen
              print("开始延时操作。。。")
              time.sleep(5)
              try:
                  gen.send("我发送的数据")
              except StopIteration as e:
                  pass
              print("结束延时操作。。。")
          threading.Thread(target=run).start()
      
      
      def genCoroutine(func):
          def wrapper(*args, **kwargs):
              global gen
              gen = func(*args, **kwargs)
              next(gen)
          return wrapper
      
      
      @genCoroutine
      def reaA():
          print("开始执行reaA")
          res = yield longIo()
          print("接收的数据", res)
          print("结束执行reaA")
      
      
      def reaB():
          print("开始执行reaB")
          time.sleep(2)
          print("结束执行reaA")
      
      
      def main():
          reaA()
          reaB()
      
      main()
      
      
      # 版本二
      def longIo():
          print("开始延时操作。。。")
          time.sleep(5)
          print("结束延时操作。。。")
          yield "我发送的数据"
      
      
      def genCoroutine(func):
          def wrapper(*args, **kwargs):
              gen1 = func(*args, **kwargs)  # reqA生成器
              gen2 = next(gen1)  # longIo的生成器
      
              def run(g):
                  res = next(g)  # 拿到longIo的返回数据
                  try:
                      gen1.send(res)  # 返回给reqA数据
                  except Exception as e:
                      pass
              threading.Thread(target=run, args=(gen2,)).start()
          return wrapper
      
      
      @genCoroutine
      def reaA():
          print("开始执行reaA")
          res = yield longIo()
          print("接收的数据", res)
          print("结束执行reaA")
      
      
      def reaB():
          print("开始执行reaB")
          time.sleep(2)
          print("结束执行reaA")
      
      
      def main():
          reaA()
          reaB()
      
      main()
      

      输出结果:

      开始执行reaA
      开始延时操作。。。
      开始执行reaB
      结束执行reaA
      结束延时操作。。。
      接收的数据 我发送的数据
      结束执行reaA

    2. 异步IO加回调函数

      import time
      import threading
      
      
      def longIo(callback):
          def run(cb):
              print("开始延时操作。。。")
              time.sleep(5)
              print("结束延时操作。。。")
              cb("回调函数接收值")
          threading.Thread(target=run, args=(callback, )).start()
      
      
      def finsh(data):
          """
          回调函数
          :param data:
          :return:
          """
          print("接收到longIo的值", data)
      
      
      def reaA():
          print("开始执行reaA")
          longIo(finsh)
          print("结束执行reaA")
      
      
      def reaB():
          print("开始执行reaB")
          longIo(finsh)
          print("结束执行reaB")
      
      
      def reaC():
          print("开始执行reaC")
          longIo(finsh)
          print("结束执行reaC")
      
      reaA()
      reaB()
      reaC()
      

      输出结果:

      开始执行reaA
      开始延时操作。。。
      结束执行reaA
      开始执行reaB
      开始延时操作。。。
      结束执行reaB
      开始执行reaC
      开始延时操作。。。
      结束执行reaC
      结束延时操作。。。
      结束延时操作。。。
      接收到longIo的值 回调函数接收值
      结束延时操作。。。
      接收到longIo的值 回调函数接收值
      接收到longIo的值 回调函数接收值

    四、tornado基本框架结构

    在这里插入图片描述

    mysql_db.py

    import pymysql
    
    # 单例模式的装饰器
    # def singleton(cls, *args, **kwargs):
    #     instance = {}
    #
    #     def _singleton():
    #         if cls not in instance:
    #             instance[cls] = cls(*args, **kwargs)
    #         return instance[cls]
    #     return _singleton
    
    
    class LongMySQl(object):
        def __init__(self, host, user, passwd, dbName):
            self.host = host
            self.user = user
            self.passwd = passwd
            self.dbName = dbName
    
        def connect(self):
            self.db = pymysql.connect(self.host, self.user, self.passwd, self.dbName)
            self.cursor = self.db.cursor(cursor=pymysql.cursors.DictCursor)
    
        def close(self):
            self.cursor.close()
            self.db.close()
    
        def get_one(self, sql):
            res = None
            try:
                self.connect()
                self.cursor.execute(sql)
                res = self.cursor.fetchone()
                self.close()
            except Exception:
                print("查询失败")
            return res
    
        def get_all(self, sql):
            res = ()
            try:
                self.connect()
                self.cursor.execute(sql)
                res = self.cursor.fetchall()
                self.close()
            except Exception:
                print("查询失败")
            return res
        #
        # def get_all_obj(self, sql, tableName, *args):
        #     resList = []
        #     filedsList = []
        #     if (len(args) > 0):
        #         for item in args:
        #             filedsList.append(item)
        #     else:
        #         filedsSql = "select COLUMN_NAME from information_schema.COLUMNS where table_name = '%s' and table_schema = '%s'"%(
        #             tableName,
        #             self.dbName
        #         )
        #         fields = self.get_all(filedsSql)
        #         for item in fields:
        #             filedsList.append(item[0])
        #
        #     res = self.get_all(sql)
        #     for item in res:
        #         obj = {}
        #         count = 0
        #         for x in item:
        #             obj[filedsList[count]] = x
        #             count += 1
        #         resList.append(obj)
        #     return resList
    
        def insert(self, sql):
            return self.__edit(sql)
    
        def update(self, sql):
            return self.__edit(sql)
    
        def delete(self, sql):
            return self.__edit(sql)
    
        def __edit(self, sql):
            count = 0
            try:
                self.connect()
                self.cursor.execute(sql)
                self.db.commit()
                count = self.cursor.lastrowid
                self.close()
            except Exception:
                print("事务提交失败")
                self.db.rollback()
            return count
    

    orm.py(只有增加功能)

    from application import Application
    
    
    class ORM(Application):
        def save(self):
            tableName = (self.__class__.__name__).lower()
            fieldsStr = valuesStr = "("
            for field in self.__dict__:  # {'name': 1, 'gender': 2}
                fieldsStr += (field + ",")
                if isinstance(self.__dict__[field], str):
                    valuesStr += ("'" + self.__dict__[field] + "',")
                else:
                    valuesStr += (str(self.__dict__[field]) + ",")
            fieldsStr = fieldsStr[:len(fieldsStr) - 1] + ")"
            valuesStr = valuesStr[:len(valuesStr) - 1] + ")"
            sql = "insert into " + tableName + " " + fieldsStr + " values" + valuesStr
            self.db.insert(sql)
    
        def delete(self):
            pass
    
        def update(self):
            pass
    
        @classmethod
        def all(self):
            print("all")
    
        def filter(self):
            pass
    

    models.py

    from db.orm import ORM
    
    
    class Student(ORM):
        def __init__(self, sname, gender):
            self.name = sname
            self.gender = gender
    

    server.py

    from tornado import ioloop  # tornado的核心IO循环模块,封装了linux的epoll和BSD的kqueue
    from tornado import httpserver
    import application
    import config
    
    
    if __name__ == '__main__':
        app = application.Application()
        http_server = httpserver.HTTPServer(app)
        http_server.bind(config.options.get("port"))  # 第三种方式
        http_server.start(1)  # 第三种方式
        ioloop.IOLoop.current().start()
    

    config.py

    import os
    
    
    BASE_DIRS = os.path.dirname(__file__)
    
    # 参数
    options = {
        "port": 8000
    }
    
    
    # 数据库配置
    mysql = {
        "host": "127.0.0.1",
        "user": "root",
        "passwd": "",
        "dbName": "db1"
    }
    
    # 配置
    
    settings = {
        "static_path": os.path.join(BASE_DIRS, "static"),
        "template_path": os.path.join(BASE_DIRS, "templates"),
        # """
        # 加密cookie
        # """
        "cookie_secret": "G0SAhaKwQ6eU/lqXoW+b3g/at/cy1EKDluw/JcFlYtA=",
        # """
        # 设置tornado是否在调试模式下
        # 为Ture时:
        #     1.在Ture下可以自动重启(有改动时)
        #     2.取消缓存编译的模板(页面缓存)
        #     3.取消缓存静态文件的hash值(CSS样式改动的缓存)
        #     4.提供追踪信息()
        # """
        "debug": False,
        # """
        # 为Ture时:
        #     仅仅使用自动重启
        # """
        "autoreload": True,
        # """
        # 为Fale:
        #     仅仅取消缓存编译的模板
        # """
        "compiled_template_cache": False,
        # """
        # 为Fale:
        #     仅仅取消缓存静态文件的hash值
        # """
        "static_hash_cache": False,
        # """
        # 为True:
        #     仅仅提供追踪信息
        # """
        "serve_traceback": True,
        "xsrf_cookie": True,
        "login_url": "/login"
    }
    

    application.py

    from db.mysql_db import LongMySQl
    from tornado import web
    from views import index
    import config
    import os
    
    
    class Application(web.Application):
        def __init__(self):
            handlers = [
                # (r"/", index.IndexHandler),
                (r"/getone", index.GetOne, {"first": "1", "second": "2"}),  # 传参
                web.url(r"/parameter", index.ParameterHandler, name="parameter"),  # 反向解析
                (r"/ret_json", index.JsonHandler),  # json
                (r"/ret_json2", index.Json2Handler),  # json2
                (r"/header", index.HeaderHandler),  # 响应头
                (r"/status", index.StatusHandler),  # 状态码
                (r"/redirect", index.RedirectHandler),  # 重定向
                (r"/errostatus", index.ErrorHandler),  # 抛出错误
                (r"/re/(\w+)", index.ReHandler),  # 正则匹配
                # (r"/re/(?P<p1>\w+)", index.ReHandler),  # 正则匹配
                (r"/reget", index.ReGet),  # get请求获取参数
                (r"/studentdb", index.StudentsHandler),  # 数据库操作
                (r"/cookie", index.CookieHandler),  # cookie
                (r"/sccookie", index.ScCookieHandler),  # 加密cookie
                (r"/cookienum", index.CookieCountHandler),  # cookie记录访问次数
                (r"/xsrf", index.XsrfHandler),  # xsrf
                (r"/setxsrf", index.SetXsrfHandler),  # xsrf
                (r"/login", index.LoginHandler),  # xsrf
                (r"/auth", index.AuthHandler),  # auth认证
                # (r"/callbackasy", index.CallbackAsy),  # 回调函数异步请求
                (r"/genasy", index.GenvenletAsy),  # 回调函数异步请求
                (r"/genasy2", index.GenvenletAsy2),  # 回调函数异步请求
                (r"/chat", index.ChatHandler),  # websocket
                (r"/home", index.HomeHandler),  # 聊天home页面
                (r"/(.*)$", index.StaticFileHandler,
                 {"path": os.path.join(config.BASE_DIRS, "static/html"), "default_filename": "main.html"}),  # 主页
            ]
            super(Application, self).__init__(handlers, **config.settings)  # 执行web.Application的__init__方法
            self.db = LongMySQl(
                config.mysql.get("host"),
                config.mysql.get("user"),
                config.mysql.get("passwd"),
                config.mysql.get("dbName"),
            )
    

    index.py(*****)

    from tornado.httpclient import AsyncHTTPClient
    from tornado.web import RequestHandler
    # from tornado.httputil import HTTPFile
    from tornado.websocket import WebSocketHandler
    from tornado import web
    import config
    import tornado
    import json
    import os
    
    
    """
    视图:
        request对象:
            self.request.XXX
                request.method
                request.post
                request.uri
                request.host
                request.path
                request.query  # 请求参数部分
                request.version  # HTTP版本
                request.headers  # 字典类型
                request.body  # 请求体数据
                request.remote_ip  # 客户端ip
                request.files  # 用户上传的文件
        响应:
             self.write("xxxx")   # 可以写多个
             self.finish()  # 在finish下边就不要再写write
             
        请求方式:
             get, post, delete, put, patch, 
             head(类似于get请求,只不过响应中没有具体的内容,用户获取报头),
             options(返回URL支持的所有HTTP方法) 
             
        请求函数:
             initialize()
             prepare()  # 在请求方法之前执行 类似于中间件
             set_default_headers()
             write_error()
             on_finish()  # 在请求处理后执行,进行资源清理释放,或者日志处理
             执行顺序:
                在正常情况下:set_default_headers,initialize,prepare,(HTTP方法),on_finish
                在抛出错误时:set_default_headers,initialize,prepare,(HTTP方法),set_default_headers,write_error,on_finish
        
    """
    """
    模板:
        1.配置模板路径
        2.渲染并返回:self.render(),self.render("index.html", num=100, dit=dict, **dict),
        3.语法:
            {{var}},{{a+b}}(可以是表达式)
            {{dit["name"]}}  #  字典取值
        4.条件语句:
            {% if xxx %}
            {% elif xxx %}
            {% else %}
            {% end %}
        5.循环:
            {" for i in arr "}
            {" end "}
        6.函数:
            static_url():
                    <link rel="stylesheet" href="{{static_url('css/my_css.css')}}">
                    优点: 创建了一个基于文件内容的hash值,并将其添加到URL末尾,这个hash值总能保证加载的总是最新版本
            自定义函数:
                    def sum(a, b):
                        return a + b                
                    self.render(),self.render("index.html", sum=sum)
                    {{sum(10,20)}}
        7.转义
            
            raw:
                str = "<h1>nihao<h1\>"  
                {% raw str %}
            autoescape:
                {% autoescape None %}
                {{ str }}
            
            在配置中修改:
              setting中添加:
                "autoescape": None
                
            {{escape(str)}}开启转义
            
        8.继承:
            {% extends "base.html" %}
            {% block main %}
            
            {% end %}d
        9.静态文件
            static_path
            StaticFileHandler: 是tornado预制的用来提供静态资源的handler
            可以通过tornado.web.StaticFileHandler
                (r"/(.*)$", StaticFileHandler, {
                "path": os.path.join(config.BASE_DIRS, "static/html"),
                "default_filename": "index.html"
                }),  # 指定访问静态文件路径,和默认文件(写在所有路由下面)
        
        10.数据库:
            tornado未带ORM
            
    安全cookie:
        setting中设置:"cookie_secret" = "xxxxxxxxxxx"   # 用base64与uuid生成
        ret = base64.b64encode(uuid.uuid4().bytes+uuid.uuid4().bytes)
        
    异步:
        from tornado.httpclient import AsyncHTTPClient
        from tornado.httpclient import HTTPResponse, HTTPRequest
    
        因为epoll主要用来解决网络IO的并发问题,所以Tornado的异步也是主要体现在网络的IO异步上,即异步web请求
        AsyncHTTPClient: 提供异步web请求客户端,用来进行异步web请求
        fetch(request, callback = None)  用于执行一个web请求,并异步响应返回一个HTTPResponse
                                         request可以是一个url, 也可以是一个HTTPRequest对象   
    """
    
    
    class IndexHandler(RequestHandler):
        """处理get请求"""
        def get(self, *args, **kwargs):
            url = self.reverse_url("parameter")
            self.write("hello world  <a href='{}'>到反向解析界面</a>".format(url))  # 做出响应数据
    
    
    class GetOne(RequestHandler):
        """
        接收参数,写死的参数
        """
        def initialize(self, first, second):
            """
            接收参数(在执行get之前执行)
            :return:
            """
            self.first = first
            self.second = second
    
        def get(self, *args, **kwargs):
            print(self.first, self.second)
            self.write("hello world")
    
    
    class ParameterHandler(RequestHandler):
        """
        获取用户传参,反向解析url
        """
        def get(self, *args, **kwargs):
            page = self.get_query_argument("page")  # 获取用户传参
            self.write("我是反向解析url过来的")
    
    
    class JsonHandler(RequestHandler):
        """
        返回json数据
        """
        def get(self, *args, **kwargs):
            per = {
                "code": 1001,
                "msg": ""
            }
            per_json = json.dumps(per)
            self.set_header("Content-Type", "application/json;charset=UTF-8")  # 设置响应头
            """返回的Content-Type为text/html"""
            self.write(per_json)
    
    
    class Json2Handler(RequestHandler):
        """
        返回json数据
        """
        def get(self, *args, **kwargs):
            per = {
                "code": 1001,
                "msg": ""
            }
            """write方法可以帮你转成json字符串,返回的Content-Type类型为application/json类型"""
            self.write(per)
    
    
    class HeaderHandler(RequestHandler):
        """
        设置响应头
        """
        def set_default_headers(self):
            """
            在get之前调用
            :return:
            """
            self.set_header("Content-Type", "text/html;charset=UTF-8")
    
    
    class StatusHandler(RequestHandler):
        """
        设置状态码
        """
        def get(self, *args, **kwargs):
            """若reason值为None,则状态码为正常值(如404,500....)"""
            self.set_status(status_code=1000, reason="描述状态码")
            self.write("xxxxxxx")
    
    
    class RedirectHandler(RequestHandler):
        """
        重定向
        """
        def get(self, *args, **kwargs):
            self.redirect("/")
    
    
    class ErrorHandler(RequestHandler):
        """
        抛出错误信息
        """
        def write_error(self, status_code, **kwargs):
            code = 200
            if status_code == 500:
                code = 500
                self.write("服务器内部出错")  # 返回500界面
            elif status_code == 404:
                code = 400
                self.write("资源不存在")  # 返回404界面
            self.set_status(code)
    
        def get(self, *args, **kwargs):
            flag = int(self.get_query_argument("flag"))
            if flag == 0:
                self.send_error(500)
            self.write("你是对的")
    
    
    class ReHandler(RequestHandler):
        def get(self, p1, *args, **kwargs):
            print(p1)
            self.write("re...")
    
    
    class ReGet(RequestHandler):
        """
        获取请求数据:
        无论get请求还是post请求都可以用:
            self.get_argument()s
            self.get_argument()
        """
        def get(self, *args, **kwargs):
            """
            获取get请求数据
             self.get_query_argument(name="", default=ARG_DEFAULT, strip=True)
            如果出现同名参数,则返回最后一个值的结果;default:若没有name则返回默认值
            若default也未设置,则报错
            strip:去除左右空格
            """
            # page_list = self.get_query_arguments()  # 这样拿到的是一个传参列表
            # page = self.get_query_argument("page")  # 拿到单个数据
            self.render("login.html")
    
    
    
        def post(self, *args, **kwargs):
            """
            获取post请求数据
            self.get_body_argument(name="", default=ARG_DEFAULT, strip=True)
            :param args:
            :param kwargs:
            :return:
            """
            username = self.get_body_argument("username", strip=True)
            password = self.get_body_argument("password", strip=True)
            """
            file数据格式:
            {
            'img': [
                {'filename': 'mmexport1526622518750.jpg', 'body':b"xxxxxx", 'content_type': 'image/jpeg'}
                {'filename': 'mmexport1526622518750.jpg', 'body':b"xxxxxx", 'content_type': 'image/jpeg'}
                ]
            "file":[
                {'filename': 'mmexport1526622518750.jpg', 'body':b"xxxxxx", 'content_type': 'image/jpeg'}
            ]
            }
             """
            files = self.request.files
            for file in files:
                filearr = files[file]
                for fileobj in filearr:
                    # 存储
                    file_path = os.path.join(config.BASE_DIRS, "upload/" + fileobj.filename)
                    with open(file_path, "wb") as f:
                        f.write(fileobj.body)
            hobby = self.get_body_arguments("hobby", strip=True)
            print(username, password, hobby, files)  # 李龙飞 123 ['pain', 'read', 'run']
            self.write("okk")
    

    login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录</title>
    </head>
    <body>
    <form action="/reget" method="post" enctype="multipart/form-data">
        姓名:<input type="text" name="username">
        <hr>
        密码:<input type="password" name="password">
        <hr>
        文件:<input type="file" name="file">
        <hr>
        爱好:
        <input type="checkbox" value="pain" name="hobby">画画
        <input type="checkbox" value="read" name="hobby">读书
        <input type="checkbox" value="run" name="hobby">跑步
        <input type="submit" name="登录"><span>第{{count}}次登录</span>
    </form>
    </body>
    </html>
    

    class StudentsHandler(RequestHandler):
        """
        数据库操作
        """
        def get(self, *args, **kwargs):
            sql = "select sname, gender from student"
            student_list = self.application.db.get_all(sql)
            print(student_list)
            self.render("student_show.html", student_list=student_list)
    

    student_show.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>学生列表</title>
    </head>
    <body>
    <ul>
        <li style="color: red">姓名--------性别</li>
        {% for student in student_list %}
         <li>{{ student["sname"] }} ------->{{ student["gender"] }}</li>
        {% end %}
    </ul>
    </body>
    </html>
    

    class CookieHandler(RequestHandler):
        """
        self.set_cookie(
        name="",  # cookie名
        value="",  # cookie值
        domain="",  # 提交cookie时匹配的域名
        expires=None,  # 设置有效期,可以是时间戳,时间元组,datetime型, 为UTC时间
        path="/",  # 提交cookie时默认的路径
         expires_days=5  # 设置有效期天数, 优先级低于expires
         )
         执行清除cookie后,并不是立即删除历览器的cookie,而是给cookie值设置为空,并改变其有效期限为失效,真正删除cookies是由浏览器自己去清理的
    
        """
        def get(self, *args, **kwargs):
            self.set_cookie("li", "youxiu")  # 实质上是调用了self.set_header("Set-Cookie", "li=youxiu;path=/")
            # self.get_cookie("li", "未登录")  # 获取cookie
            # self.clear_cookie("li")  # 清除cookie
            # self.clear_all_cookies(path="/", domain=None)  # 删除同时匹配path和domain的所有cookie
            self.write("cookie")
    
    
    class ScCookieHandler(RequestHandler):
        """
        uuid, base64
        secret = base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
        在settings中设置 "cookie_secret": "G0SAhaKwQ6eU/lqXoW+b3g/at/cy1EKDluw/JcFlYtA=",
        self.set_secure_cookie(name="", value="", expires_days=30, version=None, **kwargs)
        self.get_secure_cookie(name="", value="", max_age_days=31, min_version=None)  #  max_age_days:过滤出在这个期限内的时间
    
        """
        def get(self, *args, **kwargs):
            self.set_secure_cookie("key", "123")  # 有问题, 设置不上
            self.write("...")
    
    
    class CookieCountHandler(RequestHandler):
        """
        cookie计数
        """
        def get(self, *args, **kwargs):
            count = self.get_cookie("count", default=None)
            if count:
                count = int(count)
                count += 1
            else:
                count = 1
            self.set_cookie("count", str(count), expires=1)
            self.write("第{}次访问".format(str(count)))
    
    
    class XsrfHandler(RequestHandler):
        """
        setting中设置:"xsrf_cookie": True
        模板中添加{% module xsrf_form_html() %}
        也可以发ajax请求:
            $.ajax({
                .....
                headers:{
                    "X-XSRFToken": "xsrf_token"
                }
    
            })
        """
        def get(self, *args, **kwargs):
            count = self.get_cookie("count", default=None)
            if not count:
                count = 1
            self.render("postfilexsrf.html", count=count)
    
        def post(self, *args, **kwargs):
            count = self.get_cookie("count", default=None)
            if count:
                count = int(count)
                count += 1
            else:
                count = 1
            self.set_cookie("count", str(count))
            self.redirect("/xsrf")
    

    postfilexsrf.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录</title>
    </head>
    <body>
    <form action="/xsrf" method="post" enctype="multipart/form-data">
        姓名:<input type="text" name="username">
        <hr>
        密码:<input type="password" name="password">
        <hr>
        文件:<input type="file" name="file">
        <hr>
        爱好:
        <input type="checkbox" value="pain" name="hobby">画画
        <input type="checkbox" value="read" name="hobby">读书
        <input type="checkbox" value="run" name="hobby">跑步
        <input type="submit" name="登录"><span>第{{count}}次登录</span>
    </form>
    
    <script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script>
        function getCookie(name){
            var $cookie = document.cookie.match("\\b" + name + "=([^;]*)\\b")
            return $cookie ? $cookie[1]:undefined
        }
        $.ajax({
            url: "",
            method: "POST",
            data: {
    
            },
            success:function (data) {
    
            },
            header: {
                "X_XSRFToken": getCookie("_xsrf")
            }
        })
    </script>
    </body>
    </html>
    

    class SetXsrfHandler(RequestHandler):
        def get(self, *args, **kwargs):
            self.xsrf_token
            self.finish()
    
    
    class StaticFileHandler(web.StaticFileHandler):
        """
        在主页就设置"_xsrf"
        """
        def __init__(self, *args, **kwargs):
            super(StaticFileHandler, self).__init__(*args, **kwargs)
            self.xsrf_token
    
    
    class LoginHandler(RequestHandler):
        """
        登录界面
        """
        def get(self, *args, **kwargs):
            next = self.get_argument("next", "/")
            url = "login?next=" + next
            self.render("login2.html", url=url)
        def post(self, *args, **kwargs):
            next = self.get_argument("next", "/")
            name = self.get_body_argument("username")
            passwd = self.get_body_argument("password")
            print(name, passwd)
            if name == "long" and passwd == "123":
                print(next)
                self.redirect(next + "?flag=logined")
            else:
                self.redirect("/login?next=" + next)
    

    login2.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录</title>
    </head>
    <body>
    <form action="{{ url }}" method="post" enctype="multipart/form-data">
        {% module xsrf_form_html() %}
        姓名:<input type="text" name="username">
        <hr>
        密码:<input type="password" name="password">
        <hr>
        文件:<input type="file" name="file">
        <hr>
        爱好:
        <input type="checkbox" value="pain" name="hobby">画画
        <input type="checkbox" value="read" name="hobby">读书
        <input type="checkbox" value="run" name="hobby">跑步
        <input type="submit" name="登录"><span></span>
    </form>
    </body>
    </html>
    

    class AuthHandler(RequestHandler):
        """
        认证
        """
        def get_current_user(self):
            """
            若返回为Ture则验证成功
            若返回值为False则重定向到settings 中login_url所指定的路由
            :return:
            """
            flag = self.get_argument("flag", default="")
            return flag
    
        @web.authenticated  # 认证装饰器
        def get(self, *args, **kwargs):
            self.write("你好啊")
    
    
    # class CallbackAsy(RequestHandler):
    #     """
    #     用回调函数实现异步请求(这个有问题)
    #     """
    #     def on_response(self, response):
    #         if response.error:
    #             self.send_error(500)
    #         else:
    #             data = response.body
    #             self.write(data)
    #         self.finish()
    #
    #     @web.asynchronous
    #     def get(self, *args, **kwargs):
    #         url = "www.baidu.com"
    #         client = AsyncHTTPClient()  # 创建客户端
    #         client.fetch(url, self.on_response)
    
    
    class GenvenletAsy(RequestHandler):
        """
        用协程实现异步请求
        """
        @web.gen.coroutine
        def get(self, *args, **kwargs):
            url = "https://www.baidu.com"
            client = AsyncHTTPClient()  # 创建客户端
            res = yield client.fetch(url)
            if res.error:
                self.send_error(500)
            else:
                data = res.body
                self.write(data)
    
    
    class GenvenletAsy2(RequestHandler):
    
        def get(self, *args, **kwargs):
            res = yield self.getData()
            self.write(res)
    
        @web.gen.coroutine
        def getData(self):
            url = "www.baidu.com"
            client = AsyncHTTPClient()
            data = yield client.fetch(url)
            if data.error:
                data = {"ret": 0}
            else:
                data = data.body
            raise web.gen.Return(data)
    
    
    class HomeHandler(RequestHandler):
        def get(self, *args, **kwargs):
            self.render("home.html")
    
    
    class ChatHandler(WebSocketHandler):
        user = []  # 存储每一个人的信息
    
        def open(self):
            """
            websocket连接建立后执行
            :return:
            """
            self.user.append(self)
            for user in self.user:
                user.write_message("[{}]登录了".format(self.request.remote_ip))
    
        def on_message(self, message):
            """
            当客户端发送消息过来时调用
            :param message:
            :return:
            """
            for user in self.user:
                user.write_message("[{}]说:{}".format(self.request.remote_ip, message))
        #
        def on_close(self):
            """
            当websocket链接关闭后调用
            :return:
            """
            self.user.remove(self)
            for user in self.user:
                user.write_message("[{}]退出了".format(self.request.remote_ip))
        #
        # def write_message(self, message, binary=False):
        #     """
        #     主动向客户端发送消息
        #     :param message: 可以是字符串,或者字典(转成json字符串),
        #     :param binary:如果binary为false,则message会以UTF-8编码发送,为Ture发送二进制,字节码
        #     :return:
        #     """
        #     pass
    
        def close(self, code=1001, reason="123"):
            """
            关闭websocket链接, 服务器主动关闭
            :param code:
            :param reason:
            :return:
            """
            pass
    
        def check_origin(self, origin):
            """
            判断源origin, 对于符合的请求允许链接,同源策略
            :param origin:
            :return:
            """
            return True
    

    home.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>聊天界面</title>
    </head>
    <body>
    <div id="contents" style="width: 500px;height: 500px;overflow: auto">
    
    <div>
        <input type="text" id="message">
        <button id="send">发送</button>
    </div>
    </div>
    <script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script>
        var ws = new WebSocket("ws://127.0.0.1:8000/chat"); // 建立连接
    
    
        ws.onmessage = function(e){
            $("#contents").append("<p>" + e.data + "</p>")
        };  // 接收服务器消息
    
    
        $("#send").on("click", function () {
            var $mes = $("#message");
            var message = $mes.val();
            ws.send(message);
            $mes.val("")
        })  // 向服务器发消息
    </script>
    
    </body>
    </html>
    
    展开全文
  • #Python3中tornado高并发框架

    万次阅读 2019-01-10 02:59:13
    Python3中tornado高并发框架 简介: Tornado是一种 Web 服务器软件的开源版本。Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。 得...
  • tornado-5.1版本

    2019-07-23 00:54:02
    tornado-5.1版本 server.py python server.py执行 import tornado.ioloop import tornado.options import tornado.web from tornado.options import define, options from handler...
  • [33]python Web 框架:Tornado

    万次阅读 多人点赞 2019-12-29 19:09:46
    1.Tornado Tornado:python编写的web服务器兼web应用框架 1.1.Tornado的优势 轻量级web框架 异步非阻塞IO处理方式 出色的抗负载能力 优异的处理性能,不依赖多进程/多线程,一定程度上解决C10K问题 ...
  • tornado 简易教程

    万次阅读 2019-04-11 14:51:39
    引言 回想Django的部署方式 以Django为代表的python web应用部署时采用wsgi协议与服务器对接(被服务器托管),而这类服务器通常都是基于多线程的,也就是说每一个网络请求服务器都会有一个对应的线程来用web应用...
  • tornado

    万次阅读 2018-04-23 07:28:47
    引言回想Django的部署方式以Django为代表的python web应用部署时采用wsgi协议与服务器对接(被服务器托管),而这类服务器通常都是基于多线程的,也就是说每一个网络请求服务器都会有一个对应的线程来用web应用(如...
  • tornado入门总结

    千次阅读 2018-12-17 10:09:28
    Tornado全称Tornado Web Server,是一个用Python语言写成的Web服务器兼Web应用框架,由FriendFeed公司在自己的网站FriendFeed中使用,被Facebook收购以后框架以开源软件形式开放给大众。 特点 作为Web框架,是一个...
  • Tornado简介

    2020-04-12 16:26:39
    Tornado龙卷风是一个开源的网络服务器框架,它是基于社交聚合网站FriendFeed的实时信息服务开发而来的 Tornado是使用Python编写的Web服务器兼Web应用框架 与主流Web服务器框架不同的是,Tornado是异步非阻塞式...
  • Tornado高并发处理方法

    万次阅读 2017-04-06 19:37:19
    #!/bin/env python # -*- coding:utf-8 -*- import tornado.httpserver import tornado.ioloop import tornado.options import tornado.webimport tornado.gen from tornado.concurrent import run_on_executor from
  • windows下安装tornado

    万次阅读 2017-01-04 20:00:49
    听人说在windows下安装tornado比较困难,,于是乎去Ubuntu下鼓捣了半天,还是有bug,实在不知道哪有问题,于是乎又想试试在windows下怎么装,发现就几步过程即可:配置前提是需要安装python,并且需要是2.x以上的...
  • sudo pip3 install tornado -i ...安装 tornado后仍旧提示 ImportError: No module named 'tornado' 原因是本主机器上python3有两个版本 python3.5和python3.6,默认的python3 指向了python3.5,而tornado...
  • 当ModuleNotFoundError: No module named 'tornado'问题出现时,问题原因为缺少库文件,增加库文件的方法如下。 一、找到pycharm内的设置页面如下图所示: 二、选中project,在试图中点击加号增加徐哦...
1 2 3 4 5 ... 20
收藏数 25,372
精华内容 10,148
关键字:

tornado