这一阵正在学习廖雪峰老师的实战课程,这里对其中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将函数与模板联系起来;
 def view(path):

     def _decorator(func):
@functools.wraps(func)
def _wrapper(*args, **kw):
r = func(*args, **kw) #这时函数原本应该返回的值,应当为字典类型。
if isinstance(r, dict):
logging.info('return Template')
return Template(path, **r) #得到self.template_name 和 self.model。
raise ValueError('Expect return a dict when using @view() decorator.')
return _wrapper
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()。
 class Route(object):
'''
A Route object is a callable object.
'''
def __init__(self, func):
self.path = func.__web_route__
self.method = func.__web_method__
self.is_static = _re_route.search(self.path) is None
if not self.is_static:
self.route = re.compile(_build_regex(self.path))
self.func = func
def match(self, url):
m = self.route.match(url)
if m:
return m.groups()
return None
def __call__(self, *args):
return self.func(*args)
def __str__(self):
if self.is_static:
return 'Route(static,%s,path=%s)' % (self.method, self.path)
return 'Route(dynamic,%s,path=%s)' % (self.method, self.path)
__repr__ = __str__
 
2. Request(object)
这一部分主要是解析客户端发送的请求,我们只将其主要的部分进行分析,其他的方法都是类似的。
最主要的就是 _parse_input()方法,它主要是将请求中表单的部分转换成为字典类型,以供后面的函数调用;
_get_raw_input()和input()都是在这个基础上的装饰复制,从而方便下面的使用;
后面很多用 @property装饰的函数都是通过self._environ来获得想要的信息;
还有一些设置cookie和header的函数。
 class Request(object):
'''
Request object for obtaining all http request information.
'''
def __init__(self, environ):
self._environ = environ
def _parse_input(self):
def _convert(item):
if isinstance(item, list):
return [_to_unicode(i.value) for i in item]
if item.filename:
return MultipartFile(item)
return _to_unicode(item.value)
#将self._environ['wsgi.input']转换成字典类型
fs = cgi.FieldStorage(fp=self._environ['wsgi.input'], environ=self._environ, keep_blank_values=True)
inputs = dict()
for key in fs:
inputs[key] = _convert(fs[key])
return inputs
def _get_raw_input(self):
'''
Get raw input as dict containing values as unicode, list or MultipartFile.
'''
if not hasattr(self, '_raw_input'):
#将上面的结果放到_raw_input属性中
self._raw_input = self._parse_input()
return self._raw_input
def input(self, **kw):
#复制上边得到的表单字典
copy = Dict(**kw)
raw = self._get_raw_input()
for k, v in raw.iteritems():
copy[k] = v[0] if isinstance(v, list) else v
return copy
def get_body(self):
#得到environ['wsgi.input']原始的数据
fp = self._environ['wsgi.input']
return fp.read()
@property
def remote_addr(self):
return self._environ.get('REMOTE_ADDR', '0.0.0.0')
def _get_cookies(self):
if not hasattr(self, '_cookies'):
cookies = {}
cookie_str = self._environ.get('HTTP_COOKIE')
if cookie_str:
for c in cookie_str.split(';'):
pos = c.find('=')
if pos>0:
cookies[c[:pos].strip()] = _unquote(c[pos+1:])
self._cookies = cookies
return self._cookies
@property
def cookies(self): return Dict(**self._get_cookies())
def cookie(self, name, default=None): 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的实现,这个类将上面三部分有机结合到一起,从而完成整个服务器的过程(可以看注释):
 
 class WSGIApplication(object):
#初始化时创建后面要用到的属性
def __init__(self, document_root=None, **kw):
'''
Init a WSGIApplication.
Args:
document_root: document root path.
''' self._running = False
self._document_root = document_root
self._interceptors = []
self._template_engine = None
self._get_static = {}
self._post_static = {}
self._get_dynamic = []
self._post_dynamic = [] #用来查看服务器是否正在运行
def _check_not_running(self):
if self._running:
raise RuntimeError('Cannot modify WSGIApplication when running.')
#添加模板
@property
def template_engine(self):
return self._template_engine
@template_engine.setter
def template_engine(self, engine):
self._check_not_running()
self._template_engine = engine #add_module()和add_url()用来将urls.py中的函数注册到服务器中
def add_module(self, mod):
self._check_not_running()
m = mod if type(mod)==types.ModuleType else _load_module(mod)
logging.info('Add module: %s' % m.__name__)
for name in dir(m):
fn = getattr(m, name)
if callable(fn) and hasattr(fn, '__web_route__') and hasattr(fn, '__web_method__'):
self.add_url(fn)
def add_url(self, func):
self._check_not_running()
route = Route(func)
if route.is_static:
if route.method=='GET':
self._get_static[route.path] = route
if route.method=='POST':
self._post_static[route.path] = route
else:
if route.method=='GET':
self._get_dynamic.append(route)
if route.method=='POST':
self._post_dynamic.append(route)
logging.info('Add route: %s' % str(route)) #添加拦截函数
def add_interceptor(self, func):
self._check_not_running()
self._interceptors.append(func)
logging.info('Add interceptor: %s' % str(func)) #运行服务器
def run(self, port=9000, host='127.0.0.1'):
from wsgiref.simple_server import make_server
logging.info('application (%s) will start at %s:%s...' % (self._document_root, host, port))
#httpd = make_server('', 8000, hello_world_app) 其中self.get_wsgi_application(debug=True)便是代替hello_world_app,
#这个是一个函数对象wsgi, 可以被调用
server = make_server(host, port, self.get_wsgi_application(debug=True))
server.serve_forever()
#这时这个应用中的核心
#返回值wsgi是一个函数对象,而不是一个确定值,主要是为了上面的调用
def get_wsgi_application(self, debug=False):
self._check_not_running()
if debug:
self._get_dynamic.append(StaticFileRoute())
self._running = True
_application = Dict(document_root=self._document_root) #这个函数的作用就是将注册的函数和请求的路径联系起来
def fn_route():
request_method = ctx.request.request_method
path_info = ctx.request.path_info
if request_method=='GET':
fn = self._get_static.get(path_info, None)
if fn:
#静态路径的话就可以直接调用函数:
return fn()
for fn in self._get_dynamic:
#如果是动态的路径,那么将其中的动态部分提取出来作为函数的参数:
args = fn.match(path_info)
if args:
return fn(*args)
raise notfound()
if request_method=='POST':
fn = self._post_static.get(path_info, None)
if fn:
return fn()
for fn in self._post_dynamic:
args = fn.match(path_info)
if args:
return fn(*args)
raise notfound()
raise badrequest() #添加拦截函数
fn_exec = _build_interceptor_chain(fn_route, *self._interceptors) #wsgi就是应用程序了,其中的两个参数在wsgiref中会提供的:
def wsgi(env, start_response): #将Request和Response实例化成为ctx的属性
ctx.application = _application
ctx.request = Request(env)
response = ctx.response = Response() try: r = fn_exec()
#正常情况下r是被包裹的函数返回的填入返回值的模板
if isinstance(r, Template):
r = self._template_engine(r.template_name, r.model)
if isinstance(r, unicode):
r = r.encode('utf-8')
if r is None:
r = []
start_response(response.status, response.headers)
return r
#处理各种错误
except RedirectError, e:
response.set_header('Location', e.location)
start_response(e.status, response.headers)
return []
except HttpError, e:
start_response(e.status, response.headers)
return ['<html><body><h1>', e.status, '</h1></body></html>']
except Exception, e:
logging.exception(e)
if not debug:
start_response('500 Internal Server Error', [])
return ['<html><body><h1>500 Internal Server Error</h1></body></html>']
exc_type, exc_value, exc_traceback = sys.exc_info()
fp = StringIO()
traceback.print_exception(exc_type, exc_value, exc_traceback, file=fp)
stacks = fp.getvalue()
fp.close()
start_response('500 Internal Server Error', [])
return [
r'''<html><body><h1>500 Internal Server Error</h1><div style="font-family:Monaco, Menlo, Consolas, 'Courier New', monospace;"><pre>''',
stacks.replace('<', '&lt;').replace('>', '&gt;'),
'</pre></div></body></html>']
#请求结束后将线程的各个属性删除
finally:
del ctx.application
del ctx.request
del ctx.response
return wsgi
这就是整个web.py框架的结构了,其中具体的细节就需要参看代码了。
 
 
 
 
 
 
 
 
 
 
 
 

Python_web框架解析的更多相关文章

  1. [转载]iOS 10 UserNotifications 框架解析

    活久见的重构 - iOS 10 UserNotifications 框架解析 TL;DR iOS 10 中以前杂乱的和通知相关的 API 都被统一了,现在开发者可以使用独立的 UserNotifica ...

  2. ABP使用及框架解析系列 - [Unit of Work part.1-概念及使用]

    前言 ABP ABP是“ASP.NET Boilerplate Project”的简称. ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开 ...

  3. ABP使用及框架解析系列 - [Unit of Work part.2-框架实现]

    前言 ABP ABP是“ASP.NET Boilerplate Project”的简称. ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开 ...

  4. iOS 10 UserNotifications 框架解析

    摘自:https://onevcat.com/2016/08/notification/ iOS 10 中以前杂乱的和通知相关的 API 都被统一了,现在开发者可以使用独立的 UserNotifica ...

  5. Poco::TCPServer框架解析

    Poco::TCPServer框架解析 POCO C++ Libraries提供一套 C++ 的类库用以开发基于网络的可移植的应用程序,功能涉及线程.文件.流,网络协议包括:HTTP.FTP.SMTP ...

  6. Scrapy爬虫框架解析

    Scrapy框架解析 Scrapy框架大致包括以下几个组件:Scrapy Engine.Spiders.Scheduler.Item Pipeline.Downloader: 组件 Scrapy En ...

  7. 使用 STHTTPRequest 框架解析 Soap1.2 教程

    1.STHTTPRequest框架地址 https://github.com/nst/STHTTPRequest 将 STHTTPRequest .h  STHTTPRequest.m 文件拖入工程中 ...

  8. Sword框架解析——知识采集流程页面初始化

    Sword框架解析——知识采集流程页面初始化 Sword框架解析知识采集流程页面初始化 问题解答流程采集新增页面初始化 1后台t_xt_gnzy表和BLH类 2BLH类的写法前台目录树代码 3登录系统 ...

  9. iScroll框架解析——Android 设备页面内 div(容器,非页面)overflow:scroll; 失效解决(转)

    移动平台的活,兼容问题超多,今儿又遇到一个.客户要求在弹出层容器内显示内容,但内容条数过多,容器显示滚动条.按说是So easy,容器设死宽.高,CSS加属性 overflow:scroll; -we ...

随机推荐

  1. 安装Windows2012操作系统 - 初学者系列 - 学习者系列文章

    Windows 2012是微软最新的服务器操作系统,估计在国外服务器空间的运营商安装的比较多些吧.下面简要介绍下该操作系统的安装. 1.  将光盘放入光驱.进入BIOS设置成光驱启动.重启计算机. 2 ...

  2. 使用Windows2003的IIS发布网站 - 进阶者系列 - 学习者系列文章

    本系列文章目录:http://www.cnblogs.com/lzhdim/category/277150.html Windows 2003是一款不错的Windows服务器版本.在刚工作的前些年里, ...

  3. HDOJ 5063 Operation the Sequence

    注意到查询次数不超过50次,那么能够从查询位置逆回去操作,就能够发现它在最初序列的位置,再逆回去就可以求得当前查询的值,对于一组数据复杂度约为O(50*n). Operation the Sequen ...

  4. SSIS中Sql Task 获取系统变量

    原文:SSIS中Sql Task 获取系统变量 执行 SQL 任务使用不同的连接类型时,SQL 命令的语法使用不同的参数标记.例如,ADO.NET 连接管理器类型要求 SQL 命令使用格式为 @var ...

  5. memcpy源代码

    7月22号去面试开发的职位,面试官先问我在以前项目中写了什么程序.我就巴拉巴拉的说了一堆写过的code,主要还是测试工具和自动化测试代码.之后让我写memcpy的函数,面试官还挺好的,帮我把函数原型都 ...

  6. LINQ TO SQL ——Group by

    原文:LINQ TO SQL --Group by 分组在SQL中应用的十分普遍,在查询,统计时都有可能会用到它.LINQ TO SQL中同样具备group的功能,这篇我来讲下LINQ TO SQL中 ...

  7. Xamarin.Android

    Xamarin.Android之使用百度地图起始篇 一.前言 如今跨平台开发层出不穷,而对于.NET而言时下最流行的当然还是Xamarin,不仅仅能够让我们在熟悉的Vs下利用C#开发,在对原生态类库的 ...

  8. 存储过程的参数问题与C#中的调用

    1. 带参数的存储过程 set ANSI_NULLS ON set QUOTED_IDENTIFIER ON GO ALTER PROCEDURE [dbo].[sp_select_gua] @num ...

  9. avalon1.0正式发布

    2013年最后的收成:avalon1.0正式发布 大半年前我就说过,MVVM是前端究极的解决方案,因此之后我大多数时间都在折腾avalon,成立了专门的QQ群与感兴趣的一起讨论.感谢第一批吃螃蟹的人, ...

  10. Django数据库迁移

    如果你用过Django的数据库就会发现一个比较令人纠结的地方:数据库更改. 我们知道添加或者删除一个models.Model 需要在数据库里相应的操作,这需要我们进入数据库命令行手动添加或删除,因为s ...