本文共 12354 字,大约阅读时间需要 41 分钟。
别人的理解
http://yeqianfeng.me/aiphttp-handler-of-comming-request/
自己的理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 先看懂整体框架,再看详细实现 1.coroweb .py在client请求开始返回func( * args, * * kw),然后编写func( * args, * * kw)处理 #比如get('/index')(func(*args, **kw)) 2.middlewares = [logger_factory, response_factory] init_jinja2(app, filters = dict (datetime = datetime_filter)) 3. 先看懂add_routes(app, 'handlers' ),然后是add_static(app),最后await handler(request) add_route(app, handles.create_comment) 变成 # 自动把handler模块的所有符合条件的函数注册了: add_routes(app, 'handlers' ) add_routes,handler - >是否有index,blog等属性 / / 不理解的 fn = getattr (mod, attr) / / 到了add_route,变成了app.router.add_route(method, path, RequestHandler(app, fn)) 4. 最后的细节 func( * args, * * kw) if 全部都会执行 5.middlewares 拦截器 / / await handler(request) 06.30 更新 上面一团乱糟糟的,重新梳理 1.coroweb .py 主要是app.router.add_route(method, path, RequestHandler(app, fn)) 理解为url和对应的函数绑定 2.add_route (app,fn) fn变成协程 3. 然后handlers.py写具体实现方法,比如index() 请求一过来,先找到add_route对应的函数,因为app.py已经批量绑定了handlers.py中方法到url上(模糊匹配,类似的感觉,通用) 找到对应的函数,执行handlers中的函数,执行的过程调用coroweb.py中的get和post偏函数,就是装饰器 4. 再添加一些拦截器 app = web.Application(loop = loop, middlewares = [logger_factory, response_factory]) 其中 return (await handler(request)),handler(request)应该是在框架里面写好了。 5.handlers .py就是写全部逻辑的地方。 |
代码地址
https://github.com/michaelliao/awesome-python3-webapp/blob/day-05/www/coroweb.py
http://blog.csdn.net/qq_38801354/article/details/73008111
然后要搞清楚一下这些东西,多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | 文档 http: / / aiohttp.readthedocs.io / en / stable / web.html 先看懂整体框架,再看详细实现 参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。 去熟悉这些写法 数据集合并 http: / / pandas.pydata.org / pandas - docs / stable / merging.html from jinja2 import Environment python inspect模块解析 - 郭猛的个人空间 Python 的内置函数 __import__ inspect.signature https: / / docs.python.org / 3 / library / inspect.html 为啥进程池封装在装饰器中不能生效,而多进程可以? http: / / pythontutor.com / visualize.html #mode=edit 1000 + w 的数据去重也可以用 bloom filter 啊,就用 Redis 的 bitmap 存 bit 数组就可以了。 aiomysql.DictCursor会将结果返回为字典 __all__ = [ "echo" , "surround" , "reverse" ]。 这就意味着当 from sound.effects import * 语句执行时,会导入那三个模块 相当详细的解释 https: / / github.com / icemilk00 / Python_L_Webapp / blob / master / www / app.py https: / / github.com / icemilk00 / Python_L_Webapp https: / / www.v2ex.com / t / 347788 https: / / www.v2ex.com / t / 347421 还要不断的去翻python3的原始文档,看asyncio,httpio的说明和源代码,最终还是明白了 优先看 https: / / github.com / moling3650 / mblog / blob / master / www / app / frame / __init__.py http: / / blog.csdn.net / qq_38801354 / article / details / 73008111 https: / / segmentfault.com / a / 1190000008400059 http: / / www.w2bc.com / article / 218471 https: / / zhuanlan.zhihu.com / p / 22494483 http: / / lovenight.github.io / 2016 / 09 / 25 / Python - 3 - % E5 % AD % A6 % E4 % B9 % A0 % E7 % AC % 94 % E8 % AE % B0 / http: / / blog.csdn.net / yueguanghaidao / article / details / 11708417 init_jinja2(app, filters = dict (datetime = datetime_filter)) def init_jinja2(app, * * kw): logging.info( 'init jinja2...' ) options = dict ( autoescape = kw.get( 'autoescape' , True ), block_start_string = kw.get( 'block_start_string' , '{%' ), block_end_string = kw.get( 'block_end_string' , '%}' ), variable_start_string = kw.get( 'variable_start_string' , '{ {' ), variable_end_string = kw.get( 'variable_end_string' , '}}' ), auto_reload = kw.get( 'auto_reload' , True ) ) path = kw.get( 'path' , None ) if path is None : path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates' ) logging.info( 'set jinja2 template path: %s' % path) env = Environment(loader = FileSystemLoader(path), * * options) filters = kw.get( 'filters' , None ) if filters is not None : for name, f in filters.items(): env.filters[name] = f app[ '__templating__' ] = env env上添加属性datetime_filter,变成了方法 |
最后看能不能看懂别人的问题和回复
问题
看到好多函数都有request做参数,但这个request是什么时候传进去的呢,没看到从哪里得到request的,新手求轻喷
哥们,你源码里的所有url处理函数的参数都是来源于request。可我查询了aiohttp,发现request是一个对象,好像没有那些属性呀。aiohttp。迷惑中,盼回。
RequestHandler
我遇到的第二个难点就是RequestHandler,因为RequestHandler看起来是一个类,但又不是一个类,从本质上来说,它是一个函数,那问题来了,这个函数的作用到底是什么呢?
如果大家有仔细看day2的hello world的例子的话,就会发现在那个index函数里是包含了一个request参数的,但我们新定义的很多函数中,request参数都是可以被省略掉的,那是因为新定义的函数最终都是被RequestHandler处理,自动加上一个request参数,从而符合app.router.add_route第三个参数的要求,所以说RequestHandler起到统一标准化接口的作用。
接口是统一了,但每个函数要求的参数都是不一样的,那又要如何解决呢?得益于factory的理念,我们很容易找一种解决方案,就如同response_factory一样把任何类型的返回值最后都统一封装成一个web.Response对象。RequestHandler也可以把任何参数都变成self._func(**kw)的形式。那问题来了,那kw的参数到底要去哪里去获取呢?
request.match_info的参数: match_info主要是保存像@get('/blog/{id}')里面的id,就是路由路径里的参数
GET的参数: 像例如/?page=2
POST的参数: api的json或者是网页中from
request参数: 有时需要验证用户信息就需要获取request里面的数据
说到这里应该很清楚了吧,RequestHandler的主要作用就是构成标准的app.router.add_route第三个参数,还有就是获取不同的函数的对应的参数,就这两个主要作用。只要你实现了这个作用基本上是随你怎么写都行的,当然最好加上参数验证的功能,否则出错了却找不到出错的消息是一件很头痛的是事情。在这个难点的我没少参考同学的注释,但觉得还是把这部分的代码太过复杂化了,所以我用自己的方式重写了RequestHandler,从老师的先检验再获取转换成先获取再统一验证,从逻辑上应该是没有问题,但大幅度简化了程序。
你可以参考我的data_factory的实现。
如果method == 'GET'时,参数就是查询字符串,也就是request.query_string
如果method == 'POST'时,有两种可能,Ajax的json和html的form(表单),分别对应request.json()和request.post()。 data_factory的主要作用就是把这些参数统一绑定在request.__data__上。
在RequestHandler里,init是初始化用的,在生成实例的时候执行,而call是模拟()的调用,需要在实例上应用,在下面这句代码里:
app.router.add_route(method, path, RequestHandler(func))
RequestHandler这个类并没有创建实例,是不是意味着call并没有执行,在我的代码里貌似是这样的。
小白一只,卡在这里好几天了,希望能解决我的疑惑。。。
你理解错了,RequestHandler(func)就是一个实例,只不过没有给它命名,最终会在factorys的response_factory调用。
r = await handler(request)
这里的request也就是__call__(request)的参数。
你说的意思是 r = await handler(request) 里的handler就代表RequestHandler(func),这样call就被执行了,可是我还是不太明白handler怎么跟RequestHandler(func)联系起来的
response_factory的r = await handler(request)实际上是调用r = await RequestHandler(request),然后内部又调用了await self._func(**kw),这里才是调用我们自己定义的函数比如await hello(**kw),最后把函数处理后的数据传回到response_factory,response_factory根据hello(**kw)的返回值封装成一个响应对象发送给浏览器。
app.router.add_route只不过是一个注册器,把我们写的某的函数和URL绑定,形成一个映射关系而已
不赖@流留66 ,我也糊涂了。
难道r = await handler(request)不是调用r = await RequestHandler(app,fn)(request)的意思吗?
@灰_手 你手滑了?
pymysql.err.InternalError: (1054, "Unknown column 'password' in 'field list'")
我的git的ORM测试,不过要将# 测试count rows语句下面两句注释,我重写了findNum方法了。
day06,day07是水课,后边除了还有个day13 watchdog , day15 fabric之外都是体力活
day12 日志列表分页,如果之前没有做过分页,这一天的课程也很有趣
前端确实是个大坑,前端届太闹腾.今天这个框架火了,明天那个库黄了.
要学的东西又多又杂,看得我两股战战,几欲先走.
最基本的老三样还是得掌握的:
html掌握常用标签,会简单布个局.
css 掌握选择符和浮动清除的概念就能毕业,日常使用查手册就差不多了.
css这东西掌握了没卵用,我用起来和别人用起来是两回事,没有美感做出东西来照样丑.
像UIKit,bootstrap这些UI包真的是业界良心,一下子把没有美感不懂设计的人们救活了.
原生javascript,它的很多概念我学过又忘记了.
只剩下document.getElementById,XMLHttpRequest,setInterval是我的三宝.
有了这三样,DOM,ajax,动画我都能捅咕一下.
以前javascript对象的继承方式有很多种,我一样也记不住.
prototype引用来引用去的根本闹不清什么状况.
廖大的教程讲ES6,我要学一下.
学习资料
石川(blue) 2012年录制的一套javascript教程32集.
第1集 初探javascript魅力
http://v.youku.com/v_show/id_XNDU1MjMxNTQ0.html
我学习实战的时候day04,day05卡了一个星期.
前端的部分我是直接抄廖大的代码.我对廖大实现的ORM特别感兴趣.
day04,day05以后我就判定自己毕业了,博客的其他功能我根本没完成.
我现在复习了一遍整个教程,目前看到sqlalchemy部分,快到实战填坑了,假如在实战部分我学习起前端来,大概就绕不回来了.到时候咱们就只好在node.js教程里见了.
前端是大坑!
html,css,js的耦合度太高了,随便一个改动也是非常困难的,比如你想把vue.js从0.12版升级到1.02版,就会发现在语法有N多不兼容的地方,不但要改js,就连html也要改,如果你想把uikit换成bootstrap的话,你会发现html要改,javascipt也是要改的,css是改成最少的地,前提是你不用它,只要换了ui框架,命名问题必要存在,除非你自己在js把id和class写成可变的... 总之就是牵一发而动全身。
有问题多找文档,没有精力学就是复制廖大的代码好了。
UK中文网,虽然容易看懂,但也有些deom不适用了。能看英文最好看英文的
Vue.js中文官网,这里的文档还是挺好的,易实现,效果好。
import(module_name, fromlist=['get_submodule'])里的get_submodule是什么意思?
在其他代码里也没发现get_submodule模块
get submodule没有任何意思。这里是个黑魔法。当fromlist不为空时,__import__可以直接导入子模块
user.id 是users表中每一行记录业务无关的唯一标识.
它的值由定义在models.py中的next_id()生成.
@post('/api/authenticate') #登录鉴定
sha1.update(user.id.encode('utf-8'))
sha1.update(b':')
sha1.update(passwd.encode('utf-8'))
这么验证的原因是为了配合新建用户的时候设置的密码的格式
@post('/api/users')#新建用户
sha1_passwd = '%s:%s' % (uid, passwd) #密码的生成格式
user = User(id=uid, name=name.strip(), email=email,
passwd=hashlib.sha1(sha1_passwd.encode('utf-8')).hexdigest(),#看密码那里
image='http://www.gravatar.com/avatar/%s?d=mm&s=120' % hashlib.md5(email.encode('utf-8')).hexdigest())
await user.save()
这里看到的password已经是进行过一次摘要的,防止密码明文在中途被截获.
注册用户页面和用户登录页面上使用javascript 提前将email和password明文揉在一起
CryptoJS.SHA1(email + ':' + this.password1).toString()
最后将摘要作为password,和email一起发送给服务端.服务端将user.id:password再次进行摘要操作.作为用户的密码密文存储在数据库表中.
带cookie的登录流程:
前提条件:
创建一个管理员账户,注册新用户,admin字段手动修改为1.我记得我改过.
1.访问/manage开头的url时,auth_factory middleware检查客户端带来的cookie,鉴定是否可以免登录.如果客户端没有带来一个叫COOKIE_NAME的cookie或者cookie内容是伪造的,将客户端浏览器重定向到登录页面.
2.在登陆页面填写email和passwrod,点击登录按钮.
前端代码将email:password进行一次摘要作为password,然后通过廖大的postJSON将email和password发送给服务端 /api/authenticate.
3.服务端鉴定,authenticate方法中,首先通过email查找到对应的user,得到user.id.
然后将user.id:password进行一次摘要,并与创建用户时生成的user.password进行对比.
若两者相同,则创建一个免登录cookie,随response一起响应给客户端.
cookie的内容,查看user2cookie()方法.
为什么 user.passwd = '******'
user.passwd = '********'
r.content_type = 'application/json'
r.body = json.dumps(user, ensure_ascii=False).encode('utf-8')
return r
因为这个response的body部分是将代表当前登录用户的user对象转化为了一个json返回给客户端.
如果不抹掉user.passwd,客户端就能拿到用户登录密码的密文.
免登录cookie的制作配方里用到了这个密文
def user2cookie(user, max_age):
'''
Generate cookie str by user.
'''
# build cookie string by: id-expires-sha1
expires = str(int(time.time() + max_age))
s = '%s-%s-%s-%s' % (user.id, user.passwd, expires, _COOKIE_KEY)
L = [user.id, expires, hashlib.sha1(s.encode('utf-8')).hexdigest()]
return '-'.join(L)
客户端现在拿到密文虽然没什么卵用,但本站在未来被黑的风险增加.
在未来的日子里,不排除有科学家用户首先日翻了消息摘要算法,然后截获本站管理员的登录密码密文,伪造出合法的免登录cookie,用管理员的身份来日翻本站.
这种情况属于天有不测风云,不可抗力,真有那一天,反正大家都被日翻了,谁也不笑话谁.
这是我代码存放的地点:https://github.com/WalleSun415/awesome-python3-webapp
登陆按钮的动作
templates/signin.html
这段js大概是vue与jquery混合双打的写法.我对vue没有研究,大概能猜出这段代码的意思.
data里email和passwd的值由vue来维护.
methods里边的submit对应的就是提交处理代码.
首先一个event.preventDefault()阻止默认事件,由客户端决定行为.
然后下边把email:password摘要一次,然后用廖大的postJSON向 /api/authenticate发送post请求,如果返回没有错误,就给浏览器地址栏定位到站点根目录.
<script>
$(function() {
var vmAuth = new Vue({
el: '#vm',
data: {
email: '',
passwd: ''
},
methods: {
submit: function(event) {
event.preventDefault();
var
$form = $('#vm'),
email = this.email.trim().toLowerCase(),
data = {
email: email,
passwd: this.passwd==='' ? '' : CryptoJS.SHA1(email + ':' + this.passwd).toString()
};
$form.postJSON('/api/authenticate', data, function(err, result) {
if (! err) {
location.assign('/');
}
});
}
}
});
});
</script>
你说的注册按钮绑定的 在
templates/register.html里修改
$(function () {
var vm = new Vue({
el: '#vm',
data: {
name: '',
email: '',
password1: '',
password2: ''
},
methods: {
submit: function (event) {
event.preventDefault();
var $form = $('#vm');
if (!this.name.trim()) {
return $form.showFormError('请输入名字');
}
if (!validateEmail(this.email.trim().toLowerCase())) {
return $form.showFormError('请输入正确的Email地址');
}
if (this.password1.length < 6) {
return $form.showFormError('口令长度至少为6个字符');
}
if (this.password1 !== this.password2) {
return $form.showFormError('两次输入的口令不一致');
}
var email = this.email.trim().toLowerCase();
$form.postJSON('/api/users', { #这里postJSON('/api/users')
name: this.name.trim(),
email: email,
passwd: CryptoJS.SHA1(email + ':' + this.password1).toString()
}, function (err, r) {
if (err) {
return $form.showFormError(err);
}
return location.assign('/');
});
}
}
});
$('#vm').show();
});
@get('/register')
这个url是用来显示注册用户页面的.你github里没写错呀.
这个问题我自己找到了,问题就在那段js脚本中el,data,methods中的methods,我写成了method,这样就导致了没有调用到这个方法,还是自己太粗心了,哎呀,好气呀!
在昨天是终于把所有功能都实现了,在自己的电脑上也都跑通了,接下来还不打算部署,而是想好好总结一下,总结一下前段后端的所有流程、功能,还想画一个所有函数调用的流程图给后来人作为参考,到时可能还要请教你看我的想法是否是正确的;其次,把前段界面按照自己的想法风格改一下,现在的界面还都是和廖大教程里的一样,虽然是简洁,但是我丑起来我自己都害怕...最后,就是想重构一下,按照自己的想法把项目去耦合,再用flask实现一遍,这样这个东西就可以结束了。
在这短时间内,真是多谢谢你和@灰_手,经常会麻烦你们帮忙解答我的各种问题,多谢多谢!你最近复习教程后在做什么?接下来准备干嘛?(ps:你有新浪微博或是知乎的账号么?我关注你 。。。)
我复习到python实战第一天的时候,忽然心血来潮学起了廖大的javascript教程.看到DOM部分了.后边我想试试node.js. python的实战不知道什么时候才能绕回去.
哈哈,等你重构完实战项目,咱们node.js教程里见.
我的git有现成的flask框架搭建的后端,需要有些地方可以改进,不过还是可以用的。
flask和aiohttp都是web框架吧,我看到网上很多关于flask和Django的资料,aiohttp很少,这些框架有什么优劣吗?
在 #联调时一直出现如下错误
#self._encoding = charset_by_name(self._charset).encoding
#AttributeError: 'NoneType' object has no attribute 'encoding'
#原因竟然是把这里的utf8 写成了utf-8,卧槽!!
charset=kw.get('charset', 'utf8'),
autocommit=kw.get('autocommit', True),
#默认最大连接数
maxsize=kw.get('maxsize', 10),
minsize=kw.get('minsize', 1),
loop=loop
此插入代码
day05难点,06,07简单,跑起来了。
本文转自 liqius 51CTO博客,原文链接:http://blog.51cto.com/szgb17/1943521,如需转载请自行联系原作者