[关闭]
@delight 2017-01-13T10:28:29.000000Z 字数 3208 阅读 1789

web框架源码笔记之webpy

python web


写了几年Python web,实际框架源码基本没怎么看,这次打算把web.py, djangoflask这三个框架的源码学习一下。webpy是最简单的,就从它开始吧。

注意从github拉下来的代码,默认是master分支,最好切到最近的稳定分支(tag),不然可能有很多稀奇古怪的bug,目前看到的是v0.38.

程序的入口文件,可以从web.application开始看。按着WSGI协议,数据从web服务器发到应用,主要参数都在env中,填充各种环境变量

基本语法

框架不同于一般项目,要兼容各个Python版本,所以有一些奇奇怪怪的写法,平时不怎么常见。

总体来说,这个技术其实用处不大…但是可以更清晰(而非有效)的保证可见性。

  1. def is_class(o):
  2. return isinstance(o, (types.ClassType, type))

流程解析

web.application开始看起,这里定义了一个入口类application,虽然很奇怪为啥没按着Python的命名规则来,不过webpy这个项目里面到处都是这种奇怪的写法,可能跟这个项目出现的时代有关吧。

__init__里面定义了一些初始化的操作,几个参数中,mapping是url映射,fvars是模块元素的字典,一般传入globals()autoreload一般通过调节debug开关来修改。

init_mapping将url参数改成格式[(url: classname)]的pair序列;然后调用了两个很有意思的函数loadhookunloadhook,前者需要一个函数作为参数并返回一个函数,这个函数接受一个函数作为参数,先执行参数中的函数,然后返回他自己的参数函数的执行结果。听起来非常绕,其实本质上是两个装饰器。用haskell解释一下:

  1. loadhook h = p
  2. where p x = do
  3. h
  4. return x

这东西具体干吗用的呢,其实我们定义了一个函数h(无参数),调用loadhook给它包装一下,返回了一个新的函数p。这个新函数的参数也是一个函数,我们假设为x,那么如果我们调用p(x),这个函数会先执行h,再处理x,也就是说,这里注入了一个callback,强行在执行x之前执行h. 如果叠加调用loadhook,最先添加的先执行(队列)。

unloadhook类似,不同的是,执行顺序正好相反,先调用传入的参数x,最后再调用h。如果x执行的结果不是一个数据,而是一个迭代器,当迭代器停止迭代(或者其他异常的时候)执行h。这里形成了一个堆栈,如果unloadhook(x)(y),执行顺序是y->x,那么再外围继续叠加的话:
unloadhook(unloadhook(x)(y))(z)的执行顺序就是z -> (y -> x),最后调用的最先执行。

在processeers最开始的位置,加上了hook和unhook后的_load以及_unload。前者只是将自身(即application的实例)放入web.ctx.app_stack这个全局变量(县城本地存储)中;后者是将app_stack里末尾帧弹出,并且检查是不是有需要恢复的断点。

如果打开了auto_reload,就会将Reloader()reload_mapping都加入到processors中.

request感觉是用来做测试的,服务端测试请求。从这里可以看出服务器处理请求的流程。利用env参数,构建符合WSGI标准的环境变量,这里注意主要是数据编码的修改,最后把wsgi.input转为StringIO对象。返回值更有趣,self.wsgifunc()这个函数,参数是一系列中间件(函数/对象),返回的是一个高阶函数,最里面是wsgi是真正的响应处理函数,它返回一个迭代器。真正的处理过程是:
1. 调用_cleanup清理线程本地存储;
2. 调用load加载环境变量到ctx;
3. 调用handle_with_processors进行处理;这个函数也很有意思,我们前面可以看到,appendself.processors里面的函数都是被hook或者unhook后的,这里按着lfold的思路,将一个[p1,p2,p3]的processlist转为一个p1(p2(p3(self.handle()))),类似于haskell中的p1 . p2 . p3 . handler.
4. 最里层的handler是用户写的处理逻辑。通过ctx.path对应的url,找到对应函数/类/application(sub)/字符串等情况。这里的子应用可以无限递归,类似django中的url派发机制。

倒过来看,就是真正的处理流程:先调用框架使用者的代码进行处理,完成以后将结果交给各个processor,从后道前一步一步处理,最后将结果交给中间件。这么看来有个问题,就是缺少前置的中间件,在用户处理请求之前,允许pre处理一下请求才对。其实没啥问题,这里非常绕,注意在processor中,最前面两个是loadhook(self._load)unloadhook(self._unload),所以最后调用的是loadhook(self._load)(unloadhook(self._unload)(result)),用hs描述的话:(loadhook . self._load) $ (unloadhook self._unload) result,按着loadhookunloadhook的实现,这个函数等价于

  1. self._load() # 将application放入栈
  2. for r in result:
  3. r()
  4. self._unload() # 尾部出栈

由于使用了全局变量(严格来说是线程本地存储),所以所有的processor都可以使无参数的,直接改web.ctx就可以了。

从这里可以看出,如果需要在代码中加入中间件,主要通过add_processor的方法,利用两个hook决定加载顺序。

模板技术

web.template里面是一个简单的模板解析器,里面使用python的语法。实际上一般使用jinjia2这种专业的引擎更好。

session

session的数据被存放在一个threadeddict实例里面,session作为一个processor被注入application,真正逻辑就是_load_save这两个函数,如果session里面没有id,就生成一个id,否则就根据session_id取得self.store中的上下文…不过这玩意儿会占用实际内存,所以必须有过期时间。某种意义上来讲,这玩意儿没啥意义…反正各种方法多得很。

db

webpy里面还有一个简易的db连接层,不是orm,只是方便写一些sql语句,在内部做了一些sql拼接。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注