avatar

目录
小辣椒开发笔记

应用启动流程

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

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

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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对象后,调用该对象即可。(所以说我们的应用程序好像更像是一个中间件?)

路由处理

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

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

python
1
2
3
4
5
6
7
8
9
10
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

异常处理

python
1
2
3
4
5
6
7
8
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

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

python
1
2
3
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对象。

python
1
request = local("request")
python
1
2
3
4
5
6
7
8
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代理。

python
1
2
3
4
5
6
7
8
9
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。

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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存储会话数据的会话接口。

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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类的定义,就是一个字典:

python
1
2
3
4
class Session(dict):
def __init__(self, data, sid):
super(Session, self).__init__(data)
self.sid = sid

文件发送

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

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

参考链接

flask源码解析

文章作者: 3927o
文章链接: http://yoursite.com/%E5%B0%8F%E8%BE%A3%E6%A4%92%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 lin's document

评论