初识tornado

首先从经典的helloword案例入手

 import tornado.ioloop
import tornado.web class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world") application = tornado.web.Application([
(r"/index", MainHandler),
]) if __name__ == "__main__":
application.listen()
tornado.ioloop.IOLoop.instance().start()
运行该脚本,依次执行:

创建一个Application对象,并把一个正则表达式'/'和类名MainHandler传入构造函数:tornado.web.Application(...)  
执行Application对象的listen(...)方法,即:application.listen(8888)
执行IOLoop类的类的 start() 方法,即:tornado.ioloop.IOLoop.instance().start()

1、分析如下代码

application = tornado.web.Application([

(r"/index", MainHandler),

])

源码截图:

由上图Application类的源码我们可以看出,需要传入的第一个参数是handlers,即上述代码可以进行如下表示:

application = tornado.web.Application(handlers=[
(r"/index", MainHandler),
])

这里的参数handlers非常重要,值得我们更加深入的研究。它应该是一个元组组成的列表,其中每个元组的第一个元素是一个用于匹配的正则表达式,第二个元素是一个RequestHanlder类。在hello.py中,我们只指定了一个正则表达式-RequestHanlder对,但你可以按你的需要指定任意多个。

<span style="color: #3366ff;">2.Application类的深入:</span><br>源码截图:

这是源码中关于静态文件路径的描述,从上到下的意思依次为:

  • 如果用户在settings有配置名为“static_path”的文件路径(这就要求我们如果需要配置静态文件路径,则key值必须是“static_path”)
  • 从源码可看出,这是根据字典的方式进行取值,因此可断定settings是字典格式
  • ,这是配置静态文件路径前缀,便于tronado在前端的静态文件路径中找到静态文件,即告诉tronado,我是静态文件,按照静态文件路径查询即可,这里,静态文件前缀也可以理解为静态文件标识。

application.listen(8888):

pplication.listen()

即Application类的listen方法:

源码截图:

由上述源码可看出,bind方法内部创建socket对象,调用socket对象绑定ip和端口,并进行监听。

tornado.ioloop.IOLoop.instance().start():

tornado.ioloop.IOLoop.instance().start()

这是tronado的ioloop.py文件中的IOLoop类:

instance方法源码截图:

这里使用了类方法,即可以通过类名直接访问。我们需要关注的是最后面的代码:

if not hasattr(cls, "_instance"):
cls._instance = cls()
return cls._instance

注:这里使用了单例模式,因为我们不需要为每一次连接IO都创建一个对象,换句话说,每次连接IO只需要是同一个对象即可

start方法:

 class IOLoop(object):
def add_handler(self, fd, handler, events):
#HttpServer的Start方法中会调用该方法
self._handlers[fd] = stack_context.wrap(handler)
self._impl.register(fd, events | self.ERROR) def start(self):
while True:
poll_timeout = 0.2
try:
#epoll中轮询
event_pairs = self._impl.poll(poll_timeout)
except Exception, e:
#省略其他
#如果有读可用信息,则把该socket对象句柄和Event Code序列添加到self._events中
self._events.update(event_pairs)
#遍历self._events,处理每个请求
while self._events:
fd, events = self._events.popitem()
try:
#以socket为句柄为key,取出self._handlers中的stack_context.wrap(handler),并执行
#stack_context.wrap(handler)包装了HTTPServer类的_handle_events函数的一个函数
#是在上一步中执行add_handler方法时候,添加到self._handlers中的数据。
self._handlers[fd](fd, events)
except:
#省略其他

由上述源码中while Ture:可以看出当执行了start方法后程序会进入死循环,不断检测是否有用户发送请求过来,如果有请求到达,则执行封装了HttpServer类的_handle_events方法和相关上下文的stack_context.wrap(handler)(其实就是执行HttpServer类的_handle_events方法),详细见下篇博文,简要代码如下:

class HTTPServer(object):
def _handle_events(self, fd, events):
while True:
try:
connection, address = self._socket.accept()
except socket.error, e:
if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
return
raise
if self.ssl_options is not None:
assert ssl, "Python 2.6+ and OpenSSL required for SSL"
try:
connection = ssl.wrap_socket(connection,
server_side=True,
do_handshake_on_connect=False,
**self.ssl_options)
except ssl.SSLError, err:
if err.args[0] == ssl.SSL_ERROR_EOF:
return connection.close()
else:
raise
except socket.error, err:
if err.args[0] == errno.ECONNABORTED:
return connection.close()
else:
raise
try:
if self.ssl_options is not None:
stream = iostream.SSLIOStream(connection, io_loop=self.io_loop)
else:
stream = iostream.IOStream(connection, io_loop=self.io_loop)
HTTPConnection(stream, address, self.request_callback,
self.no_keep_alive, self.xheaders)
except:
logging.error("Error in connection callback", exc_info=True)

tornado.web.RequestHandler

这是所有业务处理handler需要继承的父类,接下来,介绍一些RequestHandler类中常用的一些方法:

  • initialize:

从源码中可以看出initialize函数会在RequestHandler类初始化的时候执行,但是源码中initialize函数并没有做任何事情,这其实是tornado为我们预留的修改源码的地方,这就允许程序在执行所有的handler前首先执行我们在initialize中定义的方法。

  • write

源码截图:

write方法是后端向前端页面直接展示的方法。从上述源码中可以看出,write方法接收字典和字符串类型的参数,如果用户传来的数据是字典类型,源码中会自动用json对字典进行序列化,最终序列化成字符串。

self._write_buffer是源码中定义的一个临时存放需要输出的字符串的地方,是列表格式。

  • 需要write的内容添加到 self._write_buffer后,系统会执行flush方法:

flush方法源码截图:

由上述源码可以看出:flush方法会self._write_buffer列表中的所有元素拼接成字符串,并赋值给chunk,然后清空self._write_buffer列表,然后设置请求头,最终调用request.write方法在前端页面显示。

  • render方法:

源码截图:

由上述源码可看出render方法是根据参数渲染模板,下面我们来介绍具体源码中是如何渲染的:

js和css部分的源码截图:

由上述源码可看出,静态文件(以JavaScript为例,css是类似的)的渲染流程是:

首先通过module.embedded_javascript() 获取需要插入JavaScript字符串,添加到js_embed 列表中;

进而通过module.javascript_files()获取已有的列表格式的JavaScript files,最终将它加入js_files.

下面对js_embed和js_files做进一步介绍:

js_embed源码截图:

上图源码即生成script标签,这是一些我们自己定义的一些JavaScript代码;最终是通过字符串拼接方式插入到整个html中。

js_files源码截图:

上图源码即生成script标签,这是一些需要引入的JavaScript代码块;最终是通过字符串拼接方式插入到整个html中。需要注意的是:其中静态路径是调用self.static_url(path)实现的。

static_url方法源码截图:

由上述代码可看出:源码首先会判断用户有没有设置静态路径的前缀,然后将静态路径与相对路径进行拼接成绝对路径,接下来按照绝对路径打开文件,并对文件内容(f.read())做md5加密,最终将根目录+静态路径前缀+相对路径拼接在前端html中展示。

render_string方法:

这是render方法中最重要的一个子方法,它负责去处理Html模板并返回最终结果:

详细流程:

    1. 创建Loader对象,并执行load方法
          -- 通过open函数打开html文件并读取内容,并将内容作为参数又创建一个 Template 对象
          -- 当执行Template的 __init__ 方法时,根据模板语言的标签 {{}}、{%%}等分割并html文件,最后生成一个字符串表示的函数
    2. 获取所有要嵌入到html模板中的变量,包括:用户返回和框架默认
    3. 执行Template对象的generate方法
          -- 编译字符串表示的函数,并将用户定义的值和框架默认的值作为全局变量
          -- 执行被编译的函数获取被嵌套了数据的内容,然后将内容返回(用于响应给请求客户端)

源码注释:

 class RequestHandler(object):

     def render_string(self, template_name, **kwargs):

         #获取配置文件中指定的模板文件夹路径,即:template_path = 'views'
template_path = self.get_template_path() #如果没有配置模板文件的路径,则默认去启动程序所在的目录去找
if not template_path:
frame = sys._getframe()
web_file = frame.f_code.co_filename
while frame.f_code.co_filename == web_file:
frame = frame.f_back
template_path = os.path.dirname(frame.f_code.co_filename)
if not getattr(RequestHandler, "_templates", None):
RequestHandler._templates = {} #创建Loader对象,第一次创建后,会将该值保存在RequestHandler的静态字段_template_loaders中
if template_path not in RequestHandler._templates:
loader = self.application.settings.get("template_loader") or\
template.Loader(template_path)
RequestHandler._templates[template_path] = loader #执行Loader对象的load方法,该方法内部执行执行Loader的_create_template方法
#在_create_template方法内部使用open方法会打开html文件并读取html的内容,然后将其作为参数来创建一个Template对象
#Template的构造方法被执行时,内部解析html文件的内容,并根据内部的 {{}} {%%}标签对内容进行分割,最后生成一个字符串类表示的
        函数并保存在self.code字段中
t = RequestHandler._templates[template_path].load(template_name) #获取所有要嵌入到html中的值和框架默认提供的值
args = dict(
handler=self,
request=self.request,
current_user=self.current_user,
locale=self.locale,
_=self.locale.translate,
static_url=self.static_url,
xsrf_form_html=self.xsrf_form_html,
reverse_url=self.application.reverse_url
)
args.update(self.ui)
args.update(kwargs) #执行Template的generate方法,编译字符串表示的函数并将namespace中的所有key,value设置成全局变量,然后执行该函数。从而
        将值嵌套进html并返回。
return t.generate(**args) 
class Loader(object):
"""A template loader that loads from a single root directory. You must use a template loader to use template constructs like
{% extends %} and {% include %}. Loader caches all templates after
they are loaded the first time.
"""
def __init__(self, root_directory):
self.root = os.path.abspath(root_directory)
self.templates = {} Loader.__init__

Loader.__init__

class Loader(object):
def load(self, name, parent_path=None):
name = self.resolve_path(name, parent_path=parent_path)
if name not in self.templates:
path = os.path.join(self.root, name)
f = open(path, "r")
#读取html文件的内容
#创建Template对象
#name是文件名
self.templates[name] = Template(f.read(), name=name, loader=self)
f.close()
return self.templates[name] Loader.load

Loader.load

lass Template(object):

    def __init__(self, template_string, name="<string>", loader=None,compress_whitespace=None):
# template_string是Html文件的内容 self.name = name
if compress_whitespace is None:
compress_whitespace = name.endswith(".html") or name.endswith(".js") #将内容封装到_TemplateReader对象中,用于之后根据模板语言的标签分割html文件
reader = _TemplateReader(name, template_string) #分割html文件成为一个一个的对象
#执行_parse方法,将html文件分割成_ChunkList对象
self.file = _File(_parse(reader)) #将html内容格式化成字符串表示的函数
self.code = self._generate_python(loader, compress_whitespace) try:
#将字符串表示的函数编译成函数
self.compiled = compile(self.code, self.name, "exec") except:
formatted_code = _format_code(self.code).rstrip()
logging.error("%s code:\n%s", self.name, formatted_code)
raise Template.__init__

Template.__init__

class Template(object):
def generate(self, **kwargs):
"""Generate this template with the given arguments."""
namespace = {
"escape": escape.xhtml_escape,
"xhtml_escape": escape.xhtml_escape,
"url_escape": escape.url_escape,
"json_encode": escape.json_encode,
"squeeze": escape.squeeze,
"linkify": escape.linkify,
"datetime": datetime,
} #创建变量环境并执行函数,详细Demo见上一篇博文
namespace.update(kwargs)
exec self.compiled in namespace
execute = namespace["_execute"] try:
#执行编译好的字符串格式的函数,获取嵌套了值的html文件
return execute()
except:
formatted_code = _format_code(self.code).rstrip()
logging.error("%s code:\n%s", self.name, formatted_code)
raise Template.generate

Template.generate

示例html:

源码模板语言处理部分的截图:

结束语

  本博文从tornado  url正则匹配、路由与映射、底层socket实现、端口监听、多请求并发、Handler类方法的源码剖析,便于深入的理解tronado的运行机制,更高效的进行tronado开发。

tornado源码分析的更多相关文章

  1. tornado源码分析-iostream

    tornado源码分析-iostream 1.iostream.py作用 用来异步读写文件,socket通信 2.使用示例 import tornado.ioloop import tornado.i ...

  2. Tornado源码分析 --- 静态文件处理模块

    每个web框架都会有对静态文件的处理支持,下面对于Tornado的静态文件的处理模块的源码进行分析,以加强自己对静态文件处理的理解. 先从Tornado的主要模块 web.py 入手,可以看到在App ...

  3. Tornado源码分析系列之一: 化异步为'同步'的Future和gen.coroutine

    转自:http://blog.nathon.wang/2015/06/24/tornado-source-insight-01-gen/ 用Tornado也有一段时间,Tornado的文档还是比较匮乏 ...

  4. Tornado源码分析 --- Cookie和XSRF机制

    Cookie和Session的理解: 具体Cookie的介绍,可以参考:HTTP Cookie详解 可以先查看之前的一篇文章:Tornado的Cookie过期问题 XSRF跨域请求伪造(Cross-S ...

  5. Tornado源码分析 --- Redirect重定向

    “重定向”简单介绍: “重定向”指的是HTTP重定向,是HTTP协议的一种机制.当client向server发送一个请求,要求获取一个资源时,在server接收到这个请求后发现请求的这个资源实际存放在 ...

  6. Tornado源码分析之http服务器篇

    转载自 http://kenby.iteye.com/blog/1159621 一. Tornado是什么? Facebook发布了开源网络服务器框架Tornado,该平台基于Facebook刚刚收购 ...

  7. tornado源码分析系列一

    先来看一个简单的示例: #!/usr/bin/env python #coding:utf8 import socket def run(): sock = socket.socket(socket. ...

  8. tornado源码分析-多进程

    1.源码文件 process.py 2.fork子进程 def fork_processes(num_processes, max_restarts=100): ... def start_child ...

  9. Tornado源码分析 --- Etag实现

    Etag(URL的Entity Tag): 对于具体Etag是什么,请求流程,实现原理,这里不进行介绍,可以参考下面链接: http://www.oschina.net/question/234345 ...

随机推荐

  1. 固态硬盘(SSD) 和机 械硬盘(HDD) 优缺点比較

    Attribute SSD (Solid State Drive) HDD (Hard Disk Drive) Power Draw / Battery Life (功耗/电池寿命) Less pow ...

  2. windows利用jconsole远程监控linux的tomcat

    1.配置tomcat ①  编辑tomcat的catelina.sh文件,进入tomcat安装目录,使用命令:sudo vim bin/catalina.sh,如果是普通用户启动的,则修改damen. ...

  3. jquery的json的遍历

    jquery遍历解析json对象1: var json = [{dd:'SB',AA:'东东',re1:123},{cccc:'dd',lk:'1qw'}]; for(var i=0,l=json.l ...

  4. jeesite中activiti中的流程表梳理

    最近在利用jeesite开发一个小系统,趁着这个机会整理了activiti中的相关表,跟踪流程,然后查看这几个表中数据的变化,可以更好地理解流程的开发.现在整理出来,希望可以帮助更多的人! 表结构 一 ...

  5. JDK自带的定时任务

    import java.util.TimerTask; /** * 实现定时任务 * */ public class MyTimerTask extends TimerTask { @Override ...

  6. 值类型,Nullable类型

    1. 值类型 比如说int吧,是值类型,是个struct,是这样声明的 public struct Int32 : IComparable, IFormattable, IConvertible, I ...

  7. 对无向图的深度优先搜索(DFS)

    [0]README 0.1) 本文总结于 数据结构与算法分析, 源代码均为原创, 旨在 理解 如何对无向图进行深度优先搜索 的idea 并用源代码加以实现: 0.2) 本文还引入了 背向边(定义见下文 ...

  8. Zabbix二次开发_03api列表

    基于zabbix 3.0 https://www.zabbix.com/documentation/3.0/manual/api/reference Method reference This sec ...

  9. PHP-Manual的学习----【语言参考】----【类型】-----【string字符串型】

    1.一个字符串 string 就是由一系列的字符组成,其中每个字符等同于一个字节.这意味着 PHP 只能支持 256 的字符集,因此不支持 Unicode .2. string 最大可以达到 2GB. ...

  10. MySQL 下 ROW_NUMBER / DENSE_RANK / RANK 的实现

    原文链接:http://hi.baidu.com/wangzhiqing999/item/7ca215d8ec9823ee785daa2b MySQL 下 ROW_NUMBER / DENSE_RAN ...