webpy源码分析概览图
admin
2023-07-31 00:38:17
0

今天花了点时间把看了web.py的代码分析了一遍,稍稍的总结成一个图片,供有兴趣的人参考。

原因

在开始之前先来说下分析它代码的原因,昨天是打算给wechat这个项目加上异常处理,可是发现在服务器返回400错误之后,客户端获取到得responseText和我服务器端定义的不一样,我服务器端是这么返回错误的: return web.BadRequest(message=\"用户名或密码错误\") ,于是去查看了下这个BadRequest是怎么处理这个message的,发现web.py的所有http状态的返回都是基于Exception来做的(包括你想直接返回200,可以通过 web.OK 来返回。

研究到最后发现,既然是基于Exception来做的,那么就直接return是不对的,应该用raise才行——raise web.OK 或者 raise web.BadRequest(message=\"blabla..\") 。

这部分对应的处理代码在application.py#287行:

123456789101112131415 try:    # allow uppercase methods only    if web.ctx.method.upper() != web.ctx.method:        raise web.nomethod()     result = self.handle_with_processors()    if is_generator(result):        result = peep(result)    else:        result = [result]except web.HTTPError, e:    # 就在这里了,会把data取出来,如果不使用raise的话,只能去改造result对象了    result = [e.data] result = web.safestr(iter(result))  # 不用raise就改造safestr

意识到这个问题的时候,我已经通过自己的方式改造成功了,就是在web.safestr中对对象的data进行判断,当然有什么副作用不知道,这么改造:

123456789101112131415161718192021 def safestr(obj, encoding=\’utf-8\’):    r\”\”\”    Converts any given object to utf-8 encoded string.         >>> safestr(\’hello\’)        \’hello\’        >>> safestr(u\’\\u1234\’)        \’\\xe1\\x88\\xb4\’        >>> safestr(2)        \’2\’    \”\”\”    if isinstance(obj, unicode):        return obj.encode(encoding)    elif isinstance(obj, str):        return obj    elif hasattr(obj, \’next\’): # iterator        return itertools.imap(safestr, obj)    elif hasattr(obj, \’data\’):  # the5fire 添加的这个        return str(obj.data)    else:        return str(obj)

当然,最佳的方式还是要用raise来做。

源码分析开始

不管目的是什么,反正最后还是把关键代码通读了下,整理成下面这个图,不是很详细,但对于想分析的人来说应该会有些帮助:

http://the5fireblog.b0.upaiyun.com/staticfile/webpy-source-analyze.jpg

整个流程是从项目的启动开始的。

application.py

先从application.py开始执行,这是使用webpy开发是简单运行项目的入口,简单的例子就是:

123456789101112131415 import web urls = (    \’/(.*)\’, \’hello\’)app = web.application(urls, globals()) class hello:    def GET(self, name):        if not name:            name = \’World\’        return \’Hello, \’ + name + \’!\’ if __name__ == \”__main__\”:    app.run()

这里的application就是图中最上面的那个类,我把类的关键属性和方法都写了出来。

app.run() 中会执行 wsgifunc(self, *middleware) 这个方法,在这个方法里定义了一个 wsgi 的方法(上面贴得那段异常处理的代码就是wsgi中的),最后被传递出去给到 wsgi.runwsgi` 的参数中。

httpserver.py

这个 runwsgi 是在wsgi.py中定义的,作用仅仅是调用httpserver的runsimple方法,并把前面的那个 wsgi 传递进去。

这时执行到了httpserver.py的runsimple这个函数中,在此函数中,首先是调用WSGIServer这个函数生成一个server实例,然后在启动这个server。

在WSGIServer中主要工作是实例化wsgiserver包中的 CherryPyWSGIServer 这个类。顺着图再往下看 CherryPyWSGIServer 是继承自HTTPServer的。在CherryPyWSGIServer的初始化中首先是创建了一个self.requests的线程池,用来存放所有的请求连接。然后是把上面的 wsgi 赋给属性 wsgi_app ,还有就是声明网关 gateway 这个用来把应用生成的数据最终返回给客户端的组件。

wsgiserver/__init__.py

在CherryPyWSGIServer初始化是会创建一个self.requests的线程池,这个线程池在初始化时会持有这个CherryPyWSGIServer对象的实例。在上面说的启动server时——server.start(),主要工作是绑定要监听的地址和端口,然后启动self.request这个线程池。线程池在启动的时候会根据在创建requests这个线程池时指明线程池的大小来创建用来具体干活的 WorkerThread (这个每个Worker也都会持有Server的实例),并启动这些WorkerThread。

这些WorkerThead启动之后会等待,从server的线程池的队列中(也就是 self.server.requests.get() )获取来自客户端的连接 conn 。在上面的HTTPServer的定义中还有一个tick的方法,这个方法就是用来接收来自客户端的请求,然后建立连接——HTTPConnection,最后把实例conn放到requests线程池中的队列中。

在WorkerThead启动之后会调用conn——HTTPConnection的 communicate 方法。在这个方法中会生成一个HTTPRequest对象,做完一些验证和数据转换之后(根据HTTP协议,把数据放到对象中),会调用这个HTTPRequest实例的respond方法,这个respond方法中,关键的一句是: self.server.gateway(self).respond() ——调用server中gateway的方法(注意:全局只有一个CherryPyWSGIServer的实例server)。

WSGIGateway_10

上面的那句调用gateway中respond方法的代码逻辑是:实例化gateway——WSGIGateway_10,调用respond。在WSGIGateway_10的respond中,又通过request调用server上的wsgi_app方法:

1 response = self.req.server.wsgi_app(self.env, self.start_response)

通过这个生成response,最后写回到客户端(浏览器)。

到此为止就是上面图片中要介绍的所有流程了。

最后补充下那个被传递到最后的wsgi_app代码,我一直觉得一个类或者函数被传递这么多层最后来执行有点别扭,就像是在写JavaScript中时有很深的回调一样难以理解。

123456789101112131415161718192021222324252627282930313233 def wsgi(env, start_resp):    # clear threadlocal to avoid inteference of previous requests    self._cleanup()     self.load(env)    try:        # allow uppercase methods only        if web.ctx.method.upper() != web.ctx.method:            raise web.nomethod()         result = self.handle_with_processors()        if is_generator(result):            result = peep(result)        else:            result = [result]    except web.HTTPError, e:        result = [e.data]     result = web.safestr(iter(result))     status, headers = web.ctx.status, web.ctx.headers    start_resp(status, headers)     def cleanup():        self._cleanup()        yield \’\’ # force this function to be a generator    #import pdb;pdb.set_trace()    return itertools.chain(result, cleanup()) for m in middleware:    wsgi = m(wsgi) return wsgi

这么一个函数翻山涉水被传递到WSGI中实属不易,理解了这个函数的传递过程,和这个函数的作用,基本上也就理解了webpy了。


相关内容

热门资讯

500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
python查找阿姆斯特朗数 题目解释 如果一个n位正整数等于其各位数字的n次方之和,则称该数为阿姆斯特朗数。 例如1^3 + 5...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...