220 lines
10 KiB
Python
220 lines
10 KiB
Python
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进行了包装,固定了wrapped,assigned,updated三个参数。
|
||
# 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))
|
||
# iscoroutinefunction():Return True if the object is a 协程函数
|
||
# isgeneratorfunction():Return 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() 函数用于检查一个对象是否是可调用的。如果返回 True,object 仍然可能调用失败;但如果返回 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) |