Day 5 - 编写Web框架 要理解的问题多多

简介:

别人的理解

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

相关文章
|
4天前
|
中间件 Go API
【Go 语言专栏】Go 语言中的 Web 框架比较与选择
【4月更文挑战第30天】本文对比了Go语言中的四个常见Web框架:功能全面的Beego、轻量级高性能的Gin、简洁高效的Echo,以及各自的性能、功能特性、社区支持。选择框架时需考虑项目需求、性能要求、团队经验和社区生态。开发者应根据具体情况进行权衡,以找到最适合的框架。
|
4天前
|
机器学习/深度学习 前端开发 数据可视化
数据分析web可视化神器---streamlit框架,无需懂前端也能搭建出精美的web网站页面
数据分析web可视化神器---streamlit框架,无需懂前端也能搭建出精美的web网站页面
|
4天前
|
开发框架 前端开发 JavaScript
学会Web UI框架--Bootstrap,快速搭建出漂亮的前端界面
学会Web UI框架--Bootstrap,快速搭建出漂亮的前端界面
|
4天前
|
缓存 前端开发 安全
Python web框架fastapi中间件的使用,CORS跨域详解
Python web框架fastapi中间件的使用,CORS跨域详解
|
4天前
|
API 数据库 Python
Python web框架fastapi数据库操作ORM(二)增删改查逻辑实现方法
Python web框架fastapi数据库操作ORM(二)增删改查逻辑实现方法
|
4天前
|
关系型数据库 MySQL API
Python web框架fastapi数据库操作ORM(一)
Python web框架fastapi数据库操作ORM(一)
|
4天前
|
Python
python web框架fastapi模板渲染--Jinja2使用技巧总结
python web框架fastapi模板渲染--Jinja2使用技巧总结
|
4天前
|
开发框架 网络协议 前端开发
Python高性能web框架--Fastapi快速入门
Python高性能web框架--Fastapi快速入门
|
5天前
|
网络协议 数据库 开发者
构建高效Python Web应用:异步编程与Tornado框架
【4月更文挑战第29天】在Web开发领域,响应时间和并发处理能力是衡量应用性能的关键指标。Python作为一种广泛使用的编程语言,其异步编程特性为创建高性能Web服务提供了可能。本文将深入探讨Python中的异步编程概念,并介绍Tornado框架如何利用这一机制来提升Web应用的性能。通过实例分析,我们将了解如何在实际应用中实现高效的请求处理和I/O操作,以及如何优化数据库查询,以支持更高的并发用户数和更快的响应时间。
|
5天前
|
JSON 前端开发 网络架构
Django的web框架Django Rest_Framework精讲(四)
Django的web框架Django Rest_Framework精讲(四)