Python_web框架解析

这一阵正在学习廖雪峰老师的实战课程,这里对其中web.py框架进行一些分析,总结一下学到的东西。

这一部分的课程网站:http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0014023080708565bc89d6ab886481fb25a16cdc3b773f0000

近期学习了廖雪峰老师Python教程实战课程中WebApp博客的编写,这个项目最核心的部分就是web框架的编写。在这里总结一下学到的东西。

其实简化来说这就是一个WSGI的实现填充,所以需要对wsgi有一定的了解。 
首先要对整体的流程和结构有一个掌握:
1.用Request接受并分析客户端发出请求;
2.通过Route将请求的地址和要返回的模板关联起来;
3.再通过Response将结果返回客户端。
 
流程图
 
以上就是整个框架中主要的三块,这三部分通过wsgi协议来组装,从而构成一个完整的框架。
在对这三个部分进行分析前,还有一些启动程序前要做的准备工作:
a.注册函数
 1.先用@get, @post来装饰函数,从而设定func.__web_method__和func.__web_route__;
def get(path):
    
    def _decorator(func):
        func.__web_route__ = path
        func.__web_method__ = 'GET'
        return func
    return _decorator

def post(path):
   
    def _decorator(func):
        func.__web_route__ = path
        func.__web_method__ = 'POST'
        return func
    return _decorator
 
 2.再用@view将函数与模板联系起来;
 1 def view(path):
 2    
 3     def _decorator(func):
 4         @functools.wraps(func)
 5         def _wrapper(*args, **kw):
 6             r = func(*args, **kw)         #这时函数原本应该返回的值,应当为字典类型。
 7             if isinstance(r, dict):
 8                 logging.info('return Template')
 9                 return Template(path, **r)       #得到self.template_name 和 self.model。
10             raise ValueError('Expect return a dict when using @view() decorator.')
11         return _wrapper
12     return _decorator

 

 3.最后用wsgi.add_module(urls)来将这些函数注册到WSGIApplication中(这个函数存在于WSGIApplication中)。
 
b.添加拦截器
wsgi.add_interceptor(urls.user_interceptor)
 1.用@interceptor()装饰拦截函数;
def interceptor(pattern='/'):
   
    def _decorator(func):
        #简单来说就是:给func添加了一个__interceptor__属性,这一属性并非一个单纯的值,
        #  而是一个可调用的函数,这个函数使用时:
        #          func.__interceptor__(ctx.request.path_info)
        #  根据ctx.request.path_info判断返回True或者False,从而决定拦截函数是否运行-->看2.
        func.__interceptor__ = _build_pattern_fn(pattern)
        return func
    return _decorator

 

 2.根据ctx.request.path_info判断返回True或者False,从而决定拦截函数是否运行;
def _build_interceptor_fn(func, next):
    def _wrapper():
        if func.__interceptor__(ctx.request.path_info):
            #如果上面为True,那么启动拦截函数
            return func(next)
        else:
            #否则直接运行原函数
            return next()
    return _wrapper

 

 
下面看主要的三个部分:
1. class Route(object):
    这个模块的目的是将函数包裹Route()中,从而存储func对应的请求路径和请求方法,这个函数是在add_url()中调用的:route = Route(func)。
    这个类的作用主要是:
        a.将函数路径和对应的方法放到实例中;
        b.并给实例添加属性来判断对应的请求路径是静态还是动态(就是在函数用@get, @post来装饰时的path中是否含有变量如:'/:id'等
        c.添加match方法,当路径是动态时,用来将其中的变量提出,作为函数的参数使用:具体看WSGIApplication中get_wsgi_application下的函数fn_route()。
 1 class Route(object):
 2     '''
 3     A Route object is a callable object.
 4     '''
 5     def __init__(self, func):
 6         self.path = func.__web_route__
 7         self.method = func.__web_method__
 8         self.is_static = _re_route.search(self.path) is None
 9         if not self.is_static:
10             self.route = re.compile(_build_regex(self.path))
11         self.func = func
12     def match(self, url):
13         m = self.route.match(url)
14         if m:
15             return m.groups()
16         return None
17     def __call__(self, *args):
18         return self.func(*args)
19     def __str__(self):
20         if self.is_static:
21             return 'Route(static,%s,path=%s)' % (self.method, self.path)
22         return 'Route(dynamic,%s,path=%s)' % (self.method, self.path)
23     __repr__ = __str__
 
2. Request(object)
这一部分主要是解析客户端发送的请求,我们只将其主要的部分进行分析,其他的方法都是类似的。
最主要的就是 _parse_input()方法,它主要是将请求中表单的部分转换成为字典类型,以供后面的函数调用;
_get_raw_input()和input()都是在这个基础上的装饰复制,从而方便下面的使用;
后面很多用 @property装饰的函数都是通过self._environ来获得想要的信息;
还有一些设置cookie和header的函数。
 1 class Request(object):
 2     '''
 3     Request object for obtaining all http request information.
 4     '''
 5     def __init__(self, environ):
 6         self._environ = environ
 7     def _parse_input(self):
 8         def _convert(item):
 9             if isinstance(item, list):
10                 return [_to_unicode(i.value) for i in item]
11             if item.filename:
12                 return MultipartFile(item)
13             return _to_unicode(item.value)
14         #将self._environ['wsgi.input']转换成字典类型
15         fs = cgi.FieldStorage(fp=self._environ['wsgi.input'], environ=self._environ, keep_blank_values=True)
16         inputs = dict()
17         for key in fs:
18             inputs[key] = _convert(fs[key])
19         return inputs
20     def _get_raw_input(self):
21         '''
22         Get raw input as dict containing values as unicode, list or MultipartFile.
23         '''
24         if not hasattr(self, '_raw_input'):
25             #将上面的结果放到_raw_input属性中
26             self._raw_input = self._parse_input()
27         return self._raw_input
28     def input(self, **kw):
29         #复制上边得到的表单字典
30         copy = Dict(**kw)
31         raw = self._get_raw_input()
32         for k, v in raw.iteritems():
33             copy[k] = v[0] if isinstance(v, list) else v
34         return copy
35     def get_body(self):
36         #得到environ['wsgi.input']原始的数据        
37         fp = self._environ['wsgi.input']
38         return fp.read()
39     @property
40     def remote_addr(self):   
41         return self._environ.get('REMOTE_ADDR', '0.0.0.0')
42     def _get_cookies(self):
43         if not hasattr(self, '_cookies'):
44             cookies = {}
45             cookie_str = self._environ.get('HTTP_COOKIE')
46             if cookie_str:
47                 for c in cookie_str.split(';'):
48                     pos = c.find('=')
49                     if pos>0:
50                         cookies[c[:pos].strip()] = _unquote(c[pos+1:])
51             self._cookies = cookies
52         return self._cookies
53     @property
54     def cookies(self):
55         
56         return Dict(**self._get_cookies())
57     def cookie(self, name, default=None):
58         
59         return self._get_cookies().get(name, default)

 

 
3.Response(object)
这部分主要的作用就是设置status 和 headers ,后边各种方法都是围绕这一主题进行的。具体可以看源码的注释。
class Response(object):
    def __init__(self):
        self._status = '200 OK'
        self._headers = {'CONTENT-TYPE': 'text/html; charset=utf-8'}  
 
接下来就是这个模块最关键的部分了,WSGI的实现,这个类将上面三部分有机结合到一起,从而完成整个服务器的过程(可以看注释):
 
  1 class WSGIApplication(object):
  2     #初始化时创建后面要用到的属性
  3     def __init__(self, document_root=None, **kw):
  4         '''
  5         Init a WSGIApplication.
  6         Args:
  7           document_root: document root path.
  8         '''
  9 
 10         self._running = False
 11         self._document_root = document_root
 12         self._interceptors = []
 13         self._template_engine = None
 14         self._get_static = {}
 15         self._post_static = {}
 16         self._get_dynamic = []
 17         self._post_dynamic = []
 18 
 19     #用来查看服务器是否正在运行
 20     def _check_not_running(self):
 21         if self._running:
 22             raise RuntimeError('Cannot modify WSGIApplication when running.')
 23     #添加模板
 24     @property
 25     def template_engine(self):
 26         return self._template_engine
 27     @template_engine.setter
 28     def template_engine(self, engine):
 29         self._check_not_running()
 30         self._template_engine = engine
 31 
 32     #add_module()和add_url()用来将urls.py中的函数注册到服务器中
 33     def add_module(self, mod):
 34         self._check_not_running()
 35         m = mod if type(mod)==types.ModuleType else _load_module(mod)
 36         logging.info('Add module: %s' % m.__name__)
 37         for name in dir(m):
 38             fn = getattr(m, name)
 39             if callable(fn) and hasattr(fn, '__web_route__') and hasattr(fn, '__web_method__'):
 40                 self.add_url(fn)
 41     def add_url(self, func):
 42         self._check_not_running()
 43         route = Route(func)
 44         if route.is_static:
 45             if route.method=='GET':
 46                 self._get_static[route.path] = route
 47             if route.method=='POST':
 48                 self._post_static[route.path] = route
 49         else:
 50             if route.method=='GET':
 51                 self._get_dynamic.append(route)
 52             if route.method=='POST':
 53                 self._post_dynamic.append(route)
 54         logging.info('Add route: %s' % str(route))
 55   
 56     #添加拦截函数
 57     def add_interceptor(self, func):
 58         self._check_not_running()
 59         self._interceptors.append(func)
 60         logging.info('Add interceptor: %s' % str(func))
 61 
 62     #运行服务器
 63     def run(self, port=9000, host='127.0.0.1'):
 64         from wsgiref.simple_server import make_server
 65         logging.info('application (%s) will start at %s:%s...' % (self._document_root, host, port))
 66         #httpd = make_server('', 8000, hello_world_app) 其中self.get_wsgi_application(debug=True)便是代替hello_world_app,
 67         #这个是一个函数对象wsgi, 可以被调用
 68         server = make_server(host, port, self.get_wsgi_application(debug=True))
 69         server.serve_forever()
70 #这时这个应用中的核心 71 #返回值wsgi是一个函数对象,而不是一个确定值,主要是为了上面的调用 72 def get_wsgi_application(self, debug=False): 73 self._check_not_running() 74 if debug: 75 self._get_dynamic.append(StaticFileRoute()) 76 self._running = True 77 _application = Dict(document_root=self._document_root) 78 79 #这个函数的作用就是将注册的函数和请求的路径联系起来 80 def fn_route(): 81 request_method = ctx.request.request_method 82 path_info = ctx.request.path_info 83 if request_method=='GET': 84 fn = self._get_static.get(path_info, None) 85 if fn: 86 #静态路径的话就可以直接调用函数: 87 return fn() 88 for fn in self._get_dynamic: 89 #如果是动态的路径,那么将其中的动态部分提取出来作为函数的参数: 90 args = fn.match(path_info) 91 if args: 92 return fn(*args) 93 raise notfound() 94 if request_method=='POST': 95 fn = self._post_static.get(path_info, None) 96 if fn: 97 return fn() 98 for fn in self._post_dynamic: 99 args = fn.match(path_info) 100 if args: 101 return fn(*args) 102 raise notfound() 103 raise badrequest() 104 105 #添加拦截函数 106 fn_exec = _build_interceptor_chain(fn_route, *self._interceptors) 107 108 #wsgi就是应用程序了,其中的两个参数在wsgiref中会提供的: 109 def wsgi(env, start_response): 110 111 #将Request和Response实例化成为ctx的属性 112 ctx.application = _application 113 ctx.request = Request(env) 114 response = ctx.response = Response() 115 116 try: 117 118 r = fn_exec() 119 #正常情况下r是被包裹的函数返回的填入返回值的模板 120 if isinstance(r, Template): 121 r = self._template_engine(r.template_name, r.model) 122 if isinstance(r, unicode): 123 r = r.encode('utf-8') 124 if r is None: 125 r = [] 126 start_response(response.status, response.headers) 127 return r 128 #处理各种错误 129 except RedirectError, e: 130 response.set_header('Location', e.location) 131 start_response(e.status, response.headers) 132 return [] 133 except HttpError, e: 134 start_response(e.status, response.headers) 135 return ['<html><body><h1>', e.status, '</h1></body></html>'] 136 except Exception, e: 137 logging.exception(e) 138 if not debug: 139 start_response('500 Internal Server Error', []) 140 return ['<html><body><h1>500 Internal Server Error</h1></body></html>'] 141 exc_type, exc_value, exc_traceback = sys.exc_info() 142 fp = StringIO() 143 traceback.print_exception(exc_type, exc_value, exc_traceback, file=fp) 144 stacks = fp.getvalue() 145 fp.close() 146 start_response('500 Internal Server Error', []) 147 return [ 148 r'''<html><body><h1>500 Internal Server Error</h1><div style="font-family:Monaco, Menlo, Consolas, 'Courier New', monospace;"><pre>''', 149 stacks.replace('<', '&lt;').replace('>', '&gt;'), 150 '</pre></div></body></html>'] 151 #请求结束后将线程的各个属性删除 152 finally: 153 del ctx.application 154 del ctx.request 155 del ctx.response 156 return wsgi
这就是整个web.py框架的结构了,其中具体的细节就需要参看代码了。
 
 
 
 
 
 
 
 
 
 
 
 

 

posted @ 2015-09-04 13:09  arvinls  阅读(372)  评论(0编辑  收藏  举报