新建pecan工程的默认config.py文件(上一篇博文讲过,该文件是从scaffolds中的模板copy的)
// 启动的默认的host和port server = { 'port': '8080', 'host': '0.0.0.0' } // app的配置 app = { // root项很重要,指定开始路由的处理器 'root': 'pecanstudy.controllers.root.RootController', 'modules': ['pecanstudy'], 'static_root': '%(confdir)s/public', 'template_path': '%(confdir)s/pecanstudy/templates', 'debug': True, 'errors': { // abort(404)时会转发到该路由 404: '/error/404', '__force_dict__': True } } // python格式的日志配置 logging = { 'root': {'level': 'INFO', 'handlers': ['console']}, 'loggers': { 'pecanstudy': {'level': 'DEBUG', 'handlers': ['console'], 'propagate': False}, 'pecan': {'level': 'DEBUG', 'handlers': ['console'], 'propagate': False}, 'py.warnings': {'handlers': ['console']}, '__force_dict__': True }, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'color' } }, 'formatters': { 'simple': { 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]' '[%(threadName)s] %(message)s') }, 'color': { '()': 'pecan.log.ColorFormatter', 'format': ('%(asctime)s [%(padded_color_levelname)s] [%(name)s]' '[%(threadName)s] %(message)s'), '__force_dict__': True } } }app是一个Pecan实例,先来看看初始化Pecan实例时做了哪些工作
// core.py文件 class Pecan(PecanBase): def __new__(cls, *args, **kw): if kw.get('use_context_locals') is False: self = super(Pecan, cls).__new__(ExplicitPecan, *args, **kw) self.__init__(*args, **kw) return self return super(Pecan, cls).__new__(cls) def __init__(self, *args, **kw): self.init_context_local(kw.get('context_local_factory')) //调用了PecanBase的初始化方法 super(Pecan, self).__init__(*args, **kw) ...... ...... class PecanBase(object): def __init__(self, root, default_renderer='mako', template_path='templates', hooks=lambda: [], custom_renderers={}, extra_template_vars={}, force_canonical=True, guess_content_type_from_ext=True, context_local_factory=None, request_cls=Request, response_cls=Response, **kw): // 这里将配置文件中的root配置项导入为一个python对象 if isinstance(root, six.string_types): root = self.__translate_root__(root) self.root = root // url中的路径,比如:/v1/books self.request_cls = request_cls // webob的Request类,所以pecan以来WebOb self.response_cls = response_cls //webob的Response类 self.renderers = RendererFactory(custom_renderers, extra_template_vars) // 渲染器 self.default_renderer = default_renderer // 初始化钩子程序 if six.callable(hooks): hooks = hooks() self.hooks = list(sorted( hooks, key=operator.attrgetter('priority') )) self.template_path = template_path self.force_canonical = force_canonical self.guess_content_type_from_ext = guess_content_type_from_ext在core.py中定义了一个全局变量state,它的生命周期和整个请求的生命周期一致,保存了请求过程中各种参数状态值 state.hooks:存储钩子程序的list state.request:请求对象 state.response:响应对象 state.controller:处理器(程序中被expose装饰的方法) state.arguments:参数 state.app:Peacn对象
当一个请求从wsgiserver转发过来,首先处理的是Pecan中的__call__方法
// core.py文件 class Pecan(PecanBase): def __call__(self, environ, start_response): try: state.hooks = [] state.app = self state.controller = None state.arguments = None // 调用了PecanBase的__call__方法 return super(Pecan, self).__call__(environ, start_response) finally: del state.hooks del state.request del state.response del state.controller del state.arguments del state.app class PecanBase(object): def __call__(self, environ, start_response): // WebOb的Request和Response req = self.request_cls(environ) resp = self.response_cls() state = RoutingState(req, resp, self) environ['pecan.locals'] = { 'request': req, 'response': resp } controller = None internal_redirect = False try: req.context = environ.get('pecan.recursive.context', {}) req.pecan = dict(content_type=None) // 路由方法,对象分发路由机制,传入state,记录整个过程中的状态 controller, args, kwargs = self.find_controller(state) // 调用处理方法 self.invoke_controller(controller, args, kwargs, state) except Exception as e: ...... ...... self._handle_empty_response_body(state) // 返回结果 return state.response(environ, start_response)主要调用了find_controller和invoke_controller方法。find_controller根据对象分发机制找到url的处理方法,如果没找到,则抛出异常,由后面的except代码块处理,找到了就调用invoke_controller执行该处理方法,将处理结果保存到state中。
class PecanBase(object): def find_controller(self, state): req = state.request pecan_state = req.pecan pecan_state['routing_path'] = path = req.path_info // 处理钩子程序 self.handle_hooks(self.hooks, 'on_route', state) ...... ...... // 具体路由方法 controller, remainder = self.route(req, self.root, path) ...... ...... // 根据路由结果处理参数,如果没有路由到,则该方法会抛出异常 args, varargs, kwargs = self.get_args( state, params.mixed(), remainder, cfg['argspec'], im_self ) state.arguments = Arguments(args, varargs, kwargs) // 处理钩子程序 self.handle_hooks(self.determine_hooks(controller), 'before', state) return controller, args + varargs, kwargs钩子程序分为4种,路由前(on_route),路由后处理前(before),处理后(after),发生错误(on_error),钩子程序可在app.py中自定义,需要继承PecanHook类(在hooks.py中定义)
route(req, self.root, path): req:WebOb的Request对象,存储了请求的信息 self.root:是第一个处理对象(config.py中定义的root对象) path:路径信息,如:/v1/books
def route(self, req, node, path): path = path.split('/')[1:] // 转化后的路径 path:['v1','boos'] try: // 调用了routing.py文件中的lookup_controller方法 node, remainder = lookup_controller(node, path, req) return node, remainder except NonCanonicalPath as e: if self.force_canonical and \ not _cfg(e.controller).get('accept_noncanonical', False): if req.method == 'POST': raise RuntimeError( "You have POSTed to a URL '%s' which " "requires a slash. Most browsers will not maintain " "POST data when redirected. Please update your code " "to POST to '%s/' or set force_canonical to False" % (req.pecan['routing_path'], req.pecan['routing_path']) ) redirect(code=302, add_slash=True, request=req) return e.controller, e.remainder // routing.py文件 def lookup_controller(obj, remainder, request=None): ...... ...... // 存储在obj中未找到处理方法时的_default,_lookup notfound_handlers = [] while True: try: obj, remainder = find_object(obj, remainder, notfound_handlers, request) handle_security(obj) return obj, remainder except (exc.HTTPNotFound, exc.HTTPMethodNotAllowed, PecanNotFound) as e: if isinstance(e, PecanNotFound): e = exc.HTTPNotFound() while notfound_handlers: name, obj, remainder = notfound_handlers.pop() if name == '_default': return obj, remainder else: result = handle_lookup_traversal(obj, remainder) if result: if ( remainder == [''] and len(obj._pecan['argspec'].args) > 1 ): raise e obj_, remainder_ = result return lookup_controller(obj_, remainder_, request) else: raise elookup_controller针对每一个controller对象,在其中查找对应的处理方法,如果没找到,则会继续找_default,如果没定义_default,则找_lookup,然后继续循环调用lookup_controller,直到找到对应的方法,或notfound_handlers 为空抛出异常
obj, remainder = find_object(obj, remainder, notfound_handlers, request)具体查找: obj:当前的controller对象 remainder:路由信息,如[‘v1’, ‘books’] notfound_handlers:该controller中没找到时,存储_default或者_lookup request:请求信息
def find_object(obj, remainder, notfound_handlers, request): prev_obj = None while True: if obj is None: raise PecanNotFound // 如果传入的obj直接时一个处理方法(被expsoe装饰),直接返回 if iscontroller(obj): if getattr(obj, 'custom_route', None) is None: return obj, remainder // 处理自定义路由信息 _detect_custom_path_segments(obj) // 根据自定义路由名找到处理方法 if remainder: custom_route = __custom_routes__.get((obj.__class__, remainder[0])) if custom_route: return getattr(obj, custom_route), remainder[1:] cross_boundary(prev_obj, obj) // 如果根据默认和自定义路由都没找到,则找该controller中的index方法 // 如果有路由:/v1/books//best(不标准的路径),那么就只能路由到/v1/books,后面的就没法路由 try: next_obj, rest = remainder[0], remainder[1:] if next_obj == '': index = getattr(obj, 'index', None) if iscontroller(index): return index, rest except IndexError: index = getattr(obj, 'index', None) if iscontroller(index): raise NonCanonicalPath(index, []) // 存储_default方法到notfound_handlers default = getattr(obj, '_default', None) if iscontroller(default): notfound_handlers.append(('_default', default, remainder)) // 则存储_lookup方法到notfound_handlers lookup = getattr(obj, '_lookup', None) if iscontroller(lookup): notfound_handlers.append(('_lookup', lookup, remainder)) // 根据自定义的_route方法来处理路由(pecan允许开发者在controller中自定义_route方法,让开发者完全掌控路由方式) route = getattr(obj, '_route', None) if iscontroller(route): if len(getargspec(route).args) == 2: warnings.warn( ( "The function signature for %s.%s._route is changing " "in the next version of pecan.\nPlease update to: " "`def _route(self, args, request)`." % ( obj.__class__.__module__, obj.__class__.__name__ ) ), DeprecationWarning ) next_obj, next_remainder = route(remainder) else: next_obj, next_remainder = route(remainder, request) cross_boundary(route, next_obj) return next_obj, next_remainder if not remainder: raise PecanNotFound prev_remainder = remainder prev_obj = obj remainder = rest // 根据方法名(或者属性名)(默认的路由方式)找到处理方法 try: obj = getattr(obj, next_obj, None) except UnicodeEncodeError: obj = None if not obj and not notfound_handlers and hasattr(prev_obj, 'index'): if request.method in _cfg(prev_obj.index).get('generic_handlers', {}): return prev_obj.index, prev_remainderfind_object 首先会处理自定义的路由信息,然后存储_default和_lookup,最后处理默认路由(个人觉得可以先处理默认路由信息,然后根据是否配置route装饰进行取舍,这样可能处理更高效)
routing.py中的lookup_controller 和 find_object是核心路由方式的实现,从代码中可以看出,最终找到处理方法的方式是根据路径(/v1/books)中每一个segment来查找对应的对象,然后根据当前对象再查找下一个对象,所以pecan的路由机制叫做对象分发
下一篇将学习pecan的异常抛出,根据方法路由等内容