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

相关文章
|
1月前
|
安全 数据库 开发者
Python Web框架简介
【2月更文挑战第10天】Python Web框架简介。
90 2
|
16天前
|
安全 数据库 C++
Python Web框架比较:Django vs Flask vs Pyramid
【4月更文挑战第9天】本文对比了Python三大Web框架Django、Flask和Pyramid。Django功能全面,适合快速开发,但学习曲线较陡;Flask轻量灵活,易于入门,但默认配置简单,需自行添加功能;Pyramid兼顾灵活性和可扩展性,适合不同规模项目,但社区及资源相对较少。选择框架应考虑项目需求和开发者偏好。
|
1天前
|
开发框架 前端开发 数据库
Python从入门到精通:3.3.2 深入学习Python库和框架:Web开发框架的探索与实践
Python从入门到精通:3.3.2 深入学习Python库和框架:Web开发框架的探索与实践
|
10天前
|
前端开发 数据挖掘 API
使用Python中的Flask框架进行Web应用开发
【4月更文挑战第15天】在Python的Web开发领域,Flask是一个备受欢迎的轻量级Web框架。它简洁、灵活且易于扩展,使得开发者能够快速地构建出高质量的Web应用。本文将深入探讨Flask框架的核心特性、使用方法以及在实际开发中的应用。
|
22天前
|
前端开发 安全 Java
使用Java Web框架:Spring MVC的全面指南
【4月更文挑战第3天】Spring MVC是Spring框架的一部分,用于构建高效、模块化的Web应用。它基于MVC模式,支持多种视图技术。核心概念包括DispatcherServlet(前端控制器)、HandlerMapping(请求映射)、Controller(处理请求)、ViewResolver(视图解析)和ModelAndView(模型和视图容器)。开发流程涉及配置DispatcherServlet、定义Controller、创建View、处理数据、绑定模型和异常处理。
使用Java Web框架:Spring MVC的全面指南
|
26天前
|
前端开发 JavaScript 数据管理
描述一个使用Python开发Web应用程序的实际项目经验,包括所使用的框架和技术栈。
使用Flask开发Web应用,结合SQLite、Flask-SQLAlchemy进行数据管理,HTML/CSS/JS(Bootstrap和jQuery)构建前端。通过Flask路由处理用户请求,模块化代码提高可维护性。unittest进行测试,开发阶段用内置服务器,生产环境可选WSGI服务器或容器化部署。实现了用户注册登录和数据管理功能,展示Python Web开发的灵活性和效率。
14 4
|
1月前
|
数据库
最全三大框架整合(使用映射)——struts.xml和web.xml配置
最全三大框架整合(使用映射)——数据库资源文件jdbc.properties
10 0
|
1月前
|
前端开发 API 网络架构
Python 如何开发出RESTful Web接口,DRF框架助力灵活实现!
Python 如何开发出RESTful Web接口,DRF框架助力灵活实现!
|
1月前
|
XML JSON API
通过Flask框架创建灵活的、可扩展的Web Restful API服务
通过Flask框架创建灵活的、可扩展的Web Restful API服务
|
1月前
|
物联网 调度 开发者
构建高效Python Web应用:异步编程与Tornado框架解析
【2月更文挑战第27天】 在处理高并发的Web应用场景时,传统的同步阻塞模型往往难以满足性能需求。本文将深入探讨Python世界中的异步编程概念,并结合Tornado这一轻量级、非阻塞式Web服务器及框架,展示如何构建高性能的Web应用。通过实例驱动的方法论,我们将剖析Tornado的核心组件,包括其IOLoop、异步HTTP客户端和服务器端处理机制,以及与协程集成的细节。文章旨在为开发者提供一套实践指南,帮助他们利用Python实现快速响应和资源高效的Web服务。
29 2