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()