Django中间件机制

Django中的每一个请求都要通过中间件,先执行所有中间件的process_request函数,返回再执行所有的process_response。

# django中间件
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.local.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware'
]

# 编写自己的中间件
class CommonMiddleware(object):
    def process_request(self, request):
        return None

    def process_response(self, request, response):
        return response

    # process_view 等等

# 部分源码
class BaseHandler(object):
    def load_middleware(self):
        if settings.MIDDLEWARE is None:
            warnings.warn(
                "Old-style middleware using settings.MIDDLEWARE_CLASSES is "
                "deprecated. Update your middleware and use settings.MIDDLEWARE "
                "instead.", RemovedInDjango20Warning
            )
            handler = convert_exception_to_response(self._legacy_get_response)
            for middleware_path in settings.MIDDLEWARE_CLASSES: # 获取设置中的中间件, for循环是有顺序的
                mw_class = import_string(middleware_path) # 得到中间件
                try:
                    mw_instance = mw_class() # 中间件实例
                except MiddlewareNotUsed as exc:
                    if settings.DEBUG:
                        if six.text_type(exc):
                            logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
                        else:
                            logger.debug('MiddlewareNotUsed: %r', middleware_path)
                    continue

                if hasattr(mw_instance, 'process_request'):
                    self._request_middleware.append(mw_instance.process_request) # 先处理process_request, 是按设置的从上到下顺序的
                if hasattr(mw_instance, 'process_view'):
                    self._view_middleware.append(mw_instance.process_view)
                if hasattr(mw_instance, 'process_template_response'):
                    self._template_response_middleware.insert(0, mw_instance.process_template_response)
                if hasattr(mw_instance, 'process_response'):
                    self._response_middleware.insert(0, mw_instance.process_response)
                if hasattr(mw_instance, 'process_exception'):
                    self._exception_middleware.insert(0, mw_instance.process_exception)

Django/social-auth管道机制

管道机制在linux是指由一个进程的连接数据流向另一个进程,属于进程通信的一种。 这里的django管道机制,是指由一个函数的数据流向一个函数,类管道机制。 本文以python-social-auth为例。在social-auth中,我们可以在管道中随意添加函数, 以满足我们的需求,其特点是前一个函数的返回值(定义为字典数据类型), 作为后一个函数的参数,直到执行到最后,管道执行完成。

SOCIAL_AUTH_PIPELINE: (  # socia_auth 默认管道设置
    'social_core.pipeline.social_auth.social_details',
    'social_core.pipeline.social_auth.social_uid',
    'social_core.pipeline.social_auth.auth_allowed',
    'social_core.tests.pipeline.ask_for_password',
    'social_core.pipeline.social_auth.social_user',
    'social_core.pipeline.user.get_username',
    'social_core.pipeline.user.create_user',
    'social_core.pipeline.social_auth.associate_user',
    'social_core.pipeline.social_auth.load_extra_data',
    'social_core.tests.pipeline.set_password',
    'social_core.pipeline.user.user_details'
)

这里以微信授权登录(token)为例,理解一下登录过程。

  • 首先post请求thirdparty-auth/social/token_user/ 参数provider,code, 授权登录
  • 通过code换取用户信息,通过auth_comlete()函数保存access_token和用户信息
  • 通过authenticate()函数开始管道之旅

    # social_core/backends/base.py
    def authenticate(self, *args, **kwargs):
    """Authenticate user using social credentials
    
    Authentication is made if this is the correct backend, backend
    verification is made by kwargs inspection for current backend
    name presence.
    """
    # Validate backend and arguments. Require that the Social Auth
    # response be passed in as a keyword argument, to make sure we
    # don't match the username/password calling conventions of
    # authenticate.
    if 'backend' not in kwargs or kwargs['backend'].name != self.name or \
       'strategy' not in kwargs or 'response' not in kwargs:
        return None
    
    self.strategy = kwargs.get('strategy') or self.strategy
    self.redirect_uri = kwargs.get('redirect_uri') or self.redirect_uri
    self.data = self.strategy.request_data()
    kwargs.setdefault('is_new', False) # 默认设is_new为False
    pipeline = self.strategy.get_pipeline(self) # 得到上面设置的管道
    args, kwargs = self.strategy.clean_authenticate_args(*args, **kwargs)
    return self.pipeline(pipeline, *args, **kwargs) # 开始管道
    
    def pipeline(self, pipeline, pipeline_index=0, *args, **kwargs):
    out = self.run_pipeline(pipeline, pipeline_index, *args, **kwargs) # 执行管道里的函数
    if not isinstance(out, dict):
        return out
    user = out.get('user')
    if user:
        user.social_user = out.get('social') # 额外的两个参数
        user.is_new = out.get('is_new')
    return user # 返回user
    
    def run_pipeline(self, pipeline, pipeline_index=0, *args, **kwargs): # 核心
    out = kwargs.copy()
    out.setdefault('strategy', self.strategy)
    out.setdefault('backend', out.pop(self.name, None) or self)
    out.setdefault('request', self.strategy.request_data())
    out.setdefault('details', {})
    
    if not isinstance(pipeline_index, int) or \
       pipeline_index < 0 or \
       pipeline_index >= len(pipeline):
        pipeline_index = 0
    
    for idx, name in enumerate(pipeline[pipeline_index:]): # 管道机制原来是一个for循环
        out['pipeline_index'] = pipeline_index + idx
        func = module_member(name) # 取出函数
        result = func(*args, **out) or {} # 执行函数
        if not isinstance(result, dict): # 不为字典返回
            return result
        out.update(result) # 这是最关键的一步,它把返回更新到out,供下一次循环里函数的参数
    return out
    
    def module_member(name):
    mod, member = name.rsplit('.', 1) # 以.分割,返回两个
    module = import_module(mod) # 导入模块
    return getattr(module, member) # 返回函数
    
    # 我们随便找两个函数看看
    def social_details(backend, details, response, *args, **kwargs):
    return {'details': dict(backend.get_user_details(response), **details)}
    
    def social_uid(backend, details, response, *args, **kwargs):
    return {'uid': backend.get_user_id(details, response)}
    
    # result = func(*args, **out) 先执行social_details, 然后返回字典里包含details, 这个detail就作为第二次循环执行social_uid里参数的details
    

参考

https://docs.djangoproject.com/en/2.1/topics/http/middleware/
https://python-social-auth-docs.readthedocs.io/en/latest/