![[Karrigell]Karrigell网络框架结构分析.doc_第1页](http://file.renrendoc.com/FileRoot1/2019-2/8/857d3089-fa1d-42f3-9019-78a76ee02f1d/857d3089-fa1d-42f3-9019-78a76ee02f1d1.gif)
![[Karrigell]Karrigell网络框架结构分析.doc_第2页](http://file.renrendoc.com/FileRoot1/2019-2/8/857d3089-fa1d-42f3-9019-78a76ee02f1d/857d3089-fa1d-42f3-9019-78a76ee02f1d2.gif)
![[Karrigell]Karrigell网络框架结构分析.doc_第3页](http://file.renrendoc.com/FileRoot1/2019-2/8/857d3089-fa1d-42f3-9019-78a76ee02f1d/857d3089-fa1d-42f3-9019-78a76ee02f1d3.gif)
![[Karrigell]Karrigell网络框架结构分析.doc_第4页](http://file.renrendoc.com/FileRoot1/2019-2/8/857d3089-fa1d-42f3-9019-78a76ee02f1d/857d3089-fa1d-42f3-9019-78a76ee02f1d4.gif)
![[Karrigell]Karrigell网络框架结构分析.doc_第5页](http://file.renrendoc.com/FileRoot1/2019-2/8/857d3089-fa1d-42f3-9019-78a76ee02f1d/857d3089-fa1d-42f3-9019-78a76ee02f1d5.gif)
已阅读5页,还剩4页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Karrigell 网络框架结构分析 我喜欢有灵气的程序作品,喜欢看tvb电视剧,趁着欣赏tvb的布衣神相的同时,我决定分析一下Karrigell的框架代码,看看它是怎么实现的,也作为写zope结构分析过程中的一个插曲_。 我还是喜好从一个框架的启动开始,一直跟踪它的工作流程,从而分析它各种功能的协作与实现。 Karrigell使用的是一个异步模型,但是它不是直接使用asyncore和asynchar标准模块,原作者说是为了使处理的逻辑简单一点,但是Server的算法和asyncore的算法是一样的。(可以看看我的zope的结构分析中的medusa篇)。Karrigell的server其实非常简单就是soocket-bind-listen-accept-new client甚至可以说它只是一个通用的server,而不是httpserver(不过每一个server,在最地层都是这个样子的啦,其实我想说的是,在接受连接这一步上,Karrigell使用的是自己的手工代码,而不是现有的http server库。(zope使用的是medusa) 在接收到新的请求(accept成功后,KG创建一个asyncRequestHandler实例与它对应,并把它加入到监听队列中,这个队列是一个字典)。也就是说所有的是与协议相关的事情和响应的流程都是通过这个asyncRequestHandler(继承DialogManager和KarrigellRequestHandler)来实现的,与asyncore的设计一样,当server监听到对应的请求有数据到达时,或者可以发送数据时,就会调用DialogManager的handle_read和hande_write,从而实现数据处理。 先看handle_read,也许是类似的东西看多了,KG在完成的工作我们可以猜到:首先是收集输入的数据,然后就是解析头部,一般来说头部都只要解析到/r/n/r/n就可以了,因为python的标准模块cgi.FieldStorage会帮我们把POST或者是PUT的数据以树的结构解析出来(cgi.FieldStorage具体的处理流程可以看看我前面写的cherrpy3结构分析)。当然还要加上基本的错误处理,否则KG就会很脆弱了。 看看原代码,的确和我想象的差不多,不过这里KG做了完整头部的缓冲。这里有一个parse_request的调用这个函数是来自于BaseHTTPRequestHandler中的,正如KG所说的,其实它自己并不解析HTTP协议,所有的东西都是借助python的标准模块SimpleHTTPServr或相关的功能模块完成。所以在本分析只对于这些标准模块的结合的细节上说明,至于SimpleHTTPServer的工作方式,留在分析SimpleHTTPServer的时候再展开。 由于在初始化的过程中DialogManager并没有调用基类的_init_,所以DialogManager必须实现一些标准模块中需要的调用接口,那个wfile就是为了要实现这样的协作接口而引入的,同时一些也要覆盖一些基本的成员函数,例如;do_GET等。这里有一点需要注意的,对于POST方式提交的数据,KG做了一件非常傻的事,就是自己先缓冲提交的数据(包括:把过大的上传信息保存为临时文件,然后又通过cgi.FieldStorage来重新组织这些数据,在KG的设计中这是没有办法避免的,因为KG的设计是基于复用IO,这种模式的最大的特点(也可以说是优点/局限)就是单线程,任何的数据交互都不能阻塞,否则导致整个系统阻塞)。所以为了不阻塞,KG必须在数据完成接收完前(可以调用cgi.FieldStorage进一布分析上传的数据前)进行自己缓存,而为了节省内存,它又不可避免地创建临时文件。 在处理完Client提交地数据以后,KG很简单地根据请求地命令调用(do_XXX/GET/HEAD/POST),其实这些调用最后都会转到self.pre_handle_data()在整个函数中做了一个很奇怪地操作,那就是将self.wfile清空,到这里为止,我不能想象这个操作到底有什么含义(在后面的分析中我们可以看到它的作用)?这个self.wfile只有在parse_request调用时用于产生(保存出错的信息,但是这里却把它清空了?难道这是为了表示一个结束?还是在后面进行的处理需要重写这个self.wfile?现在先不管),接着就是调用self.handle_data() 这个self.handle_data()做的一件事就是寻路,也就是根据前面parse_request的结果得到对应的文件,这个只是用户请求的文件,不一定是真实存在的,同时translate_path函数还有转化别名(alias)路径的功能,但是从它的转换算法来看,alias路径是通过路径分析中一个名称映射的,比如:http:/localhost/alise1/file1.pyhttp:/localhost/alise2/file2.py 这里的分割顺序是固定的,也就是说必须在base的后面第一个分割路径是一个定义好的alias路径alias: doc: documentation。 路径解析完后进行的是文件类型检测,如发现是一个文件夹,那么尝试寻找index.html和index.htm,这里并没有文档上说的还会寻找index.ks、index.pih等(看到这里的时候我还没有看KarrigellRequestHandler对于handle_data的覆盖_),当发现对应的文件后,那么根据文件的后缀名猜测文件的类型,接着就是将请求的文件内容封装为一个self.sources数组里(当然这种处理的过程中,会先检查对应的数据长度,对于过长的文件长度,会独立成一个wfile对象,而对于比较小的内容,就会直接保存在self.wfile中),可以看到,这里使用了一个技巧: 首先使self.wfile对象总是指向self.sources的最后一个元素,同时在调用self.finish的时候会将一个None对象直接加入到self.source中,并把writeable调用变成true,这样就能够在handle_write的时候进行统一的对self.sources进行操作,不过在这里留了一个很大的问号-到底动态操作是怎么解析-执行-返回的呢?(这些是在KarrigellRequestHandler覆盖的handle_data中处理的) 带着这个问题,我们继续看handle_write的实现:晕,这个函数只是非常标准的socket.send的wrapper,一点动态的迹象都没有,看来这个DialogManager是处理动态资源的,看来我只能把目光放到一个从名字上就能知道它是KG非常重要的组成部分的一个模块(KarrigellRequestHandler)上,打开这个文件,我非常高兴发现了handle_data,因为根据mro规则,python解析器首先会找到这个处理函数,从而覆盖了DialogManager.handle_data函数。不用多说了,一切的动态功能就是在这里发生的。其实我写关于Karrigell的分析,最想看的就是它的模板机制。 顺便说一下,KarrigellRequestHandler除了覆盖了handle_data函数外,还覆盖了send_error函数,同时从这些代码痕迹也可以看到KG的作者开发这个框架的时候最先就是实现了静态文件的处理,然后才一步一步地吧动态功能(模板功能)加入到KG中的,我想完成这个静态的功能最最主要的目的就是熟悉HTTP协议。 现在我们一起看看KarrigellRequestHandler这个模块是怎么工作的: 首先KarrigellRequestHandler继承LocalizedRequestHandler,而LocalizedRequestHandler并没有做很多事情,从它的名字我们可以知道这个模块主要负责语言的转换的,它是出于友好(扩展),我们先分析KarrigellRequestHandler的handle_data的工作。 在KarrigellRequestHandler中的handle_data实际上是一个wrapper,它内部调用try_handle_data函数,这个才是真正负责动态脚本的加载与运行。try_handle_data函数比较长,但是结合KG的使用说明,我们很容易可以知道作者的思路(我总喜欢yy作者的想法):首先应该是路径分析(任何HTTPserver都会做的事情),然后是按照htm, html, py, pih, hip, ks的顺序搜索用户请求的文件(又或者是追加用户可能的请求文件,KG有一个很奇怪的规则,就是这些文件的优先级其实是并列的,也就是说,如果用户不明确指定想要访问的文件,那么,如果存在两个可以响应用户请求的文件,那么KG就会抛出异常),然后就是获得这些文件的内容,然后进行解析,最后把解析(运行)的结果返回给用户。 有了上面的主体流程的概念,我们在看看源代码是不是这样的,爽,基本差不多了,但是KG做了更多的东西:分析路径-虚拟路径的映射-提取路径中的子信息(也就是对于ks文件而言,如果存在多个响应函数,那么在subpath中可以指定)。在cherrpypy3中的寻路算法是根据树型对象的属性(expose=True)的方式最后得到响应的函数的,而KG中没有这种树型的对象结构,它使用的是文件平行划分,关于cherrypy3的分析请看我的另一篇分析。KG做的事情还包括;隐藏路径检查、隐藏文件扩展名的检查,这些都不是我最感兴趣的(因为这些都是很常用的代码,灵气相对较小),我最感兴趣的是它的cache算法和模板解析过程。 好,我们先来看看模板的工作过程(其实KG的方便和灵气很大程度上取决于这一特色功能上): 首先引起我注意的是Template.getScript(),顾名思义,这个函数肯定是装载对应的脚本模块,从源代码可以看到,它只是简单地使用sys.modules来装载指定的解析模块,而这些模块的import是发生在import Template的时候,结合k_config.handle_extensions来得到相应的模块的导入。当然这个模块是KG开发的(从它的算法可以看到,用这种模式,可以支持很多中类型扩展,甚至于可以支持其他的脚本语言,莫非KG想大小通吃?),每一个解析模块可以近似理解为apache中一个mod_*.so(KG连命名也抄袭了。),这种解析模块是有命名规则的,它必须包含一个命名为Script的可调用对象,因为源代码就是这么写的。好,看来这个Script就是负责解析的工作的。 我选了mod_hip.py看了一下,很简单,就是一个手工把单纯是字符串的行(string)转换为 python的语言print string的样子,不过这里它使用了tokenize模块,也就是说,在python解析之前先手工替换一些源代码,然后把结果通过StringIO保存起来,以后作为python的源码编译,然后再运行。就这么简单。需要注意的是,python的运行所需要的环境变量的传递是怎么进行的呢,在Template这个文件中给出了答案,它建了一个方便运行脚本的wrapper-ExecContext类,这个类主要做两件事情: 1、初始化名空间,也就是在模板中可以使用的名空间 2、在名空间中,启动脚本,同时它还维护了一个调用的堆栈,我想这是用于显示出错的信息 对于ExecoContent的分析,暂时先放一下,我们继续看handle_data的处理流程。 在handle_data中装载了一个脚本后,首先就是cache它,看了一些关于python代码,所有的cache都是通过字典实现的,但是有点框架会保持一个最大的cache对象数,从而在溢出后自动删除最久没有使用的对象,以节省内存。而有的框架并不进行垃圾回收。cashe以后就是进行脚本运行的环境配置,它通过self.prepare_env()函数设置,我想详细看看这个prepare_env, 用于以后和zope对比环境变量的设置:首先是COOKIE,然后是LOGGED_USER,接着是SET_COOKIE(这个是用于保存给客户端发送cookie用的),然后是QUERY,对于QUERY的处理有点特别,在调用开发者提供的处理(模板)页面前对于提交的非ascii编码的变量进行了unicode编码,也就是说,如果用户在页面中要使用这些变量前需要改变编码,对于POST提交的数据,在这里还要转化为QUERY的形式(POST提交的数据是通过cgi.FieldStorage处理的),对于不是上传文件的字段,在这里直接转换为普通的字典,注意,在这里,所有的字典变量都是一个list,即使它只有一个数据成员。完成QUERY后,这里调用了一个钩子,同时对于前面生成的字典,现在全部转化为string(对于list而言,只取第一个值),而保留为list项是通过变量名来确定的,如:choices:1,2=choices:1,2, 接着就是设置浏览器支持的语言,这个和调用模板没有关系,只是设置响应的参数而已。然后是从浏览器提交的头部信息(这个的解析是通过BaseHTTPServer.parse_request)中寻找认证信息,这些信息是通过base64编码的,所以首先需要进行base64的解码。 这个是环境变量的初始化顺序: COOKIE-LOGGED_USER-SET_COOKIE-QUERY-AUTH_USER-AUTH_PASSWORD-SESSION 再往下就是初始化session(这个也是非常重要的,在后面我想单独分析)。 接着就是初始化名空间,这个就是在脚本中使用的预定义变量。接着调用self.make_cgi_env完成调用环境的真正映射晕,这个函数的说明是:完全拷贝自CGIHTPServer, 不过这不影响我们对它了解。这个函数只是把从浏览器提交的请求中得到的信息合并到os.environ中,它做的工和prepare_env非常象,只是prepare_env是把环境变量直接绑定到这个handler对象上而已,我不知道这个make_cgi_env的作用是什么,莫非在某个地方,参数是通过os.envrion来共享的吗(这个调用在KG本身是否作用不是很大)? 从prepare_env返回后,handle_data接着就调用self.excute(script),看来现在才是真正执行脚本的时候了: 在excute中,首先是根据模块的修改时间确定重新装载的模块,然后是创建了前面提到的ExecContext对象,然后是删除了在要保护的模块,接着就是调用ExecContext的_call_函数,而这个_call_只是做了很简单的事情,那就是初始化名空间(namespace)并调用BaseScript的render函数,实际上,这个是script对象的函数,它是负责映射名空间(主要是在原来的基础上增加了THIS和_name这样的引用方式),接着就通过sys.stdout = StringIO.IO()来得到处理的输出,也就是说,输入输出必须都是字符串。然后调用: run_script-exec self.pythonCode() in ns 好了, 看到这里,我们搞清楚了KG中解析模板的过程就是,把python Code和HTML的混合产物首先经过处理(这种处理是根据请求的文件的后缀名来动态确定对应的mod_*.py处理的,而这些模块中是按照特定的接口Script来把不同的类型的文件变成pythonCode,在调用的时候就通过不同BaseScript子类的run_script函数来进行处理,这些处理包括初始化一些特殊的环境变量,例如,有些Script对象是有up变量的,而有的是没有的),当然KG有很多地方做的很全面,包括多国语言的支持,异常处理,如果存在递归调用的话,还要处理临时增加的 名空间变量等等(到这里为止,我们只是分析了HIP模型的处理,在这种模型中只是简单的字符串替换,根本不会产生名空间的增加,在处理HIP文件的时候没有使用递规机制,应为它只是简单地把字符串改变成“print string”地形式(当然,如果开发者需要也可以手工调用,不过这样其实不是很直观),而在分析ks、PIH的时候Include调用会比较多。 好,现在我们回到handle_data中继续看看将要发生什么事情: 当Script运行完毕后,所有的输出会写入到self.wfile中,这样在handle_write中就会把数据送到客户端,这里有一个比较奇怪的地方,那就是这里所有的输出都是通过写到wfile中的,也就是说按照这种处理流程,在self.sources中最多只会有三项,一项是头信息,另一项是由于脚本调用产生的,还有就是一个None,即使是对于静态文件的处理,KG都是只产生一个wfile,而没有写成一个独立的source加到self.sources中,以节省内存。 在execute中,接下来就是善后操作-保存session-最后就是记录一些新加载的模块,同时在sys.modules中把对应的模块删除,这是什么原因呢?而且KG的注释说,这是必须的,我还不清楚这里“必须”的含义。 现在我们先放一下,因为我估计这个主要是解决由于相互应用造成的原因,现在我更关心的是session的实现机制,如果聪明的你看过我的关于cherrypy3的结构分析中关于session的讨论的话,会知道cherrypy3中是通过threading.local和钩子机制实现的,那么KG又是通过什么机制来实现的呢? 这里引起了我很大的好奇心。 在KG的处理流程中,只有两个地方是和seesion的处理有关的,一个是在调用模板前加载session,一个是脚本运行结束返回后保存session的信息。好,我们先来看看session是怎么加载的: self.setSessionObject() 事实上这个函数只是一个wrapper,它只是从cookie中提取sessionid,然后根据sessionid获得对应的session 对象,如过找不到相应的对象,那么就创建一个和它对应,KG中session是每次都创建的,并没有配置的选择。从k_seesion.py的源码可以看到KG中session的实现非常简单,只是使用了python的标准模块shelve来实现,同时在KG中只能保存python的内建模块(例如:list、string、dict等等),不能自定义用户的模块,应为KG没有办法保存用户的自定义类的定义,所以在还原的时候就会出错,当然,如果你愿意,当然可以在k_session.py import 一个自定义的类,然后还原就没有问题了,不过这个是有风险的,因为你必须自己维护这个文件的更新和移植。 很自然地,session地的关闭就是shelve.close()就好了,一切就是这么简单的,看到这里,可以发现KG都是用一些很简单很简单的模块搭建起来的,和cherrypy的设计理念不同,(我觉得cherrypy的基本理念是:全对象、全配置型的,所以在它的处理流程上经过很多的配置检查和间接调用,这些都是为了使开发者可配置),而KG是提供了主体的配置,对于细节的配置就没有了,对开发者而言可以容易上手一点,不过它有一个不太好的地方,那就是它使用了大量的内部模块,这些模块是python中为了实现相互联系的功能开发的,KG只是使用了一些小的部分,但是就必须额外的做一些协调的工作,而且在很大程度上不能把握自身的运行效率(pyhton的标准 模块使用的时间是比较难预测的,除非KG的作者已经完全了解他所使用的python标准模块)。 看完了session的部分,也是时候开始学习KG的剩下几个模板功能的实现了,先看看PIH把,我想这个应该是最麻烦的,不过我们先猜猜它需要做些什么事情:有了前面关于Script的调用流程分析,PIH模块必须把这个页面变成一个可以运行的字符串,也就是说它要把HTML全部转化为print string的形式,同时也需要把的形式转化为有效的python语句,但是最烦人的就是处理python语句中的缩进语法,可以想象这个是最麻烦的。另外在前面的讨论中可以看到处理PIH时可能会引起递规的调用,在分析代码的时候,我们可以看看这个是怎么处理的。 正如我们所猜测的,PythonInsideHTML.py果然是非常复杂(说明和代码都比较多),说明部分基本和KG的使用说明一样,光看代码中的函数名,就知道绝大部分的处理都是关于缩进的,重这里也可以看出,一个语法解析器是很麻烦的事情,所以才会有lex和bison这样的工具产生出来。好,我们开始看看KG的实现, 在PythonInsightHTML.py中都是字符串操作,KG使用的是将代解析的文件一次读入内存,并采用一个字符一个字符的处理方式,这个效率是非常低的。仔细地看了KG对于PIH的解析流程,感觉代码可优化余地很大,而且从组织上并不是很出色,这里只讨论一下KG的思路: 在设计上,KG把PIH的内容分成两类,一类是纯粹的HTML,对于这些代码要做两件事:确定当前的python代码的缩进, 把它转化为self.output.write的表达方式,另外一类就是python脚本,对于这些代码只要做一件事:确定当前的代码缩进程度,就这么简单。这里没有相互调用的处理,也没有应用第三方的模板功能。实在有点失望。而且有的地方可以用正则表达式处理的,但是KG却使用了过于原始的手段,大大降低了代码的可读性和灵气。 好吧,希望我想看到的东西(与Cheetah的结合)可以在下面的分析对象中出现。 现在看看另外一个模板解析功能模块mod_ks.py,这个模块也是非常简单,它一方面和其它类型的算法一样,把所有的内容保存为一个string,等待下一步处理,所不同的是在这个处理的过程中不需要语法分析,因为在ks文件中都是python脚本,另一方面它要记录从第一行开始定义的并且名字不是以_开始的函数,这个是在后面请求的时候subpath来进行区分调用的, 例如:http:/localhost/test.ks/test_function?a=100&b=100 将会调用test.ks里面的test_function(a=100,b=100)。 mod_py.py就更简单了,简单到不能再简单了。 好,到了mod_tmpl.py, 终于然我发现了from Cheetah.Template import Template终于看到KG与Cheetah的结合。Cheetah是一个比较独立的第三方模块,而且看了一下几个文件,发现它比较复杂,而且它和KG的结合都集中在*.tmpl的处理中,所以我想在别的分析文章中单独讨论。 好,KG的主线已经出来了,我们再看看它一些相关的模块,看看它的设计理念。现在看看core中的HTMLTags.py模块:我觉得这个设计看起来比较爽: 1、使用动态的方式产生类 2、覆盖了_add_函数,非常适合人的思维习惯 3、TAG之间可以嵌套使用,这一点和一些javascript库很像,也
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 《2025技术咨询委托合同》
- (高清版)DB13∕T 2990.6-2019 毒蘑菇识别 第6部分:3A光过敏性皮炎型毒蘑菇
- 第23课《太空一日》第二课时(导学案)-七年级语文下册同步备课系列(部编版)
- 爱的温暖我家的温馨时光写景10篇
- 2025标准版多方合作经营合同范本
- 电子竞技俱乐部管理平台建设方案
- 市场营销战略策划试题集及答案解析
- 环境工程污染治理案例研究题集
- 农业社区综合开发项目协议
- 生物学遗传学知识点梳理题集
- 注册安全工程师安全生产技术培训课件
- 电商仓库流程及诊断
- 施工场地平整施工方案
- 湘少版英语三至六年级单词表(带音标)
- SCB系列干式变压器使用说明书
- 202x检察院工作总结汇报、述职报告PPT模板
- YYT 1182-2020 核酸扩增检测用试剂(盒)
- GB∕T 33212-2016 锤上钢质自由锻件 通用技术条件
- 高效液相色谱法分析(三聚氰胺)原始记录1
- 全国公共英语等级考试三教材-Monolog-and-passage原文及翻译-一字一句输入的
- 汇川伺服追剪控制指导说明完整版
评论
0/150
提交评论