flask 2.2.3
众所周知,flask可以在response中添加Set-Cookie header来把一个加密过的key:value返还给浏览器,并写入cookie中,在expire之前,后面的请求都会带上这个header。这个功能可以做为一种简单、基础的登录状态验证。
但是,在我对着app.py一顿修改,对着flask源文件一顿猛读和猛注释后,突然发现我的测试代码不给我写session cookie了!怎样测试response里都没有Set-Cookie这个header了!
注意,不是不可以通过response.set_cookie()来添加明文cookie,set_cookie的方法正常,而是不能写这种通过app.secret_key加密过的session cookie了。
因为对框架还不熟悉,当时也不知道具体是哪一步判断写cookie的,更不知道从哪里设置断点,然后就新建项目 下载最新flask 3.0, 使用测试代码
发现正常
但是我记得之前用这个2.2.3也是正常的,于是又在同一个项目写个测试app,发现也正常 。
那么就只有一个可能,当前项目的app代码有bug,导致没有正常写session cookie。
后来就是翻来覆去 的看flask源码,大概明白了http请求到来后的处理流程和调用过程
- 大概是从socket上面到达http server,再到wsgi serving
- 从wsgi serving到flask层面 app.__call__, flask层面处理view func, 生成response对象,加工response对象(header, code, after_request func, session cookie, cookies等)
- response对象加工完成后,再回调到wsgi serving, 通过start_response把响应结果(body,headers,status)返还给客户端
最后找到判断是否写session cookie的代码 在 app.py 的 process_response()的最后
process_response() 会先被finalize_response()调用, 在try中:
def finalize_request( self, rv: t.Union[ft.ResponseReturnValue, HTTPException], from_error_handler: bool = False, ) -> Response: response = self.make_response(rv) ## 根据各种各样的返回值,加工转换为response对象 try: response = self.process_response(response) # 生成response对象后,再依次执行after request funcs,再添加cookie request_finished.send(self, response=response) # 发信号 if not from_error_handler: raise self.logger.exception( "Request finalizing failed with an error while handling an error") except Exception as e: pass return response
# cookie: 在执行完after request函数后,通过save_session处理 是否需要写cookie到response对象中
def process_response(self, response: Response) -> Response: ## 倒序 执行所有的after request 函数
ctx = request_ctx._get_current_object() # type: ignore[attr-defined] # 从LocalProxy对象返回一个requestcontext对象
for func in ctx._after_request_functions:
response = self.ensure_sync(func)(response)
### 倒序执行所有的after_request funcs
for name in chain(request.blueprints, (None,)):
if name in self.after_request_funcs:
for func in reversed(self.after_request_funcs[name]):
response = self.ensure_sync(func)(response)
## 保存session,save保存之前会去判断一次
if not self.session_interface.is_null_session(ctx.session):
self.session_interface.save_session(self, ctx.session, response)
return response
判断的条件在flask/sessions里写,条件很简单,基本就是如果你在视图函数手动编辑过session对象内容, 例如 session['mvp'] = 'jordan' 那么就是 judge = True。
def should_set_cookie(self, app: "Flask", session: SessionMixin) -> bool: """ 判断是否需要写session cookie 要在子类中调用 judge = session.modified or ( session.permanent and app.config["SESSION_REFRESH_EACH_REQUEST"] ) return judge
但是通过一行一行调试,我发现 我的程序在process_response中,执行完after request funcs的部分后就不往下了,直接返回finalize_response()中的exception部分了,异常信息是 :
此时我就意识到可能是我的after request funcs中某一个忘记把response重新返还了,
去检查,果不其然,有一个本来打算记录日志的函数,return被注释掉了,导致没有返回值,默认就返回None了,然后就被类型检查捕捉到了,然后process_response异常,提前结束,save_session的部分无法执行,也就写不了session cookie了。
主要还是对框架不熟悉,花了巨量时间来阅读源码,添加笔记,debug。坏处是相当费时间,
好处是对框架更熟悉了,对各种模块和实现加深了印象。
BTW debug第三方框架或者代码时,最好不要用print 否则很可能debug完了 找不到print写在哪里了,想删都删不掉。最好用logger,可以方便找到filename, lineno.