pythonapifreezepycurlqualys

PyCurl request hangs infinitely on perform


I have written a script to fetch scan results from Qualys to be run each week for the purpose of metrics gathering.

The first part of this script involves fetching a list of references for each of the scans that were run in the past week for further processing.

The problem is that, while this will work perfectly sometimes, other times the script will hang on the c.perform() line. This is manageable when running the script manually as it can just be re-run until it works. However, I am looking to run this as a scheduled task each week without any manual interaction.

Is there a foolproof way that I can detect if a hang has occurred and resend the PyCurl request until it works?

I have tried setting the c.TIMEOUT and c.CONNECTTIMEOUT options but these don't seem to be effective. Also, as no exception is thrown, simply putting it in a try-except block also won't fly.

The function in question is below:

# Retrieve a list of all scans conducted in the past week
# Save this to refs_raw.txt
def getScanRefs(usr, pwd):

    print("getting scan references...")

    with open('refs_raw.txt','wb') as refsraw: 
        today = DT.date.today()
        week_ago = today - DT.timedelta(days=7)
        strtoday = str(today)
        strweek_ago = str(week_ago)

        c = pycurl.Curl()

        c.setopt(c.URL, 'https://qualysapi.qualys.eu/api/2.0/fo/scan/?action=list&launched_after_datetime=' + strweek_ago + '&launched_before_datetime=' + strtoday)
        c.setopt(c.HTTPHEADER, ['X-Requested-With: pycurl', 'Content-Type: text/xml'])
        c.setopt(c.USERPWD, usr + ':' + pwd)
        c.setopt(c.POST, 1)
        c.setopt(c.PROXY, 'companyproxy.net:8080')
        c.setopt(c.CAINFO, certifi.where())
        c.setopt(c.SSL_VERIFYPEER, 0)
        c.setopt(c.SSL_VERIFYHOST, 0)
        c.setopt(c.CONNECTTIMEOUT, 3)
        c.setopt(c.TIMEOUT, 3)

        refsbuffer = BytesIO()
        c.setopt(c.WRITEDATA, refsbuffer)
        c.perform()

        body = refsbuffer.getvalue()
        refsraw.write(body)
        c.close()

    print("Got em!")

Solution

  • I fixed the issue myself by launching a separate process using multiprocessing to launch the API call in a separate process, killing and restarting if it goes on for longer than 5 seconds. It's not very pretty but is cross-platform. For those looking for a solution that is more elegant but only works on *nix look into the signal library, specifically SIGALRM.

    Code below:

    # As this request for scan references sometimes hangs it will be run in a separate thread here
    # This will be terminated and relaunched if no response is received within 5 seconds
    def performRequest(usr, pwd):
        today = DT.date.today()
        week_ago = today - DT.timedelta(days=7)
        strtoday = str(today)
        strweek_ago = str(week_ago)
    
        c = pycurl.Curl()
    
        c.setopt(c.URL, 'https://qualysapi.qualys.eu/api/2.0/fo/scan/?action=list&launched_after_datetime=' + strweek_ago + '&launched_before_datetime=' + strtoday)
        c.setopt(c.HTTPHEADER, ['X-Requested-With: pycurl', 'Content-Type: text/xml'])
        c.setopt(c.USERPWD, usr + ':' + pwd)
        c.setopt(c.POST, 1)
        c.setopt(c.PROXY, 'companyproxy.net:8080')
        c.setopt(c.CAINFO, certifi.where())
        c.setopt(c.SSL_VERIFYPEER, 0)
        c.setopt(c.SSL_VERIFYHOST, 0)
    
        refsBuffer = BytesIO()
        c.setopt(c.WRITEDATA, refsBuffer)
        c.perform()
        c.close()
        body = refsBuffer.getvalue()
        refsraw = open('refs_raw.txt', 'wb')
        refsraw.write(body)
        refsraw.close()
    
    # Retrieve a list of all scans conducted in the past week
    # Save this to refs_raw.txt
    def getScanRefs(usr, pwd):
    
        print("Getting scan references...") 
    
        # Occasionally the request will hang infinitely. Launch in separate method and retry if no response in 5 seconds
        success = False
        while success != True:
            sendRequest = multiprocessing.Process(target=performRequest, args=(usr, pwd))
            sendRequest.start()
    
            for seconds in range(5):
                print("...")
                time.sleep(1)
    
            if sendRequest.is_alive():
                print("Maximum allocated time reached... Resending request")
                sendRequest.terminate()
                del sendRequest
            else:
                success = True
    
        print("Got em!")