Python asyncio aiohttp 超时问题

9
注意事项:这是我的第一次尝试使用asyncio,所以可能做了一些非常愚蠢的事情。
场景如下:
我需要“http-ping”一个巨大的url列表,以检查它们是否响应200或任何其他值。 尽管像gobuster之类的工具报告200,403等,但每个请求都会超时。
我的代码类似于以下内容:
import asyncio,aiohttp
import datetime 
#-------------------------------------------------------------------------------------
async def get_data_coroutine(session,url,follow_redirects,timeout_seconds,retries):
    #print('#DEBUG '+datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+' '+url)
    try:
        async with session.get(url,allow_redirects=False,timeout=timeout_seconds) as response:
            status  =   response.status
            #res     =   await response.text()
            if(  status==404):
                pass
            elif(300<=status and status<400):
                location = str(response).split("Location': \'")[1].split("\'")[0]
                print('#HIT   '+datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+' '+str(status)+' '+url+' ---> '+location)
                if(follow_redirects==True):
                    return await get_data_coroutine(session,location,follow_redirects,timeout_seconds,retries)
            else:
                print('#HIT   '+datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+' '+str(status)+' '+url)
            return None
    except asyncio.exceptions.TimeoutError as e:
        print('#ERROR '+datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+' '+'   '+' '+url+' TIMEOUT '+str(e))
        return None
#---------------------------------------------------------------------------    
async def main(loop):
        base_url                =   'http://192.168.59.37'
        extensions              =   ['','.html','php']
        fd                      =   open('/usr/share/wordlists/dirb/common.txt','r')
        words_without_suffix    =   [x.strip() for x in fd.readlines()]#[-5:] #DEBUG!
        words_with_suffix       =   [base_url+'/'+x+y for x in words_without_suffix for y in extensions]
        follow                  =   True
        total_timeout           =   aiohttp.ClientTimeout(total=60*60*24)
        timeout_seconds         =   10
        retries                 =   1
        async with aiohttp.ClientSession(loop=loop,timeout=total_timeout) as session:
            tasks = [get_data_coroutine(session,url,follow,timeout_seconds,retries) for url in words_with_suffix]
            await asyncio.gather(*tasks)
        print('DONE')
#---------------------------------------------------------------------------    
if(__name__=='__main__'):
    loop    =   asyncio.get_event_loop()
    result  =   loop.run_until_complete(main(loop))
   

我做错了什么吗?

有什么建议吗?

非常感谢!


无法重现,当我使用 urls = ['https://www.google.com', 'https://www.yahoo.com'] 运行您的代码时,每个 URL 都返回 200。您在 URL 中指定了 https:// 吗? - Andrej Kesely
是的,我确实指定了协议。抱歉,我复制代码片段时出错了。已经修改过了。 - glezo
你使用代理吗?你确定这些URL是有效的吗?先尝试一下你知道存在的地址。 - Andrej Kesely
不使用代理或基本HTTP身份验证。URL是活动的,并且可以通过使用不同的工具(如gobuster、dirsearch.py甚至dirb)响应200/403。 - glezo
你确定混合中没有代理吗?在创建会话时指定 trust_env=True 是否有帮助? - user4815162342
显示剩余2条评论
2个回答

21

实际上,我最终在aio-libs / aiohttp中找到了一个开放问题:https://github.com/aio-libs/aiohttp/issues/3203

这样一来,他们建议一个解决方法,满足我的需求:

session_timeout =   aiohttp.ClientTimeout(total=None,sock_connect=timeout_seconds,sock_read=timeout_seconds)
async with aiohttp.ClientSession(timeout=session_timeout) as session:
    async with session.get(url,allow_redirects=False,timeout=1) as response:
       ...


3
回答你的问题 - 不,你没有做错任何事情。就HTTP请求/响应/超时处理而言,我看不出你的代码有什么问题。
如果确实所有的请求都超时到主机(http://192.168.59.37),我怀疑你遇到的问题很可能是由于你的网络如何解析请求(或者你的代码如何构建URL)导致的。
你可以使用像curl这样的工具来确认请求是否独立成功/失败,例如:
curl "http://192.168.59.37/abc.html"

我通过本地使用进行了测试。
python3 -m http.server 8080

将空文件“abc”和“abc.html”放置在同一目录中,并更新base_url。
base_url = "http://127.0.0.1:8080"

通过我的小改动(下面的代码),这是输出结果。
http://127.0.0.1:8080/.bashrc.php
#404
http://127.0.0.1:8080/.bashrc
#404
http://127.0.0.1:8080/.bashrc.html
#404
http://127.0.0.1:8080/abc
#HIT   2020-11-03 12:57:33 200  http://127.0.0.1:8080/abc
http://127.0.0.1:8080/zt.php
#404
http://127.0.0.1:8080/zt.html
#404
http://127.0.0.1:8080/zt
#404
http://127.0.0.1:8080/abc.html
#HIT   2020-11-03 12:57:33 200  http://127.0.0.1:8080/abc.html
http://127.0.0.1:8080/abc.php
#404
DONE

我的更新大多是小改动,但可能有助于进一步调试。
  • 为了调试,请打印url。确定代码是否正确构建url很重要。这使我意识到'php'扩展名缺少一个“.”,因此它将寻找abcphp而不是abc.php
  • 使用response.ok测试成功的http响应,你的代码没有处理500错误(而是返回hit)。
  • 使用python f-string进行更清晰的格式化。
import asyncio
import aiohttp
import datetime


async def get_data_coroutine(session, url, follow_redirects, timeout_seconds, retries):
    try:
        async with session.get(
            url, allow_redirects=False, timeout=timeout_seconds
        ) as response:
            print(url)
            now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            if response.ok:
                print(f"#HIT   {now} {response.status}  {url}")
            else:
                status = response.status
                if status == 404:
                    print("#404")
                elif 300 <= status and status < 400:
                    location = str(response).split("Location': '")[1].split("'")[0]
                    print(f"#HIT   {now}  {status} {url} ---> {location}")
                    if follow_redirects is True:
                        return await get_data_coroutine(
                            session, location, follow_redirects, timeout_seconds, retries
                        )
                else:
                    print("#ERROR ", response.status)
            return None
    except asyncio.TimeoutError as e:
        now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        print(f"#ERROR   {now} {url} TIMEOUT ", e)
        return None


async def main(loop):
    base_url = "http://127.0.0.1:8080"
    extensions = ["", ".html", ".php"]
    fd = open("/usr/share/wordlists/dirb/common.txt", "r")
    words_without_suffix = [x.strip() for x in fd.readlines()]
    words_with_suffix = [
        base_url + "/" + x + y for x in words_without_suffix for y in extensions
    ]
    follow = True
    total_timeout = aiohttp.ClientTimeout(total=60 * 60 * 24)
    timeout_seconds = 10
    retries = 1
    async with aiohttp.ClientSession(loop=loop, timeout=total_timeout) as session:
        tasks = [
            get_data_coroutine(session, url, follow, timeout_seconds, retries)
            for url in words_with_suffix
        ]
        await asyncio.gather(*tasks)
    print("DONE")


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    result = loop.run_until_complete(main(loop))

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