小辣椒开发笔记
小辣椒开发笔记
应用启动流程
根据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
方法
好晚了,不写了,咕咕咕。