PyCurl запрос бесконечно зависает при выполнении

Я написал скрипт для извлечения результатов сканирования из Qualys, который будет запускаться каждую неделю с целью сбора метрик.

Первая часть этого скрипта включает выбор списка ссылок для каждого из сканирований, которые были выполнены на прошлой неделе, для дальнейшей обработки.

Проблема в том, что, хотя иногда это будет работать идеально, в других случаях скрипт будет зависать на c.perform() линия. Это управляемо при запуске сценария вручную, так как его можно просто повторно запустить, пока он не заработает. Тем не менее, я хочу запускать это как запланированное задание каждую неделю без какого-либо ручного взаимодействия.

Есть ли надежный способ определить, что произошло зависание, и повторно отправить запрос PyCurl, пока он не заработает?

Я пытался установить c.TIMEOUT а также c.CONNECTTIMEOUT варианты, но они не кажутся эффективными. Кроме того, поскольку исключение не выбрасывается, простое размещение его в блоке try-Кроме также не будет летать.

Рассматриваемая функция ниже:

# 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!")

1 ответ

Решение

Я сам исправил проблему, запустив отдельный процесс, используя multiprocessing запускать вызов API в отдельном процессе, убивая и перезапуская, если он продолжается дольше 5 секунд. Это не очень красиво, но кроссплатформенно. Для тех, кто ищет более элегантное решение, но работает только на * nix, загляните в библиотеку сигналов, в частности, SIGALRM.

Код ниже:

# 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!")

Вопрос старый, но я добавлю этот ответ, он может кому-то помочь.

единственный способ завершить выполнение curl после выполнения "execute ()" - использовать обратные вызовы:

1 - используя CURLOPT_WRITEFUNCTION: как указано в документации:

Ваш обратный вызов должен возвращать количество фактически обработанных байтов. Если эта сумма отличается от суммы, переданной вашей функции обратного вызова, она сообщит об ошибке в библиотеку. Это приведет к тому, что передача будет прервана, а используемая функция libcurl вернет CURLE_WRITE_ERROR.

Недостатком этого метода является то, что curl вызывает функцию записи только тогда, когда получает новые данные с сервера, поэтому, если сервер перестал отправлять данные, curl просто будет ждать на стороне сервера и не получит ваш сигнал уничтожения.

2- альтернатива и лучший пока что использует обратный вызов прогресса:

прелесть прогресса выполнения в том, что curl будет вызывать его, по крайней мере, один раз в секунду, даже если с сервера не поступает никаких данных, что даст вам возможность вернуть 0 в качестве переключателя уничтожения в curl

используйте опцию CURLOPT_XFERINFOFUNCTION, обратите внимание, что это лучше, чем использование CURLOPT_PROGRESSFUNCTION, как указано в документации:

Мы рекомендуем пользователям использовать более новую CURLOPT_XFERINFOFUNCTION вместо этого, если вы можете.

также вам нужно установить опцию CURLOPT_NOPROGRESS

CURLOPT_NOPROGRESS должен быть установлен в 0, чтобы эта функция действительно вызывалась.

Это пример, демонстрирующий реализацию функций write и progress в python:

# example of using write and progress function to terminate curl
import pycurl

open('mynewfile', 'w') as f  # used to save downloaded data
counter = 0

# define callback functions which will be used by curl
def my_write_func(self, data):
    """write to file"""
    f.write(data)
    counter += len(data)

    # tell curl to abort if the downloaded data exceeded 1024 byte by returning -1 or any number 
    # not equal to len(data) 
    if counter >= 1024:
        return -1

def progress(*data):
    """it receive progress figures from curl"""
    d_size, downloaded, u_size, uploade = data

    # tell curl to abort if the downloaded data exceeded 1024 byte by returning 0 
    if downloaded >= 1024:
        return 0


# initialize curl object and options
c = pycurl.Curl()

# callback options
c.setopt(pycurl.WRITEFUNCTION, my_write_func)

self.c.setopt(pycurl.NOPROGRESS, 0)  # required to use a progress function
self.c.setopt(pycurl.XFERINFOFUNCTION, self.progress) 
# self.c.setopt(pycurl.PROGRESSFUNCTION, self.progress)  # you can use this option but pycurl.XFERINFOFUNCTION is recommended
# put other curl options as required

# executing curl
c.perform()
Другие вопросы по тегам