6-315/www/coroweb.py

220 lines
10 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio, os, inspect, logging, functools
from urllib import parse
from aiohttp import web
from apis import APIError
def get(path):
# 函数通过@get()的装饰就附带了URL信息。
'''
Define decorator @get('/path')
'''
# wrapper装饰器
# 装饰器本质上是一个Python函数它可以让其他函数在不需要做任何代码变动的前提下增加额外功能
# 装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
# 装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
# 可变参数*args和关键字参数**kwargs有了这两个参数装饰器就可以用于任意目标函数
def decorator(func):
# 返回了一个partial对象这个对象对update_wrapper进行了包装固定了wrappedassignedupdated三个参数。
# wraps本省就是一个装饰器因为它返回的是一个“函数”即partial对象这个对象接收函数作为参数同时以函数作为返回值。
# 把wrapped函数的属性拷贝到wrapper函数中。
# wrapped是被装饰的原函数
# wrapper是被装饰器装饰后的新函数。
@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 里加上仅包含关键字keyword的参数 且不包括默认值, 然后返回 args
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 _, 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 _, param in params.items():
if param.kind == inspect.Parameter.VAR_KEYWORD:
return True
def has_request_arg(fn):
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
# RequestHandler目的就是从URL函数中分析其需要接收的参数从request中获取必要的参数
# 调用URL函数然后把结果转换为web.Response对象
class RequestHandler(object):
def __init__(self, app, fn):
self._app = app
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)
async def __call__(self, request):
kw = None
if self._has_var_kw_arg or self._has_named_kw_args or self._required_kw_args:
# 只要获取到了var 姓名 或者需要的关键字之一就判断是不是post请求
if request.method == 'POST':
# 如果没有内容类型 text/html等之类的参数
if not request.content_type:
return web.HTTPBadRequest(reason='Missing Content-Type.')
ct = request.content_type.lower() #全转小写
# 如果内容类型是以json开头json文件
if ct.startswith('application/json'):
params = await request.json()
# inspect库中的检查是否是对象的方法
if not isinstance(params, dict):
return web.HTTPBadRequest(reason='JSON body must be object.')
kw = params
#判断内容类型开头是不是原生表单或者表单请求
elif ct.startswith('application/x-www-form-urlencoded') or ct.startswith('multipart/form-data'):
params = await request.post()
kw = dict(**params)
else:
# 不支持的类型
return web.HTTPBadRequest(reason='Unsupported Content-Type: %s' % request.content_type)
# 判断是不是get请求
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:
# 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():
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:
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(reason='Missing argument: %s' % name)
logging.info('call with args: %s' % str(kw))
try:
r = await self._func(**kw)
return r
except APIError as e:
return dict(error=e.error, data=e.data, message=e.message)
return RequestHandler.__call__()
def add_static(app):
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')
app.router.add_static('/static/', path)
logging.info('add static %s => %s' % ('/static/', path))
# 改变静态网址下路径
def add_static(app):
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')
app.router.add_static('/static/', path)
logging.info('add static %s => %s' % ('/static/', path))
# add_route函数用来注册一个URL处理函数
def add_route(app, fn):
# getattr() 函数用于返回一个对象属性值。
# 类型
# 路径
method = getattr(fn, '__method__', None)
path = getattr(fn, '__route__', None)
if path is None or method is None:
raise ValueError('@get or @post not defined in %s.' % str(fn))
# iscoroutinefunctionReturn True if the object is a ‎协程函数
# isgeneratorfunctionReturn True if the object is a Python 生成器函数
if not asyncio.iscoroutinefunction(fn) and not inspect.isgeneratorfunction(fn):
# asyncio.coroutine
# 用来标记基于生成器的协程的装饰器。
# 此装饰器使得旧式的基于生成器的协程能与 async/await 代码相兼容
# 此装饰器不应该被用于 async def 协程
fn = asyncio.coroutine(fn)
#日志内容inspect.signature(fn).parameters获取函数参数的参数名参数的属性参数的默认值
logging.info('add route %s %s => %s(%s)' % (method, path, fn.__name__, ', '.join(inspect.signature(fn).parameters.keys())))
app.router.add_route(method, path, RequestHandler(app, fn))
# 把很多次add_route()注册的调用
def add_routes(app, module_name):
# Python rfind() 返回字符串最后一次出现的位置,如果没有匹配项则返回 -1。
n = module_name.rfind('.')#对应python文件存为handles使用rfind找到handles.py文件
if n == (-1):#如果没有匹配项,返回(-1
# __import__() 函数用于动态加载类和函数 。
# 如果一个模块经常变化就可以使用 __import__() 来动态载入。
# globals() 函数会以字典类型返回当前位置的全部全局变量。
# locals() 函数会以字典类型返回当前位置的全部局部变量。
mod = __import__(module_name, globals(), locals())
else:#找到了,返回字符串出现的最后一次位置。
name = module_name[n+1:]
mod = getattr(__import__(module_name[:n], globals(), locals(), [name]), name)
for attr in dir(mod):
if attr.startswith('_'):
continue
fn = getattr(mod, attr)
# callable() 函数用于检查一个对象是否是可调用的。如果返回 Trueobject 仍然可能调用失败;但如果返回 False调用对象 object 绝对不会成功。
# 对于函数、方法、lambda 函式、 类以及实现了 __call__ 方法的类实例, 它都返回 True。
if callable(fn):#如果找到了对应的处理方法。
method = getattr(fn, '__method__', None)
path = getattr(fn, '__route__', None)
if method and path:
# logging.info('add rosute ')
add_route(app, fn)