小辣椒开发笔记

小辣椒开发笔记

应用启动流程

根据WSGI协议,应用程序会接收到web server提供的environ与start_response两个参数,我们只需要从environ参数中提取相关信息并返回一个可迭代对象即可。

flask对该流程的处理逻辑如下:

def wsgi_app(self, environ, start_response):
    
    #根据提供的environ创建请求上下文对象,并入栈
    ctx = self.request_context(environ)
    ctx.push()
    error = None

    try:
        # 正确的请求处理路径,会通过路由找到对应的处理函数
        response = self.full_dispatch_request()
    except Exception as e:
        # 错误处理,默认是 InternalServerError 错误处理函数,客户端会看到服务器 500 异常
        error = e
        response = self.handle_exception(e)
    return response(environ, start_response)

首先对出入的environ参数创建一个RequestContext请求上下文对象,该对象包含着HTTP请求所含有的所有信息。之后请求上下文建立成功后,会执行response = self.full_dispatch_request(),该语句会根据请求中的路由找到相应的处理函数来处理请求,并将该函数的返回值处理后生成response对象返回。在处理请求的过程中,若遇到了异常,则该异常会被捕获并交给handle_exception(e)函数处理,该函数接受一个异常对象并返回一个response对象。之后获取response对象后,调用该对象即可。(所以说我们的应用程序好像更像是一个中间件?)

路由处理

    def dispatch_request(self):
        rule = request.url_rule
        return self.view_functions[rule.endpoint](**request.view_args)

由路由到函数的映射关系为路由->端点->函数,在werkzeug库中已经提供了一系列的类供我们处理路由与端点的关系,而我们需要做的只是处理端点到函数的映射关系,该关系用一个字典即可轻松搞定。由代码可知我们会在生成request对象的时候根据路由获取对应的端点,在处理请求的时候我们只需根据端点调用相应函数即可。而对于端点与函数的映射关系,我们提供了一个简单的函数用于注册:

    def add_url_rule(self, path, view_func, endpoint=None, methods=None):
        if endpoint is None:
            endpoint = view_func.__name__

        if methods is None:
            methods = getattr(view_func, "methods", None) or ("GET",)

        rule = Rule(path, endpoint=endpoint, methods=methods)
        self.url_map.add(rule)
        self.view_functions[endpoint] = view_func

异常处理

    def handler_exceptions(self, e):
        exc_type = type(e)

        if exc_type in self.handler_map:
            handler = self.handler_map.get(exc_type)
            return handler(e)
        else:
            raise e

请求处理过程中触发异常的话,异常会被捕获并交给该函数处理,该函数接受一个异常后,根据异常所属的类查看该异常的处理方式是否被注册,若已被注册,则将该异常抛给注册的错误处理器处理,并返回响应。若未被注册,则将异常再次抛出,触发程序错误。用于注册错误处理器的函数如下:

    def register_error_handler(self, exc_class_or_code, handler):
        exc_class = _find_exceptions(exc_class_or_code)
        self.handler_map[exc_class] = handler

Request对象

由于request对象的特殊性,它必须是动态的,因为在多线程或者多协程的情况下,每个线程或者协程获取的都应该是自己独特的对象,不会互相干扰。flask对此的解决方法是采用类似多线程中threading.local的方法,采用local代理后的变量,在每一个线程中都有其独立的值。因此我们也采用该方法,在用werkzeug库的Request类生成Request对象后,local代理request对象。

request = local("request")
def wsgi_app(self, environ, start_response):

    local.request = Request(environ, self)

    resp = self.full_dispatch_request()
    iterable = resp(environ, start_response)

    return iterable

Global对象

request中的global对象为一次请求中的全局对象,应用开发者可以在该对象中存储任意值以便对请求的处理。由于Global对象的这个特征,可知它也应该是在每一个线程都是独立的,因此也应该采用local代理。

class Global(dict):
    def __setattr__(self, name, value):
        self[name] = value

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError("'Global' instance g has no attribute'{}'".format(name))

Session

flask将会话中的信息存储在cookie中,鉴于这个原因session中不适合存储一些比较敏感的信息,因此小辣椒在cookie中存储会话id,同时将session信息存储在数据库中。根据flask对session的处理,我们会给应用建立一个session接口,用于获取,保存session。

class SessionInterface:

    serializer = pickle
    session_class = Session

    def __init__(self, app):
        self.key_prefix = app.session_key_prefix
        self.session_cookie_name = app.session_cookie_name

    def open_session(self, app, request):
        sid = request.cookies.get(app.session_cookie_name, None)
        data = self.get_data(sid)
        return self.session_class(data, sid)

    def save_session(self, session, response, session_lifetime=60 * 10):
        self.save_to_db(session, session_lifetime)
        response.set_cookie(self.session_cookie_name, str(session.sid), max_age=session_lifetime)

    def get_data(self, sid):
        # return a dict
        raise NotImplementedError()

    def save_to_db(self, session, session_lifetime):
        raise NotImplementedError()

该接口实现了open_session以及save_session方法,open_session方法重cookie中获取会话的sid,之后调用get_data方法从数据库中获取session的数据,之后将数据转化为session对象并返回。save_session方法调用save_to_db方法将session数据存储到数据库中,之后对客户端的cookie信息进行重置。其中get_data方法与save_to_db方法需要开发者根据存储会话信息的数据库进行定制。小辣椒内置实现了采用Redis存储会话数据的会话接口。

class RedisSessionInterface(SessionInterface):

    def __init__(self, app):
        super(RedisSessionInterface, self).__init__(app)
        self.redis = get_redis(app)

    def get_data(self, sid):
        val = self.redis.get(self.key_prefix+str(sid))
        if val is not None:
            return self.serializer.loads(val)

    def save_to_db(self, session, session_lifetime=60 * 10):
        val = self.serializer.dumps(dict(session))
        self.redis.setex(name=self.key_prefix+str(session.sid), value=val, time=session_lifetime)

以下是Session类的定义,就是一个字典:

class Session(dict):
    def __init__(self, data, sid):
        super(Session, self).__init__(data)
        self.sid = sid

文件发送

直接借用的werkzeug库中的wrap_file方法

好晚了,不写了,咕咕咕。

参考链接

flask源码解析

posted @ 2020-05-31 23:53  3927  阅读(299)  评论(0编辑  收藏  举报