python requests session刷新_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 字段。

1

2

3

4

5

6

7

8

9

10

11

12

13 # :param request: The :class:`PreparedRequest ` being sent.

resp=conn.urlopen(

method=request.method,

url=url,

body=request.body,

headers=request.headers,

redirect=False,

assert_same_host=False,

preload_content=False,

decode_content=False,

retries=self.max_retries,

timeout=timeout

)

查阅资料发现,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。复现代码(修改自官方示例):

1

2

3

4

5

6

7

8

9

10 importrequests

s=requests.Session()

s.headers.update({

'Cookie':'k=v'

})

s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')

r=s.get('https://httpbin.org/cookies')

print(r.text)

# {"cookies":{"k":"v"}}

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

1

2

3

4

5

6 defupdate_headers(self,headers):

"""

更新当前会话的header

:param headers: header字典

"""

self.headers.update(headers)

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15 defupdate_headers(self,headers):

"""

更新当前会话的header

:param headers: header字典

"""

forheader_key,header_valueinheaders.items:

ifheader_key=='Cookie'orheader_key=='cookie':

c=Cookie.SimpleCookie()

c.load(header_value)

cookies={}

forkey,morselinc.items():

cookies[key]=morsel.value

requests.utils.add_dict_to_cookiejar(self.cookies,cookies)

delheaders[header_key]

self.headers.update(headers)

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

参考资料

你可能感兴趣的:(python,requests,session刷新)