廖雪峰Python教程 实战day05

1. Web程序工作流程

本文部分文字内容、图片摘自《Flask Web开发实战:入门、进阶与原理解析》,作者李辉。

在编写自己的Web框架之前,首先要理解常用的Web框架到底实现了什么功能。以Flask框架为例对应理解

向浏览器中输入如下网址并按下Enter

http://helloflask.com/hello

客户端向服务器端发送请求然后接收服务器返回的响应。

客户端通常指Web浏览器(简称浏览器),服务器端(Server Side)则指为用户提供服务的服务器,也是我们的程序运行的地方。

图1-1 请求响应循环示意图

图1-2 Flask Web程序工作流程

当用户访问一个URL,浏览器便生成对应的HTTP请求,经由互联网发送到对应的Web服务器。Web服务器接收请求,**通过WSGI将HTTP格式的请求数据转换成我们的Flask程序能够使用的Python数据。**在程序中,**Flask根据请求的URL执行对应的视图函数,获取返回值生成响应。**响应依次经过WSGI转换生成HTTP响应,再经由Web服务器传递,最终被发出请求的客户端接收。浏览器渲染响应中包含的HTML和CSS代码,并执行Java Script代码,最终把解析后的页面呈现在用户浏览器的窗口中。

2. 路由匹配(Route)

首先明确 URL的组成。

图2-1 URL组成

这一步是建立不同的URL和视图函数的映射关系。根据GET,POST等HTTP方法类型决定返回给客户端的具体页面。当请求发来后,Flask会根据请求报文中的URL(path部分)来尝试与这个表中的所有URL规则进行匹配,调用匹配成功的视图函数。如果没有找到匹配的URL规则,说明程序中没有处理这个URL的视图函数。

这样廖雪峰教程中注册路由部分代码段就可以理解,获取对应的HTTP方法,path并和URL规则建立关系。以装饰器的形式添加了HTTP方法和path路径。

def get(path):#通过@get()就附带了URL信息。'''Define decorator @get('/path')'''def decorator(func):@functools.wraps(func)def wrapper(*args, **kw):return func(*args, **kw)wrapper.__method__ = 'GET' wrapper.__route__ = pathreturn wrapperreturn decorator
def add_route(app, fn):#单个注册method = getattr(fn, '__method__', None)path = getattr(fn, '__route__', None)if path is None or method is None:raise ValueError('@get or @post not defined in %s.' % str(fn))if not asyncio.iscoroutinefunction(fn) and not inspect.isgeneratorfunction(fn):fn = asyncio.coroutine(fn)logging.info('add route %s %s => %s(%s)' % (method, path, fn.__name__, ', '.join(inspect.signature(fn).parameters.keys())))app.router.add_route(method, path, RequestHandler(app, fn))
def add_routes(app, module_name):#批量注册路由n = module_name.rfind('.')#对应python文件存为handles,使用rfind找到handles.py文件if n == (-1): #如果没有匹配项,返回(-1)mod = __import__(module_name, globals(), locals())else:#找到了,返回字符串出现的最后一次位置。name = module_name[n+1:]mod = getattr(__import__(module_name[:n], globals(), locals(), [name]), name)for attr in dir(mod):if attr.startswith('_'):continuefn = getattr(mod, attr)if callable(fn):#如果找到了对应的处理方法。method = getattr(fn, '__method__', None)path = getattr(fn, '__route__', None)if method and path:add_route(app, fn)

为了以上代码方便理解,把分别对关键参数打印,代码如下所示。

n = 'handlers'.rfind('.')
if n == (-1):mod = __import__('handlers', globals(), locals())
else:name = 'handlers'[n+1:]mod = getattr(__import__('handlers'[:n], globals(), locals(), [name]), name)
print(dir(mod))
for attr in dir(mod):if attr.startswith('_'):continuefn = getattr(mod, attr)#print(fn)if callable(fn):method = getattr(fn, '__method__', None)path = getattr(fn, '__route__', None)#print(method,path)

结果如下,分别获取不同方法并切进行注册:

['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'asyncio', 'base64', 'hashlib', 'json', 'logging', 're', 'time']
['Blog', 'Comment', 'User', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'asyncio', 'base64', 'get', 'hashlib', 'index', 'json', 'logging', 'next_id', 'post', 're', 'time']

注意:在rfind的注释文档中,使用了 S.rfind(sub[, start[, end]]) ->int这种形式。借由知乎大佬的解释

3. 函数参数分析

该部分涉及提取正确的参数。

def get_named_kw_args(fn):#参数处理函数。这里重点要理解inspect模块用法。args = []params = inspect.signature(fn).parametersfor name, param in params.items():if param.kind == inspect.Parameter.KEYWORD_ONLY:args.append(name)return tuple(args)

举例表示inspect.signature(fn)的用途

def f(a,b,c=1,*var,city,game='lol',**kw):pass
a = inspect.signature(f)
t = a.parameters
print(t)
for name, param in t.items():print(name,param,param.kind,param.default)
OrderedDict([('a', <Parameter "a">), ('b', <Parameter "b">), ('c', <Parameter "c=1">), ('var', <Parameter "*var">), ('city', <Parameter "city">), ('game', <Parameter "game='lol'">), ('kw', <Parameter "**kw">)])
a a POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
b b POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
c c=1 POSITIONAL_OR_KEYWORD 1
var *var VAR_POSITIONAL <class 'inspect._empty'>
city city KEYWORD_ONLY <class 'inspect._empty'>
game game='lol' KEYWORD_ONLY lol
kw **kw VAR_KEYWORD <class 'inspect._empty'>

inspect.Parameter.KEYWORD_ONLY代表函数的参数类型。

查阅文档可知:

class _ParameterKind(enum.IntEnum):POSITIONAL_ONLY = 0POSITIONAL_OR_KEYWORD = 1VAR_POSITIONAL = 2KEYWORD_ONLY = 3VAR_KEYWORD = 4

由此可得,这一系列代码是用来获取不同类型的函数参数。

def get_named_kw_args(fn):#获取命名关键字参数
def has_named_kw_args(fn):#检查是否有命名关键字参数,有了返回True。
def has_var_kw_arg(fn):#检查是否有随机参数,有了返回True
def get_required_kw_args(fn):#检查 是否有命名关键字参数并且该参数没有默认值。
def has_request_arg(fn):#如果参数名为request并且 参数不是随机参数,不是命名关键字参数,不是关键字参数就报错。

在做完以上铺垫以后,就可以最后理解RequestHandler类了。

class RequestHandler(object):def __init__(self, app, fn):self._app = appself._func = fnself._has_request_arg = has_request_arg(fn)self._has_var_kw_arg = has_var_kw_arg(fn)self._has_named_kw_args = has_named_kw_args(fn)self._named_kw_args = get_named_kw_args(fn)self._required_kw_args = get_required_kw_args(fn)async def __call__(self, request):kw = Noneif self._has_var_kw_arg or self._has_named_kw_args or self._required_kw_args:if request.method == 'POST':if not request.content_type:return web.HTTPBadRequest('Missing Content-Type.')ct = request.content_type.lower()if ct.startswith('application/json'):params = await request.json()if not isinstance(params, dict):return web.HTTPBadRequest('JSON body must be object.')kw = paramselif ct.startswith('application/x-www-form-urlencoded') or ct.startswith('multipart/form-data'):params = await request.post()kw = dict(**params)else:return web.HTTPBadRequest('Unsupported Content-Type: %s' % request.content_type)if request.method == 'GET':qs = request.query_stringif qs:kw = dict()for k, v in parse.parse_qs(qs, True).items():kw[k] = v[0]if kw is None:kw = dict(**request.match_info)else:if not self._has_var_kw_arg and self._named_kw_args:# remove all unamed kw:copy = dict()for name in self._named_kw_args:if name in kw:copy[name] = kw[name]kw = copy# check named arg:for k, v in request.match_info.items():if k in kw:logging.warning('Duplicate arg name in named arg and kw args: %s' % k)kw[k] = vif self._has_request_arg:kw['request'] = request# check required kw:if self._required_kw_args:for name in self._required_kw_args:if not name in kw:return web.HTTPBadRequest('Missing argument: %s' % name)logging.info('call with args: %s' % str(kw))try:r = await self._func(**kw)return rexcept APIError as e:return dict(error=e.error, data=e.data, message=e.message)

4.中间件

middleware是一种拦截器,一个URL在被某个函数处理前,可以经过一系列的middleware的处理。

一个middleware可以改变URL的输入、输出,甚至可以决定不继续处理而直接返回。middleware的用处就在于把通用的功能从每个URL处理函数中拿出来,集中放到一个地方。例如,一个记录URL日志的logger可以简单定义如下:

async def logger_factory(app, handler):async def logger(request):logging.info('Request: %s %s' % (request.method, request.path))# await asyncio.sleep(0.3)return (await handler(request))return logger
async def response_factory(app, handler):async def response(request):logging.info('Response handler...')r = await handler(request)if isinstance(r, web.StreamResponse):return rif isinstance(r, bytes):resp = web.Response(body=r)resp.content_type = 'application/octet-stream'return respif isinstance(r, str):if r.startswith('redirect:'):return web.HTTPFound(r[9:])resp = web.Response(body=r.encode('utf-8'))resp.content_type = 'text/html;charset=utf-8'return respif isinstance(r, dict):template = r.get('__template__')if template is None:resp = web.Response(body=json.dumps(r, ensure_ascii=False, default=lambda o: o.__dict__).encode('utf-8'))resp.content_type = 'application/json;charset=utf-8'return respelse:resp = web.Response(body=app['__templating__'].get_template(template).render(**r).encode('utf-8'))resp.content_type = 'text/html;charset=utf-8'return respif isinstance(r, int) and r >= 100 and r < 600:return web.Response(r)if isinstance(r, tuple) and len(r) == 2:t, m = rif isinstance(t, int) and t >= 100 and t < 600:return web.Response(t, str(m))# default:resp = web.Response(body=str(r).encode('utf-8'))resp.content_type = 'text/plain;charset=utf-8'return respreturn response

response这个middleware把返回值转换为web.Response对象再返回,以保证满足aiohttp的要求。

廖雪峰Python教程 实战day05相关推荐

  1. 廖雪峰Python教程-笔记

    廖雪峰Python教程 学习范围: Python基础 函数 高级特性 函数性编程 模块 面向对象编程 错误,调试和测试 IO编程 笔记: Python的整数没有大小限制 Python 3的字符串使用U ...

  2. 廖雪峰python教程视频-为什么看不懂廖雪峰的Python学习教程?

    廖雪峰的Python教程已经很友好了,接近于把饭喂到嘴边了. 这不是廖雪峰教程的问题,而是一个基础代码技能和实际应用需求的代码技能差距太远导致的. 如果是新手,只学会了廖雪峰Python教程,那约等于 ...

  3. 廖雪峰python教程完整版-为什么看不懂廖雪峰的Python学习教程?

    廖雪峰的Python教程已经很友好了,接近于把饭喂到嘴边了. 这不是廖雪峰教程的问题,而是一个基础代码技能和实际应用需求的代码技能差距太远导致的. 如果是新手,只学会了廖雪峰Python教程,那约等于 ...

  4. 廖雪峰python教程在哪看_:廖雪峰python教程在哪

    标签,你可以自己查看网页源代码. 廖雪峰的python教程有一点地方没看明白,求指导 题主贴了函数,似乎是一样的,就分析这一个版本:def add_end(L=None): if L is None: ...

  5. Python 3 学习(一)—— 基础:廖雪峰 Python 教程学习笔记

    文章目录 Python教程 值类型和引用类型 列表和字典的基本操作 列表 元组 字典 Set 函数 内置函数 定义函数 空函数 参数检查 定义默认参数要牢记一点:默认参数必须指向不变对象! Pytho ...

  6. 廖雪峰python教程笔记:装饰器

    这是看廖老师python教程的第一个的笔记,因为这是这份教程最难的章节之一,我来来回回看了三遍,终于有所突破,写在这里是为了巩固自己的理解,同时也是希望有错的地方能够得到指正.具体内容见廖雪峰老师的课 ...

  7. 会python再学java要多久_【学过python多久能学会java】廖雪峰python教程要学多久

    自学完廖雪峰python可以找到相关工作吗? 如果只是学完廖雪峰的教程我觉得是不够的,你必须对一些方面有更加深入的实践和学习.我是工作中需要用到python,看了廖雪峰的教程,实现快速开发. 学过py ...

  8. python入门到精通需要学多久-廖雪峰python教程要学多久-零基础学Python需要多久...

    零基础学python大约需要多久 看不同的人,不同的学习能和基础. 像我通java,vc ,javascript,groovy,vb,c 接触过c#,delphi,asp,E语言, 用过dreamwa ...

  9. python零基础入门教程学习要多久-廖雪峰python教程要学多久-零基础学Python需要多久...

    零基础学python大约需要多久 看不同的人,不同的学习能和基础. 像我通java,vc ,javascript,groovy,vb,c 接触过c#,delphi,asp,E语言, 用过dreamwa ...

最新文章

  1. Python基础语法总结,Python初学者必备
  2. iptables相关管理命令
  3. Linux常用命令大全-toolfk程序员在线工具网
  4. html常用标签详解4-列表标签
  5. 计算机网络的定义分类性能指标,第1章 计算机网络基础
  6. 网络库urillib3
  7. LeetCode 2202. K 次操作后最大化顶端元素
  8. 【BZOJ1096】仓库建设,斜率优化DP练习
  9. 电商新春农历年春节海报还没设计?这是你需要的新年Banner灵感!
  10. python命令行模式和交互模式区别_对命令行模式与python交互模式介绍
  11. 人群与网络:关系的平衡
  12. Python使用xpath爬取51job
  13. 网吧管理软件常见漏洞四节课
  14. 体育专业国培计算机感言,信息技术国培感言
  15. linux rm 文件找回_linux rm -rf * 文件恢复记
  16. 红警职教智能硬件电子电路基础版教材与配套视频资源即将开发完毕
  17. ANSYS APDL
  18. python员工管理系统课程设计报告_python--员工信息管理系统编译及思路
  19. Java jcmd内存远大于top_Java堆外内存排查小结
  20. iar msp430 编译文件提示非法的license错误

热门文章

  1. elment表格sort-method自定义排序功能
  2. 单招计算机Windows7知识点,计算机单招考试试题
  3. 思杰虚拟服务器退出管理主机,思杰服务器虚拟化解决详尽方案介绍2012.ppt
  4. 【基本功】深入剖析Swift性能优化
  5. 第十二届蓝桥杯D题 货物摆放
  6. tcpdump抓包使用小结
  7. 三、计算机网络的性能指标
  8. android ksoap用法
  9. 韩国渠道接入三星支付(Android 接入 Samsung in app purchase)
  10. JAVA基础 - 数组中有没有length()这个方法?String中有没有 length()这个方法?