Python Requests Session set-cookie不生效的坑

我们知道 Python Requests库 中的 Session 模块有连接池和会话管理的功能,比如请求一个登录接口后,会自动处理 response 中的 set-cookie,下次再请求时会自动把 cookie 带上。但最近出现了一个诡异的事情,cookie 没有自动带上,导致请求 403。

一开始怀疑是登录接口错误了,没有 set-cookie,但抓包发现 response header 中有 set-cookie,打印请求的 response.cookies 也有需要的 cookie。又怀疑是 set-cookie 的格式不对或者其它问题,但用浏览器实际跑了下流程,发现系统一切正常,那基本就是 requests 库的问题了。

没办法,只能 debug 了,单步调试了几轮,基本了解了 requests 的处理方式,首先把请求参数转变为 Request 对象,然后对使用 prepare_request 对 Request 进行预处理,其中有一步 merge_cookies 的操作(还有各种其它处理),把传入的 cookies 和 self.cookies merge 到 RequestsCookieJar 对象上去,这一步也没啥问题,merged_cookies 变量也是对的。后续将预处理过的请求,通过内置的 http adapter 发送出去。http adapter 底层是通过 urllib3.poolmanager 获取到 urllib3.connectionpool 连接(这里是连接池的核心部分),再通过 conn.urlopen 实际发送请求。虽然跟踪了解到了整个请求逻辑,但最终发出的请求还是没有带上需要的 cookie。

问题定位一度陷入僵局,只能再回顾上面的流程,cookie 肯定就是在 merged_cookies 和 conn.urlopen 之间没的,再仔细观察发现,conn.urlopen 请求参数里面压根没有 cookie 字段。

查阅资料发现,urllib3 的作者说,连接池只处理底层连接,cookie 跟踪等事情应该上层来做。大胆猜测,那 cookie 应该是放在 header 里了,往前捣看看 request.headers 是怎么变动的(此时里面的 Cookie 字段确实不正确)。

再走查代码发现 prepare_request 里面是调用了 PreparedRequest.prepare,其中有一步 prepare_cookies,主要是调用了 cookielib.CookieJar.add_cookie_header 最终将 cookie 放到了 self.headers['Cookie']。但里面有个 request.has_header("Cookie") 的判断,header 中没有 Cookie 字段才会放,不知道为什么这么考虑(最新版 3.8 还是这样),估计是 merge cookie 比较麻烦,但问题确实就出在这里,这次请求之前,Requests Session 已经直接通过 headers['Cookie'] 设置了 cookie。复现代码(修改自官方示例):

虽然找到了问题的原因,但又不好解决,总不能不让直接操作 headers['Cookie'] 吧,先不说无法限制使用者,而且之前的代码已经这样做了,改动量非常之大。不过好在,现在自己用的框架是在 Requests Session 上封装了一层,操作 header 都是调用的统一的 update_headers 方法:

对 headers['Cookie'] 的操作拦截一下,变成对 cookies 的操作:

这样即不用改之前的调用方代码,也防止了后人掉坑。

 

参考资料

https://requests.readthedocs.io/en/master/user/advanced/#session-objects

https://stackoverflow.com/questions/2422922/python-urllib3-and-how-to-handle-cookie-support

https://urllib3.readthedocs.io/en/latest/reference/index.html#module-urllib3.poolmanager

https://urllib3.readthedocs.io/en/latest/reference/index.html#urllib3.connectionpool.HTTPConnectionPool.urlopen

https://docs.python.org/2/library/cookie.html

CC BY-NC-SA 4.0
avatar

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据