我使用 aiohttp 来发出 post 请求。我需要在发送请求之前获取post请求中准备好的数据。所以我尝试在 aiohttp 中找到一种类似于 requests 库中的方法 prepare 的方法。可以吗?
这只是 aiohttp 中的一个概念验证解决方法,用于在发送请求之前获取请求正文,并同时执行操作(例如通常签名和添加签名标头)。
免责声明:我不建议在生产环境(或严肃的场合)使用它。因为它依赖于 aiohttp 中非公开且未记录的函数或属性。如果 aiohttp 的内部实现发生变化,它可能随时崩溃。
import aiohttp
import asyncio
import hashlib
import hmac
SECRET_KEY = '__SECRET_KEY__'
class HMACBodySignClientRequest(aiohttp.ClientRequest):
def update_body_from_data(self, body) -> None:
"""bad: hook this internal method to get body and inject signature (may fail anytime if internal changes)"""
super().update_body_from_data(body)
if body is None: # use `body` rather than `self.body` is expected.
return
body = self.body._value or b'' # bad: access to protected property (may not available)
signature = hmac.new(SECRET_KEY.encode('utf-8'), body, digestmod=hashlib.sha256)
self.headers['X-Signature'] = signature.hexdigest()
async def aiohttp_body_sign_workaround():
url = 'https://httpbin.org/api'
data = {'foo': 'bar'}
async with aiohttp.ClientSession(
request_class=HMACBodySignClientRequest) as session: # <-- auto add signature via `request_class`
async with session.post(url=url, json=data) as response:
print(response.request_info)
asyncio.run(aiohttp_body_sign_workaround())
在aiohttp 3.8.5上测试,在任何其他版本中可能会失败。
请注意,这种方法是一种“黑魔法”,可能仅在某些微不足道的情况下有效,例如小数据或非流请求。使用需要您自担风险。
在aiohttp当前的内部设计中,
_request_class: ClientRequest
上有一个内部属性ClientSession
,它委托了实际的请求生成。
在某些时候,请求正文字节可能可以获取。
发现在调用
ClientRequest
方法update_body_from_data()
之后,访问self.body._value
可能会泄露主体字节。
幸运的是,在使用
_request_class
参数初始化 ClientSession
时可以指定 request_class
属性。这意味着我们可以子类 ClientRequest
并重写/挂钩 update_body_from_data()
方法来添加自定义逻辑(在本例中,将主体的 HMAC 签名添加到标头)。
是的,它依赖于内部细节,所以正如我所说,这只是一个概念验证。