python简易博客(五)–web框架

正文:

这里个人认为很专业的分析了python创建web服务,很nice
https://www.cnblogs.com/ameile/p/5589808.html

flask框架:

Flask的socket是基于Werkzeug 实现的,模板语言依赖jinja2模板
Flask 支持用扩展来给应用添加这些功能。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask的这些特性,使得它在Web开发方面变得非常流行
https://www.cnblogs.com/wangshuyang/p/7754188.html 很nice
http://python.jobbole.com/86707/
https://blog.csdn.net/a32521500/article/details/60575641
http://python.jobbole.com/88621/
https://www.cnblogs.com/sss4/p/8097653.html 这个分析各个不同的web框架(很不错)

理解前的准备:

如果是aiohttp步骤:(编写url的处理函数(编写一个用@asyncio.coroutine装饰的函数 》传入的参数需要自己从request中获取》需要自己构造Response对象))
而web框架: 主要是处理带参数的url  (不是主要编写url处理函数)
middleware是一种拦截器,一个URL在被某个函数处理前,可以经过一系列的middleware的处理。
https://blog.csdn.net/u013679490/article/details/54772863 提取函数签名python3 inspect.signature()
https://blog.csdn.net/freeking101/article/details/52458647 很nice 讲解了提取语言中的属性
https://blog.csdn.net/weixin_35955795/article/details/53053762 廖老师的inspect讲解

coroweb.py文件:

https://blog.csdn.net/eye_water/article/details/78822727
0.inspect
获取对象的属性和信息
https://blog.csdn.net/weixin_35955795/article/details/53053762
1.get和post函数 (装饰器)
不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可
把一个函数映射为一个URL处理函数(装饰器的三重,动态获取了url的3个参数,暂时这么理解)
相当于get(index),参数什么的直接返回不用修改,再加上了两个属性__method__和__route__。这样,获取方法和路径就可以通过获取url处理函数的属性__method和__route__就行了,接下来就要进行注册url处理函数了

2.

get_required_kw_args   get_named_kw_args   has_named_kw_args   has_var_kw_arg    has_request_arg   (主要是处理带参数的url)

3.

Requesthandler类来封装一个URL处理函数
__call__ 视为函数  (具体有什么意义,还有看看)
add_route 函数 用来注册一个URL处理函数  获取url处理函数的函数对象
add_routes   自动扫描  add_routes只是用来批量注册的

处理url处理函数了,在add_route函数中将其注册
if callable(fn):
            method = getattr(fn, '__method__', None)
            path = getattr(fn, '__route__', None)
            if method and path:
                add_route(app, fn)
这里的method和route   是get和post函数(装饰器)传过来的

RequestHandle类中有__callable__方法,因此RequestHandler(app, fn)相当于创建了一个url处理函数,函数名就是fn
4.(这是在coroweb.py文件外)
middleware是一种拦截器,一个URL在被某个函数处理前,可以经过一系列的处理
logger_factory记录URL日志   (app.py文件里的函数)
response_factory把返回值转换为web.Response对象再返回,以保证满足aiohttp的要求     (app.py文件里的函数)
这两个函数在app.py里,是群处理的两个函数
有了这些基础设施,我们就可以专注地往handlers模块不断添加URL处理函数了,可以极大地提高开发效率
5.
app = web.Application(loop=loop, middlewares=[logger_factory, response_factory])    连接数据库后就构建app类
add_static(app)  添加静态文件
init_jinja2(app, filters=dict(datetime=datetime_filter))  注册模板
add_routes(app, ‘handlers’)函数   注册url处理函数(https://blog.csdn.net/qq_38209122/article/details/79218326这里有很详细的讲解)     (等价于import handlers)
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   返回app类,处理传进来的url函数(handler类函数)
6.
RequestHandle类中有__callable__方法(前面那几个函数获取的),因此RequestHandler(app, fn)相当于创建了一个url处理函数,函数名就是fn(我们要通过HTTP协议来判断在GET或者POST方法中是否丢失了参数,如果判断方法编写在url处理函数中会有很多重复代码,因此用类来封装一下)(nice的解答)
经过装饰器的修饰后,index函数包含了请求方法GET/POST,请求路径path,以及HTTP请求头request(这里很好的解释了三重装饰器的作用,自己也有种豁然开朗的感觉)(request参数实际上是一个继承于class aiohttp.web.BaseRequest的实例,而aiohttp.web.BaseRequest类包含了一个请求所携带的所有HTTP信息)
post和get方法的判断以及requesthandler类中的几个函数解释(直接去大佬的网址看吧)
7.(这是在coroweb.py文件外)
最后,在app.py中加入middleware、jinja2模板和自注册的支持

二.总结:

web框架获取了url的信息,以及判断,让handler类更专注于编写处理url。其中add_routes 注册url处理函数相当于import handler类,个人认为这个理解到了有豁然开朗的感觉。
get/post函数(装饰器) 取得url信息
其中web框架requesthandler类,判断url
几个函数处理url信息(requesthandler类(web框架里))
最后注册url处理函数(url处理函数在handler类里)
8.创建web框架和服务器小流程
https://www.cnblogs.com/ameile/p/5589808.html
HTTP 协议格式为: POST /PATH /1.1 /r/n Header1:Value /r/n .. /r/n HenderN:Valule /r/n Body:Data
所需第三方库:
aiohttp,异步 Web 开发框架;jinja2,前端模板引擎;aiomysql,异步 mysql 数据库驱动
所需内置库:
logging,系统日志;asyncio,异步IO;os,系统接口;json,json 编码解码模块;time,系统时间模块;datetime,日期模块
一,编写处理函数:
二,创建Web服务器,并将处理函数注册进其应用路径(Application.router)
三,用协程创建监听服务,并使用aiohttp中的HTTP协议簇(protocol_factory)
四,创建协程,初始化协程,返回监听服务,进入协程执行
9.模板
mako、Cheetah jinja2
10.
把WSGIApplication类填充完毕,我们就得到了一个完整的Web框架。

源码注释:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
__author__ = 'Michael Liao'
import asyncio, os, inspect, logging, functools
from urllib import parse
from aiohttp import web
from apis import APIError
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
def post(path):
    '''
    Define decorator @post('/path')
    '''
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            return func(*args, **kw)
        wrapper.__method__ = 'POST'
        wrapper.__route__ = path
        return wrapper
    return decorator
def get_required_kw_args(fn): #收集没有默认值的命名关键字参数
    args = []
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY and param.default == inspect.Parameter.empty:
            args.append(name)
    return tuple(args)
def get_named_kw_args(fn): #获取命名关键字参数
    args = []
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY:
            args.append(name)
    return tuple(args)
def has_named_kw_args(fn):#判断有没有命名关键字参数
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY:
            return True
def has_var_kw_arg(fn):#判断有没有关键字参数
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.VAR_KEYWORD:
            return True
def has_request_arg(fn): #判断是否含有名叫'request'参数,且该参数是否为最后一个参数
    sig = inspect.signature(fn)
    params = sig.parameters
    found = False
    for name, param in params.items():
        if name == 'request':
            found = True
            continue
        if found and (param.kind != inspect.Parameter.VAR_POSITIONAL and param.kind != inspect.Parameter.KEYWORD_ONLY and param.kind != inspect.Parameter.VAR_KEYWORD):
            raise ValueError('request parameter must be the last named parameter in function: %s%s' % (fn.__name__, str(sig)))
    return found
get和post函数,把一个函数映射为一个url处理函数(两重装饰器)
这样一个函数经过@get的装饰后就附带了url信息
class RequestHandler(object):#不要跟我讲,这个类中是怎样判断url,我也不是很清楚(继承了app类中的东西,然后app中的各种函数就时不时出来一个。。)
#想要理解,先弄清楚inspect吧,我到现在对这个url的信息处理还是迷的
    def __init__(self, app, fn):#RequestHandler(app, fn)相当于创建了一个url处理函数,函数名就是fn 接受app参数
        self._app = app#HTTP协议来判断在GET或者POST方法中是否丢失了参数,如果判断方法编写在url处理函数中会有很多重复代码,因此用类来封装一下
        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)
    @asyncio.coroutine
    def __call__(self, request):#经过装饰器的修饰后,index函数包含了请求方法GET/POST,请求路径path,以及HTTP请求头request,如何获取请求头呢?
        kw = None#request参数实际上是一个继承于class aiohttp.web.BaseRequest的实例,而aiohttp.web.BaseRequest类包含了一个请求所携带的所有HTTP信息
        #如果我们想要获得请求头中的cookie就可以这样写:request.cookie
        #_callable_中的参数是如何获取的? 是通过先前定义的函数  self._has_request_arg = has_request_arg(fn)....
        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:  #查询有没提交数据的格式(EncType)
                    return web.HTTPBadRequest(text='Missing Content-Type.')  #大佬是wireshark抓包看,分析内容是否相符。理解的到,是什么还有慢慢研究,要有text
                ct = request.content_type.lower()
                if ct.startswith('application/json'):
                    params = yield from 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 = yield from 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]
        if kw is None:
            kw = dict(**request.match_info)
        else:
            if not self._has_var_kw_arg and self._named_kw_args:  #很明显和kw里面的k一一对应,为了防止不必要的参数,还是做一下判断吧。当函数参数没有关键字参数时,移去request除命名关键字参数所有的参数信息
                # remove all unamed kw:
                copy = dict()
                for name in self._named_kw_args:
                    if name in kw:
                        copy[name] = kw[name]
                kw = copy
            # check named arg:
            for k, v in request.match_info.items():  #但是这还不够,kw只存了用户注册的数据,url的路径还没有存入kw
检查命名关键参数
                if k in kw:
                    logging.warning('Duplicate arg name in named arg and kw args: %s' % k)
                kw[k] = v
        if self._has_request_arg: #当然啦,还有有request实例,这个是在构造url处理函数中必不可少的参数。假如命名关键字参数(没有附加默认值),request没有提供相应的数值,报错
            kw['request'] = request
        # check required kw:
        if self._required_kw_args: #再检查一遍参数
            for name in self._required_kw_args:
                if not name in kw:
                    return web.HTTPBadRequest('Missing argument: %s' % name)
        logging.info('call with args: %s' % str(kw))
        try:
            r = yield from self._func(**kw) #然后把参数传入函数fn
            return r
        except APIError as e:
            return dict(error=e.error, data=e.data, message=e.message)
def add_routes(app, module_name): #https://blog.csdn.net/qq_38209122/article/details/79218326 我也解释不出来。。。反正这个网址能看懂就对了
 '''
 返回'.'最后出现的位置
 如果为-1,说明是 module_name中不带'.',例如(只是举个例子) handles 、 models
 如果不为-1,说明 module_name中带'.',例如(只是举个例子) aiohttp.web 、 urlib.parse() n分别为 7 和 5
 我们在app中调用的时候传入的module_name为handles,不含'.',if成立, 动态加载module
 '''
 n = module_name.rfind('.')
 #n=-1,说明module_name中不含'.',动态加载该module
 if n == (-1):
 mod = __import__(module_name, globals(), locals())
 #n!=-1 module_name中存在'.'
 else:
 '''
 比如 aaa.bbb 类型,我们需要从aaa中加载bbb
 n = 3
 name = module_name[n+1:] 为bbb
 module_name[:n] 为aaa
 mod = getattr(__import__(module_name[:n], globals(), locals(), [name]), name),动态加载aaa.bbb
 上边三句其实相当于:
 aaa = __import__(module_name[:n], globals(), locals(), ['bbb'])
 mod = aaa.bbb
 还不明白的话看官方文档,讲的特别清楚:
 https://docs.python.org/3/library/functions.html?highlight=__import__#__import__
 '''
 name = module_name[n+1:]
 mod = getattr(__import__(module_name[:n], globals(), locals(), [name]), name)
 #for循环把所有的url处理函数给注册了
 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)
 #注册url处理函数fn,如果不是url处理函数,那么其method或者route为none,自然也不会被注册
 if method and path:
 add_route(app, fn)

 
 
 
 

标签:

发表评论

电子邮件地址不会被公开。 必填项已用*标注