博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Day 5 - 编写Web框架 要理解的问题多多
阅读量:6158 次
发布时间:2019-06-21

本文共 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,如需转载请自行联系原作者

你可能感兴趣的文章
类中如何对list泛型做访问器??
查看>>
C++解析XML--使用CMarkup类解析XML
查看>>
P2P应用层组播
查看>>
Sharepoint学习笔记—修改SharePoint的Timeouts (Execution Timeout)
查看>>
CSS引入的方式有哪些? link和@import的区别?
查看>>
Redis 介绍2——常见基本类型
查看>>
asp.net开发mysql注意事项
查看>>
(转)Cortex-M3 (NXP LPC1788)之EEPROM存储器
查看>>
ubuntu set defult jdk
查看>>
[译]ECMAScript.next:TC39 2012年9月会议总结
查看>>
【Xcode】编辑与调试
查看>>
用tar和split将文件分包压缩
查看>>
[BTS] Could not find stored procedure 'mp_sap_check_tid'
查看>>
PLSQL DBMS_DDL.ALTER_COMPILE
查看>>
Activity生命周期
查看>>
高仿UC浏览器弹出菜单效果
查看>>
Ubuntu忘记密码,进不了系统的解决方法
查看>>
[原创]白盒测试技术思维导图
查看>>
<<Information Store and Management>> 读书笔记 之八
查看>>
Windows 8 开发之设置合约
查看>>