pecan源码阅读(二)

config文件

新建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 e

lookup_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_remainder

find_object 首先会处理自定义的路由信息,然后存储_default和_lookup,最后处理默认路由(个人觉得可以先处理默认路由信息,然后根据是否配置route装饰进行取舍,这样可能处理更高效)

routing.py中的lookup_controller 和 find_object是核心路由方式的实现,从代码中可以看出,最终找到处理方法的方式是根据路径(/v1/books)中每一个segment来查找对应的对象,然后根据当前对象再查找下一个对象,所以pecan的路由机制叫做对象分发

下一篇将学习pecan的异常抛出,根据方法路由等内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值