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)