Python异步编程-发送并忘记的HTTP请求

18

不确定是否可行。我想从脚本中发送HTTP POST请求,但不等待响应。相反,我希望立即返回。

我尝试了以下类似的方法:

#!/usr/bin/env python3

import asyncio
import aiohttp

async def fire():
    await client.post('http://httpresponder.com/xyz')

async def main():
    asyncio.ensure_future(fire())

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    client = aiohttp.ClientSession(loop=loop)
    loop.run_until_complete(main())

脚本返回时没有出错,但HTTP请求从未到达目的地。我可以发出POST请求,但不等待服务器响应,只要发送请求就立即终止吗?

脚本执行时没有出现错误,但是HTTP请求没有成功到达目的地。您是否可以发送POST请求后立即终止,而不必等待服务器响应?


你尝试过使用requests库的stream参数吗?这样可以避免下载正文内容,但仍然会等待服务器响应。 - André Roggeri Campos
请查看我的回答,我认为它应该被接受 ;) https://dev59.com/qVQK5IYBdhLWcg3wEb17#73967795 - Marquinho Peli
4个回答

14
使用 asyncio, 您可以编写一个简单的装饰器 @background. 现在您可以在 foo() 中编写任何内容,控制流程不会等待其完成。
import asyncio
import time


def background(f):
    from functools import wraps
    @wraps(f)
    def wrapped(*args, **kwargs):
        loop = asyncio.get_event_loop()
        if callable(f):
            return loop.run_in_executor(None, f, *args, **kwargs)
        else:
            raise TypeError('Task must be a callable')    
    return wrapped


@background
def foo():
    time.sleep(1)
    print("foo() completed")


print("Hello")
foo()
print("I didn't wait for foo()")

生成:

Hello
I didn't wait for foo()
foo() completed

注意:自版本3.10起已弃用:如果没有正在运行的事件循环,则会发出弃用警告。在未来的Python版本中,此函数将成为get_running_loop()的别名。https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.get_event_loop - ruohola
使用Python的ratelimit模块,是否可以在此基础上添加一些速率限制机制? - Sudharshann D
1
@SudharshannD 它应该能够与其他装饰器很好地配合使用。尝试将这些装饰器(@sleep_and_retry 和 @limits)添加到上面的 wrapped 函数所在行。 - nehem

2
我已经回答过一个相似的问题。
async def main():
    asyncio.ensure_future(fire())

`ensure_future`函数安排协程执行,但不会等待其完成,而`run_until_complete`函数也不会等待所有future完成。
这个问题可以这样解决:
async def main():
    await fire()

嗨,这份代码完成了任务。但是,脚本会按照预期发送请求,然后立即终止并显示以下消息: Unclosed client session client_session: <aiohttp.client.ClientSession object at 0x7fa486dc6550> Unclosed connector connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x7fa481475828>, 20136.829498141)]'] connector: <aiohttp.connector.TCPConnector object at 0x7fa48527bdd8> 该脚本的退出代码仍为 0(没有错误) - luqo33
你好!你应该使用会话 async with aiohttp.ClientSession() as session: session.post(...)。详情请参见文档 - Artemij Rodionov
错误已经消失了。然而,我认为这个设置实际上并没有实现最初的意图(一旦请求被发送就终止脚本)。目前,只要客户端会话在上下文保护内打开,主脚本就会执行。只有当响应到达时(await response),客户端会话才会关闭。我最终使用了多进程来实现我所需的功能。当然,asyncio和aiohttp非常适合异步执行多个网络调用,但它们似乎无法“发射并忘记”。 - luqo33

1
我注意到其他人提供的解决方案并没有满足您最初的问题,因此我为您想出了一个简单且同步的解决方案,无需花哨的多线程。可以使用Python内置的低级库,如socket来解决这个问题。思路是发送get请求,不关心对方是否返回200或400+,从而使我们真正地发射和忘记,不浪费任何宝贵的时间,也不需要等待任何东西。这里是完整的代码,展示了其有用性和实现 总体思路是:
write_only_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
write_only_client.connect((target_host, target_port))
write_only_client.send(request.encode())
write_only_client.close()

请注意,我们没有进行任何write_only_client.recv调用,这正是为什么我们能够真正的“点火并忘记(fire and forget)”。
P.S. 虽然这在我的代码示例中没有实践,但最好保持套接字开放,并将其作为全局变量,以避免在作用域更改期间垃圾回收,因为过早关闭套接字可能会导致问题(当我关闭与API的SSL连接时确实会出现问题)。

嗨,我按照你的建议去做了,在我的使用情况下这是最有意义的。然而,我有一个 SSL 连接,但我的请求消息从未被接收到。我没有像你提到的那样关闭连接;还有其他可能遗漏的地方吗?我想指出,我的 GET 端点在终点上有换行符(\n),因为我正在构造带参数的消息,类似于:/message?text="example \n\n example" - ISimion
请尝试一次发送多条消息,问题还存在吗?其他消息可以是胡言乱语。 - undefined
如果多次发送请求也不起作用,确保服务器在请求结束时期望接收到你发送的内容(Linux系统使用LF,其他系统使用CRLF)@ISimion - undefined

0

这段代码有点啰嗦,但你可以使用标准库 http.client.HTTPSConnection:

import http.client, json
from datetime import datetime

d = datetime.now()
conn = http.client.HTTPSConnection("medium.com")

#not needed for this call, but you may need to send data on your calls
payload = json.dumps({"Some": [{"json": ["string"]}]})

#not needed for this call, but you may need to send headers on your calls
headers = {
  'Content-Type': 'application/json'
}

# add querystring params here if needed
urlPath = "/towards-data-science/current-time-python-4417c0f3bc4f"

# do the call
conn.request("GET", urlPath, payload, headers)
print('Needed time to do the call: ', datetime.now() - d)
d = datetime.now()

#You can remove code from this point on and call is still made
res = conn.getresponse()
print('Time getting response (could skip): ', datetime.now() - d)
d = datetime.now()

data = res.read()
print('Time to read: ', datetime.now() - d)
# print(data.decode("utf-8"))

>>> Needed time to do the call:  0:00:00.180057
>>> Time getting response (could skip):  0:00:00.646882
>>> Time to read:  0:00:00.001001

祝你好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接