上一篇《白话tornado源码之请求来了》介绍了客户端请求在tornado框架中的生命周期,其本质就是利用epoll和socket来获取并处理请求。在上一篇的内容中,我们只是给客户端返回了简单的字符串,如:“Hello World”,而在实际开发中,需要使用html文件的内容作为模板,然后将被处理后的数据(计算或数据库中的数据)嵌套在模板中,然后将嵌套了数据的html文件的内容返回给请求者客户端,本篇就来详细的剖析模板处理的整个过程。

概述

上图是返回给用户一个html文件的整个流程,较之前的Demo多了绿色流线的步骤,其实就是把【self.write('hello world')】变成了【self.render('main.html')】,对于所有的绿色流线只做了五件事:

  • 使用内置的open函数读取Html文件中的内容
  • 根据模板语言的标签分割Html文件的内容,例如:{{}} 或 {%%}
  • 将分割后的部分数据块格式化成特殊的字符串(表达式)
  • 通过python的内置函数执行字符串表达式,即:将html文件的内容和嵌套的数据整合
  • 将数据返回给请求客户端

所以,如果要返回给客户端对于一个html文件来说,根据上述的5个阶段其内容的变化过程应该是这样:

classMainHandler(tornado.web.RequestHandler):defget(self):self.render("main.html",**{'data':['11','22','33'],'title':'main'})[main.html]<!DOCTYPE html>
<html>
<head lang="en"><meta charset="UTF-8"><title></title>
</head>
<body><h1>{{title}}</h1>{% for item in data %}<h3>{{item}}</h3>{% end %}</body>
</html>

XXXHandler.get

<!DOCTYPE html>
<html>
<head lang="en"><meta charset="UTF-8"><title></title>
</head>
<body><h1>{{title}}</h1>{% for item in data %}<h3>{{item}}</h3>{% end %}</body>
</html>

1.根据open函数读取html文件内容

第1块:'<!DOCTYPE html><html><head lang="en"><meta charset="UTF-8"><title></title></head><h1>'第2块:'title'第3块:'</h1> \n\n'第4块:'for item in data'第4.1块:'\n <h3>'第4.2块:'item'第4.3块:'</h3> \n'第五块:'</body>'

2.将html内容分块

'def _execute():_buffer =[]_buffer.append(\\'<!DOCTYPE html>\\n<html>\\n<head lang="en">\\n<meta charset="UTF-8">\\n<title></title>\\n</head>\\n<body>\\n<h1>\\')_tmp=titleifisinstance(_tmp, str): _buffer.append(_tmp)elif isinstance(_tmp, unicode): _buffer.append(_tmp.encode(\\'utf-8\\'))else: _buffer.append(str(_tmp))_buffer.append(\\'</h1>\\n\\')for item indata:_buffer.append(\\'\\n<h3>\\')_tmp=itemifisinstance(_tmp, str): _buffer.append(_tmp)elif isinstance(_tmp, unicode): _buffer.append(_tmp.encode(\\'utf-8\\'))else: _buffer.append(str(_tmp))_buffer.append(\\'</h3>\\n\\')_buffer.append(\\'\\n</body>\\n</html>\\')return \\'\\'.join(_buffer)'

3.将分块的内容进行处理成特殊的特殊的字符串

a、参照本篇博文的前戏 http://www.cnblogs.com/wupeiqi/p/4592637.html
b、全局变量有 title ='main';data = ['11','22','33']

4.执行字符串表示的函数

在第4步中,执行第3步生成的字符串表示的函数后得到的返回值就是要返回给客户端的响应信息主要内容。

3.13、RequestHandler的render方法

此段代码主要有三项任务:

  • 获取Html文件内容并把数据(程序数据或框架自带数据)嵌套在内容中的指定标签中本篇主题
  • 执行ui_modules,再次在html中插入内容,例:head,js文件、js内容、css文件、css内容和body
  • 内部调用客户端socket,将处理请求后的数据返回给请求客户端
class RequestHandler(object):def render(self, template_name, **kwargs):#根据Html文件名称获取文件内容并把参数kwargs嵌入到内容的指定标签内html = self.render_string(template_name, **kwargs)#执行ui_modules,再在html的内容中插入head,js文件、js内容、css文件、css内容和body信息。js_embed = []js_files = []css_embed = []css_files = []html_heads = []html_bodies = []for module in getattr(self, "_active_modules", {}).itervalues():embed_part = module.embedded_javascript()if embed_part: js_embed.append(_utf8(embed_part))file_part = module.javascript_files()if file_part:if isinstance(file_part, basestring):js_files.append(file_part)else:js_files.extend(file_part)embed_part = module.embedded_css()if embed_part: css_embed.append(_utf8(embed_part))file_part = module.css_files()if file_part:if isinstance(file_part, basestring):css_files.append(file_part)else:css_files.extend(file_part)head_part = module.html_head()if head_part: html_heads.append(_utf8(head_part))body_part = module.html_body()if body_part: html_bodies.append(_utf8(body_part))if js_files:#添加js文件# Maintain order of JavaScript files given by modulespaths = []unique_paths = set()for path in js_files:if not path.startswith("/") and not path.startswith("http:"):path = self.static_url(path)if path not in unique_paths:paths.append(path)unique_paths.add(path)js = ''.join('<script src="' + escape.xhtml_escape(p) +'" type="text/javascript"></script>'for p in paths)sloc = html.rindex('</body>')html = html[:sloc] + js + '\n' + html[sloc:]if js_embed:#添加js内容js = '<script type="text/javascript">\n//<![CDATA[\n' + \'\n'.join(js_embed) + '\n//]]>\n</script>'sloc = html.rindex('</body>')html = html[:sloc] + js + '\n' + html[sloc:]if css_files:#添加css文件paths = []unique_paths = set()for path in css_files:if not path.startswith("/") and not path.startswith("http:"):path = self.static_url(path)if path not in unique_paths:paths.append(path)unique_paths.add(path)css = ''.join('<link href="' + escape.xhtml_escape(p) + '" ''type="text/css" rel="stylesheet"/>'for p in paths)hloc = html.index('</head>')html = html[:hloc] + css + '\n' + html[hloc:]if css_embed:#添加css内容css = '<style type="text/css">\n' + '\n'.join(css_embed) + \'\n</style>'hloc = html.index('</head>')html = html[:hloc] + css + '\n' + html[hloc:]if html_heads:#添加html的headerhloc = html.index('</head>')html = html[:hloc] + ''.join(html_heads) + '\n' + html[hloc:]if html_bodies:#添加html的bodyhloc = html.index('</body>')html = html[:hloc] + ''.join(html_bodies) + '\n' + html[hloc:]#把处理后的信息响应给客户端self.finish(html)

对于上述三项任务,第一项是模板语言的重中之重,读取html文件并将数据嵌套到指定标签中,以下的步骤用于剖析整个过程(详情见下文);第二项是对返会给用户内容的补充,也就是在第一项处理完成之后,利用ui_modules再次在html中插入内容(head,js文件、js内容、css文件、css内容和body);第三项是通过socket将内容响应给客户端(见上篇)。

对于ui_modules,每一个ui_module其实就是一个类,一旦注册并激活了该ui_module,tornado便会自动执行其中的方法:embedded_javascript、javascript_files、embedded_css、css_files、html_head、html_body和render ,从而实现对html内容的补充。(执行过程见上述代码)

自定义UI Modules

此处是一个完整的 创建 --> 注册 --> 激活 的Demo

目录结构:

  ├── index.py
  ├── static
  └── views
     └── index.html

#!/usr/bin/env python#-*- coding:utf-8 -*-importtornado.ioloopimporttornado.webclassCustomModule(tornado.web.UIModule):defembedded_javascript(self):return 'embedded_javascript'defjavascript_files(self):return 'javascript_files'defembedded_css(self):return 'embedded_css'defcss_files(self):return 'css_files'defhtml_head(self):return 'html_head'defhtml_body(self):return 'html_body'defrender(self):return 'render'classMainHandler(tornado.web.RequestHandler):defget(self):self.render('index.html')settings={'static_path': 'static',"template_path": 'views',"ui_modules": {'Foo': CustomModule},}application= tornado.web.Application([(r"/", MainHandler), ], **settings)if __name__ == "__main__":application.listen(8888)tornado.ioloop.IOLoop.instance().start()

index.py

<!DOCTYPE html>
<html>
<head lang="en"><meta charset="UTF-8"><title></title>
</head>
<body><hr>{% module Foo() %}<hr></body>
</html>

index.html

<!DOCTYPE html>
<html>
<head lang="en"><meta charset="UTF-8"><title></title><!-- css_files --><link href="/static/css_files" type="text/css" rel="stylesheet"><!-- embedded_css --><style type="text/css">embedded_css</style></head>
<body><!-- html_head -->html_head<hr><!-- redner -->render<hr><!-- javascript_files --><script src="/static/javascript_files" type="text/javascript"></script><!-- embedded_javascript --><script type="text/javascript">//<![CDATA[embedded_javascript//]]></script><!-- html_body -->html_body</body>
</html>

执行结果:

3.13.1~6、RequestHandler的render_string方法

该方法是本篇的重中之重,它负责去处理Html模板并返回最终结果,【概述】中提到的5件事中前四件都是此方法来完成的,即:

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

注意:详细编译和执行Demo请参见《第四篇:白话tornado源码之褪去模板外衣的前戏 》

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(0)web_file = frame.f_code.co_filenamewhile frame.f_code.co_filename == web_file:frame = frame.f_backtemplate_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)

classLoader(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 afterthey are loaded the first time."""def __init__(self, root_directory):self.root=os.path.abspath(root_directory)self.templates= {}

Loader.__init__

classLoader(object):def load(self, name, parent_path=None):name= self.resolve_path(name, parent_path=parent_path)if name not inself.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

classTemplate(object):def __init__(self, template_string, name="<string>", loader=None,compress_whitespace=None):#template_string是Html文件的内容
self.name=nameif compress_whitespace isNone: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__

classTemplate(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 innamespaceexecute= namespace["_execute"]try:#执行编译好的字符串格式的函数,获取嵌套了值的html文件returnexecute()except:formatted_code=_format_code(self.code).rstrip()logging.error("%s code:\n%s", self.name, formatted_code)raise

Template.generate

其中涉及的类有:

class_TemplateReader(object):def __init__(self, name, text):self.name=nameself.text=textself.line=0self.pos=0def find(self, needle, start=0, end=None):assert start >=0, startpos=self.posstart+=posif end isNone:index=self.text.find(needle, start)else:end+=posassert end >=startindex=self.text.find(needle, start, end)if index != -1:index-=posreturnindexdef consume(self, count=None):if count isNone:count= len(self.text) -self.posnewpos= self.pos +countself.line+= self.text.count("\n", self.pos, newpos)s=self.text[self.pos:newpos]self.pos=newposreturnsdefremaining(self):return len(self.text) -self.posdef __len__(self):returnself.remaining()def __getitem__(self, key):if type(key) isslice:size=len(self)start, stop, step=key.indices(size)if start is None: start =self.poselse: start +=self.posif stop is not None: stop +=self.posreturnself.text[slice(start, stop, step)]elif key <0:returnself.text[key]else:return self.text[self.pos +key]def __str__(self):return self.text[self.pos:]

_TemplateReader

class_ChunkList(_Node):def __init__(self, chunks):self.chunks=chunksdefgenerate(self, writer):for chunk inself.chunks:chunk.generate(writer)defeach_child(self):return self.chunks

_ChunkList

def _parse(reader, in_block=None):#默认创建一个内容为空列表的_ChunkList对象body =_ChunkList([])#将html块添加到 body.chunks 列表中whileTrue:#Find next template directivecurly =0whileTrue:curly= reader.find("{", curly)if curly == -1 or curly + 1 ==reader.remaining():#EOFifin_block:raise ParseError("Missing {%% end %%} block for %s" %in_block)body.chunks.append(_Text(reader.consume()))returnbody#If the first curly brace is not the start of a special token,#start searching from the character after itif reader[curly + 1] not in ("{", "%"):curly+= 1continue#When there are more than 2 curlies in a row, use the#innermost ones.  This is useful when generating languages#like latex where curlies are also meaningfulif (curly + 2 < reader.remaining() andreader[curly+ 1] == '{' and reader[curly + 2] == '{'):curly+= 1continuebreak#Append any text before the special tokenif curly >0:body.chunks.append(_Text(reader.consume(curly)))start_brace= reader.consume(2)line=reader.line#Expressionif start_brace == "{{":end= reader.find("}}")if end == -1 or reader.find("\n", 0, end) != -1:raise ParseError("Missing end expression }} on line %d" %line)contents=reader.consume(end).strip()reader.consume(2)if notcontents:raise ParseError("Empty expression on line %d" %line)body.chunks.append(_Expression(contents))continue#Blockassert start_brace == "{%", start_braceend= reader.find("%}")if end == -1 or reader.find("\n", 0, end) != -1:raise ParseError("Missing end block %%} on line %d" %line)contents=reader.consume(end).strip()reader.consume(2)if notcontents:raise ParseError("Empty block tag ({%% %%}) on line %d" %line)operator, space, suffix= contents.partition(" ")suffix=suffix.strip()#Intermediate ("else", "elif", etc) blocksintermediate_blocks ={"else": set(["if", "for", "while"]),"elif": set(["if"]),"except": set(["try"]),"finally": set(["try"]),}allowed_parents=intermediate_blocks.get(operator)if allowed_parents is notNone:if notin_block:raise ParseError("%s outside %s block" %(operator, allowed_parents))if in_block not inallowed_parents:raise ParseError("%s block cannot be attached to %s block" %(operator, in_block))body.chunks.append(_IntermediateControlBlock(contents))continue#End tagelif operator == "end":if notin_block:raise ParseError("Extra {%% end %%} block on line %d" %line)returnbodyelif operator in ("extends", "include", "set", "import", "from","comment"):if operator == "comment":continueif operator == "extends":suffix= suffix.strip('"').strip("'")if notsuffix:raise ParseError("extends missing file path on line %d" %line)block=_ExtendsBlock(suffix)elif operator in ("import", "from"):if notsuffix:raise ParseError("import missing statement on line %d" %line)block=_Statement(contents)elif operator == "include":suffix= suffix.strip('"').strip("'")if notsuffix:raise ParseError("include missing file path on line %d" %line)block=_IncludeBlock(suffix, reader)elif operator == "set":if notsuffix:raise ParseError("set missing statement on line %d" %line)block=_Statement(suffix)body.chunks.append(block)continueelif operator in ("apply", "block", "try", "if", "for", "while"):#parse inner body recursivelyblock_body =_parse(reader, operator)if operator == "apply":if notsuffix:raise ParseError("apply missing method name on line %d" %line)block=_ApplyBlock(suffix, block_body)elif operator == "block":if notsuffix:raise ParseError("block missing name on line %d" %line)block=_NamedBlock(suffix, block_body)else:block=_ControlBlock(contents, block_body)body.chunks.append(block)continueelse:raise ParseError("unknown operator: %r" % operator)

_parse

classTemplate(object):def_generate_python(self, loader, compress_whitespace):buffer=cStringIO.StringIO()try:named_blocks={}ancestors=self._get_ancestors(loader)ancestors.reverse()for ancestor inancestors:ancestor.find_named_blocks(loader, named_blocks)self.file.find_named_blocks(loader, named_blocks)writer=_CodeWriter(buffer, named_blocks, loader, self,compress_whitespace)ancestors[0].generate(writer)returnbuffer.getvalue()finally:buffer.close()

Template._generate_python

so,上述整个过程其实就是将一个html转换成一个函数,并为该函数提供全局变量,然后执行该函数!!

  

结束语

上述就是对于模板语言的整个流程,其本质就是处理html文件内容将html文件内容转换成函数,然后为该函数提供全局变量环境(即:我们想要嵌套进html中的值和框架自带的值),再之后执行该函数从而获取到处理后的结果,再再之后则执行UI_Modules继续丰富返回结果,例如:添加js文件、添加js内容块、添加css文件、添加css内容块、在body内容第一行插入数据、在body内容最后一样插入数据,最终,通过soekct客户端对象将处理之后的返回结果(字符串)响应给请求用户。

转载于:https://www.cnblogs.com/wupeiqi/p/4592066.html

第五篇:白话tornado源码之褪去模板的外衣相关推荐

  1. 第四篇:白话tornado源码之褪去模板外衣的前戏

    加班程序员最辛苦,来张图醒醒脑吧! ... ... ... 好了,醒醒吧,回归现实看代码了!! 执行字符串表示的函数,并为该函数提供全局变量 本篇的内容从题目中就可以看出来,就是为之后剖析tornad ...

  2. 第二篇:白话tornado源码之待请求阶段

    上篇<白话tornado源码之一个脚本引发的血案>用上帝视角多整个框架做了一个概述,同时也看清了web框架的的本质,下面我们从tornado程序的起始来分析其源码. 概述 上图是torna ...

  3. 第三篇:白话tornado源码之请求来了

    上一篇<白话tornado源码之待请求阶段>中介绍了tornado框架在客户端请求之前所做的准备(下图1.2部分),本质上就是创建了一个socket服务端,并进行了IP和端口的绑定,但是未 ...

  4. 第五篇:Spring源码篇-ApplicationContext

    Spring源码篇-ApplicationContext   前面通过手写IoC,DI.AOP和Bean的配置.到最后ApplicationContext的门面处理,对于Spring相关的核心概念应该 ...

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

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

  6. tornado源码分析

    tornado源码分析 本源码为tornado1.0版本 源码附带例子helloworld import tornado.httpserver import tornado.ioloop import ...

  7. [五]RabbitMQ-客户端源码之AMQChannel

    欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...

  8. arcgis开发 多版本之间如何兼容_arcgis api 4.x for js 结合 react 入门开发系列初探篇(附源码下载)...

    你还在使用 JQuery 或者 Dojo 框架开发 arcgis api 4.x for js 吗?想试试模块化开发吗?随着前端技术的发展,arcgis api 4.x for js 也有了结合 re ...

  9. vscode中安装webpack_leaflet-webpack 入门开发系列一初探篇(附源码下载)

    前言 leaflet-webpack 入门开发系列环境知识点了解: node 安装包下载 webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址w ...

  10. 工具类五合一小程序源码星座配对+星座运势+恶搞放屁音效+引流工具+流量主

    今天给大家带来一套5合一小程序 星座配对+星座运势+恶搞放屁音效+引流工具+流量主 怎么弄?需要用到分包功能, 问:为什么是三套小程序,跟你演示的不一样啊? 答:为了防止有些人拿了东西就去闲鱼卖,只要 ...

最新文章

  1. 【java】4.27上课及做作业时遇到的问题及第十六节课笔记整理
  2. ls:显示指定目录的详细信息
  3. Visual Studio 2013 中如何设置断点
  4. 零基础是学java还是python-零基础学编程java和python哪个好
  5. 微信 WEUI 的 switch button 精简提取
  6. python写后台接口请求出错_python登录接口测试问题记录与解决
  7. Module not found: Error: Can‘t resolve ‘sass-loader‘ in E:\IdeaProject\xinguan\xinguan
  8. 如何写 go 代码 (How to Write Go Code 翻译)
  9. .NET跨平台之旅:博问站点迁移至ASP.NET Core on Linux并发布上线
  10. 【十二省联考2019】异或粽子【01Trie】【堆】【前k大套路】
  11. mybatis=<>的写法
  12. Android反翻译详解
  13. 教你如何窃取网络信息
  14. 【java】Java运行时动态生成类几种方式
  15. 使用Name Mangler快速为批量文件重命名
  16. gerber文件如何转为PCB文件
  17. 【OpenGL ES】纹理贴图
  18. 解决video标签无法播放avi格式的视频
  19. 写代码时切换insert键,用来解决光标小黑块问题
  20. 二值化最佳阈值选取方法以及matlab实现

热门文章

  1. 聊聊你们关心的视频号
  2. mysql update 几万 非常慢_MySQL慢日志这样分析才够味
  3. more 分页显示文件内容
  4. word2vec 数学原理
  5. EasyUI的增删查改(后台ASP.NET)
  6. Oracle Concepts Guide 中 Oracle 实例 和 数据库 【关系图】
  7. Android studio : 模拟器 真机 运行失败(mac电脑)
  8. Oracle 新建用户、赋予权限
  9. 字符串,字典,元祖,列表
  10. redis 字符串基本操作