廖老师python教程实战Day5-编写web框架理解
来源:互联网 发布:行知小学 编辑:程序博客网 时间:2024/05/16 16:55
对于每个传进来的url,都要设置一个线程来处理url
线程是如何来处理url的?
async def init(loop): await orm.create_pool(loop=loop, host='127.0.0.1', port=3306, user='www', password='www', db='awesome') app = web.Application(loop=loop, middlewares=[ logger_factory, response_factory ]) init_jinja2(app, filters=dict(datetime=datetime_filter)) add_routes(app, 'handlers') add_static(app) srv = await loop.create_server(app.make_handler(), '127.0.0.1', 9000) logging.info('server started at http://127.0.0.1:9000...') return srv
首先先连接数据库
await orm.create_pool(loop=loop, host='127.0.0.1', port=3306, user='www', password='www', db='awesome')
连接数据库之后会根据aiohttp来构造一个app类
app = web.Application(loop=loop, middlewares=[ logger_factory, response_factory ])
把app类与线程绑定并为app类添加middlewares
注册模板
init_jinja2(app, filters=dict(datetime=datetime_filter))
注册url处理函数
add_routes(app, 'handlers')
添加静态文件
add_static(app)
这样,app类就拥有了我们想要的功能接下来就是把app类返回用来处理传进来的url
srv = await loop.create_server(app.make_handler(), '127.0.0.1', 9000) logging.info('server started at http://127.0.0.1:9000...') return srv
一个很重要的问题
url处理函数是如何注册的?
看一下coroweb.py中的add_routes函数
def add_routes(app, module_name): n = module_name.rfind('.') if n == (-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('_'): continue fn = getattr(mod, attr) if callable(fn): method = getattr(fn, '__method__', None) path = getattr(fn, '__route__', None) if method and path: add_route(app, fn)
module_name = handles
handles代表着什么?
代表着handles.py
module_name.rfind(‘.’)是什么?
先来看一下handles.py中的代码,参考day-07中的handles.py文件中的代码
import re, time, json, logging, hashlib, base64, asynciofrom coroweb import get, postfrom models import User, Comment, Blog, next_id@get('/')async def index(request): users = await User.findAll() return { '__template__': 'test.html', 'users': users }
module_name.rfind(‘.’)就代表着handlers.py文件中所有的url处理函数的数目减1
为什么减1?
因为是从下标0开始
如果n==-1则代表没有找到handles.py文件中的url处理函数,则需要把handles.py当成一个模块导入
mod = __import__(module_name, globals(), locals())
等价于import handlers
如果找到的话,就要构造for循环了,因为url处理函数不会只有一个(在本例中只有一个,因为功能还不完善),那么,如何构造for循环?
name = module_name[n+1:]mod = getattr(__import__(module_name[:n], globals(), locals(), [name]), name)
就本例来讲handlers.py文件中就只有一个url处理函数,因此n=0
n+1 = 1
在看name是什么之前先来看一个例子
>>>L = ['0', '1', '2', '3']>>>L[3:]['3']
因此name这个变量保存的就是handlers.py文件中最后一个url处理函数
mod = getattr(__import__(module_name[:n], globals(), locals(), [name]), name)
_import_是用来干什么的,以及参数的含义?
__import__实际上就相当于import …
name变量是用来指定需要导入的模块
gloabls(), locals()是用来决定在当前包的上下文环境中如何解释name即模块
fromlist给出了应该从当前对象或者子模版中应该导入的对象
level如果为0意味着只根据模块的绝对路径导入,如果是整数将会计算与当前目录的模块所关联的父模块的数量
mod = getattr(__import__(module_name[:n], globals(), locals(), [name]), name)
其实就是把所有的url处理函数存到mod中
fn = getattr(mod, attr)
获取url处理函数的函数对象
为什么要获取url处理函数的函数对象?
因为目前为止,url处理函数并没有注册
add_routes不是用来注册url处理函数的吗?为什么到现在还没有注册?
看一下aiohttp官方文档是怎么注册一个url处理函数的
from aiohttp import webasync def handle(request): name = request.match_info.get('name', "Anonymous") text = "Hello, " + name return web.Response(text=text)app = web.Application()app.router.add_get('/', handle)app.router.add_get('/{name}', handle)web.run_app(app)
很明显add_routes并没有出现app.route.add_get()
其实add_routes只是用来批量注册的,避免写成这样:
add_route(app, handles.index)add_route(app, handles.blog)add_route(app, handles.create_comment)
接下来就是要处理url处理函数了,在add_route函数中将其注册
if callable(fn): method = getattr(fn, '__method__', None) path = getattr(fn, '__route__', None) if method and path: add_route(app, fn)
好了,将目标转换到add_route函数吧
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))
method,path从何而来?
通过装饰器,在url处理函数中加上属性__method和__route__,注意函数也是一个对象
装饰器是如何起作用的?
def get(path): ''' Define decorator @get('/path') ''' def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): return func(*args, **kw) wrapper.__method__ = 'GET' wrapper.__route__ = path return wrapper return decorator@get('/')async def index(request): users = await User.findAll() return { '__template__': 'test.html', 'users': users }
相当于get(index),参数什么的直接返回不用修改,再加上了两个属性__method__和__route__
这样,获取方法和路径就可以通过获取url处理函数的属性__method和__route__就行了
接下来就要进行注册url处理函数了
app.router.add_route(method, path, RequestHandler(app, fn))
RequestHandle类中有__callable__方法,因此RequestHandler(app, fn)相当于创建了一个url处理函数,函数名就是fn
为什么要通过RequestHandler类来创建url处理函数,前面不是已经有url处理函数了吗?为什么不直接拿来用?
答案就是我们要通过HTTP协议来判断在GET或者POST方法中是否丢失了参数,如果判断方法编写在url处理函数中会有很多重复代码,因此用类来封装一下
来看一个HTTP协议吧
当我们访问一个url时,HTTP都做了些什么
访问廖老师的python教程
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000
HTTP会自动构造一个请求:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8Accept-Encoding:gzip, deflate, brAccept-Language:zh-CN,zh;q=0.9Cache-Control:max-age=0Connection:keep-aliveCookie:atsp=1513410396128_1513410396776; Hm_lvt_2efddd14a5f2b304677462d06fb4f964=1513308035,1513336392,1513396663,1513410397; Hm_lpvt_2efddd14a5f2b304677462d06fb4f964=1513410466Host:www.liaoxuefeng.comReferer:https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000Upgrade-Insecure-Requests:1User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36
如果链接存在,HTTP会自动构造一个响应:
Connection:keep-aliveContent-Encoding:gzipContent-Security-Policy:upgrade-insecure-requestsContent-Type:text/html; charset=utf-8Date:Sat, 16 Dec 2017 07:49:37 GMTServer:nginx/1.10.3 (Ubuntu)Transfer-Encoding:chunkedX-Host:liaoxuefeng-2X-Response-Time:24ms
看一下RequestHandler类吧
async def __call__(self, request):
注意request参数
当我们处理url时是通过url处理函数来处理的
再观察一下url处理函数index
@get('/')async def index(request): users = await User.findAll() return { '__template__': 'test.html', 'users': users }
经过装饰器的修饰后,index函数包含了请求方法GET/POST,请求路径path,以及HTTP请求头request,如何获取请求头呢?
request参数实际上是一个继承于class aiohttp.web.BaseRequest的实例,而aiohttp.web.BaseRequest类包含了一个请求所携带的所有HTTP信息
如果我们想要获得请求头中的cookie就可以这样写:
request.cookie
_callable_中的参数是如何获取的?
答案是通过先前定义的函数
def __init__(self, app, fn): self._app = app self._func = fn self._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)
这几个函数是用来干什么的?
答案是获取url中的参数
怎么获取的?
先来构造一个url
http://127.0.0.1/api/comments
这个url对应的url处理函数为
@get('/api/comments')def api_comments(*, page='1'): return ...
再来看看这几个函数的输出
has_request_arg --> (*, page='1')has_var_kw_arg --> OrderedDict([('page', <Parameter "page='1'">)])has_named_kw_args --> OrderedDict([('page', <Parameter "page='1'">)])get_named_kw_args --> OrderedDict([('page', <Parameter "page='1'">)])get_required_kw_args --> OrderedDict([('page', <Parameter "page='1'">)])
输出都是一样的,但是为什么要定义四个函数而不是定义一个函数?
为了程序的健壮性
if self._has_var_kw_arg or self._has_named_kw_args or self._required_kw_args:
如果三次都为None,那就说明参数实在是自己跑了
if 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 = params elif 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_string if qs: kw = dict() for k, v in parse.parse_qs(qs, True).items(): kw[k] = v[0]
为什么处理POST方法和GET方法的程序不一样?
因为POST需要处理用户输入的数据比如说创建用户时用户所输入的数据
在这里借评论区一位大佬的网站一用
http://www.face-python.wang/
进入注册页面
用户名输入lalala
email输入lalala@example.com
密码随便编一个提交
利用wireshark进行抓取HTTP
这样不用解释就知道下面的代码要干啥了
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 = params elif ct.startswith('application/x-www-form-urlencoded') or ct.startswith('multipart/form-data'): params = await request.post() kw = dict(**params)
相比于处理POST方法来说,处理GET方法就简单了许多
if request.method == 'GET': qs = request.query_string if qs: kw = dict() for k, v in parse.parse_qs(qs, True).items(): kw[k] = v[0]
主要是处理带参数的url
聊完了判断,接着就以前面的HTTP方法为POST的kw做一个结尾吧
从前面的图片可以知道kw里面存的是什么
来看看关于用户注册功能的url注册函数的输出吧
INFO:root:add route POST /api/users => api_register_user(email, name, passwd)has_request_arg --> (*, email, name, passwd)has_var_kw_arg --> OrderedDict([('email', <Parameter "email">), ('name', <Parameter "name">), ('passwd', <Parameter "passwd">)])has_named_kw_args --> OrderedDict([('email', <Parameter "email">), ('name', <Parameter "name">), ('passwd', <Parameter "passwd">)])get_named_kw_args --> OrderedDict([('email', <Parameter "email">), ('name', <Parameter "name">), ('passwd', <Parameter "passwd">)])get_required_kw_args --> OrderedDict([('email', <Parameter "email">), ('name', <Parameter "name">), ('passwd', <Parameter "passwd">)])
很明显和kw里面的k一一对应,为了防止不必要的参数,还是做一下判断吧
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
但是这还不够,kw只存了用户注册的数据,url的路径还没有存入kw
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] = v
当然啦,还有有request实例,这个是在构造url处理函数中必不可少的参数
if self._has_request_arg: kw['request'] = request
再检查一遍参数:
if self._required_kw_args: for name in self._required_kw_args: if not name in kw: return web.HTTPBadRequest('Missing argument: %s' % name)
然后把参数传入函数fn
再来看一下add_route函数把
这时候对于一个url处理函数我们已经获得了必要的参数,最后一步便是注册!
app.router.add_route(method, path, RequestHandler(app, fn))
参考文档:
[1]aiohttp
[2]urllib.parse
[3]the-meaning-of-the-module-name
[4]Python rfind()方法
[5]wireshare抓包教程
[6]Python import() 函数
理解难免会有偏差,敬请指正!
阅读全文
0 0
- 廖老师python教程实战Day5-编写web框架理解
- 廖老师python教程实战Day3-编写ORM理解
- Python廖雪峰实战web开发(Day5-编写web框架)
- Python3教程Web开发实战梳理-day5(Web框架)
- python一个精简的ORM框架(廖老师的python教程)理解
- Python廖雪峰实战web开发(Day3-编写ORM)
- Python廖雪峰实战web开发(day4-编写Model)
- Python廖雪峰实战web开发(Day6-编写配置文件)
- Python廖雪峰实战web开发(Day7-编写MVC)
- Python实战开发之Pyramid Web框架在商城项目中的应用教程
- IBM python编写Web service教程
- Python廖雪峰实战web开发(Day2-编写Web APP骨架)
- python学习——编写web框架
- Python3教程Web开发实战梳理-day3(编写ORM)
- Python3教程Web开发实战梳理-day4(编写Model)
- Python3教程Web开发实战梳理-day7(编写MVC)
- Python3教程Web开发实战梳理-day9(编写API)
- Day5、Python
- 俞军给淘宝产品经理的分享
- eclipse项目上传github或码云
- Vue的Class 与 Style 绑定
- 时区修改
- Markdown编辑器用法
- 廖老师python教程实战Day5-编写web框架理解
- Java https访问
- [Err] ORA-02289: sequence does not exist序列不存在
- Android 7.1 GUI系统-窗口管理WMS-窗口大小计算(五)
- Linux 安装vsftpd
- django CKeditor 正常显示富文本
- Codeforces Round #451 (Div. 2) D. Alarm Clock
- Windows下使用Python配制环境以及打开方式
- 迷宫系列(三)利用BFS/DFS的数据得到最短路/通路