Здравствуйте.
Впервые в практике столкнулся с необходимостью рекурсивного вызова функции - и встал на этом.
Вкратце:
Скрипт работает с API одной социальной сети.
Есть ф-ция _GetResponseFromdA, ее вызываем когда нужно послать запрос и получить ответ от сервера.
Ответ может содержать
в случае успеха:
запрашиваемые данные в JSON формате. В успешном ответе может содержаться ключ "status" со значением "success", а может и нет.
в случае неудачи:
либо JSON-ответ с обязательным ключом "status" и значением "error". В этом случае обязательно также будет ключ "error" и значением, содержащим тип ошибки.
Либо (что бывает часто, когда сервер перегружен) в ответ придет просто HTML страница:
Требуется:
1. Если получен успешный JSON ответ или JSON ответ с любой ошибкой кроме ошибки типа "user_api_threshold", вернуть ответ из функции.
2. Если получена HTML-страница или JSON ответ с ошибкой типа "user_api_threshold", поспать N секунд, затем вызывать _GetResponseFromdA из нее же самой до выполнения п.1 либо пока не исчерпается число попыток (скажем, 5)
Вот что есть на этот момент. Сходу сложно, наверное, будет разобраться, не придумал как упростить. Надеюсь, комментарии помогут.
Впервые в практике столкнулся с необходимостью рекурсивного вызова функции - и встал на этом.
Вкратце:
Скрипт работает с API одной социальной сети.
Есть ф-ция _GetResponseFromdA, ее вызываем когда нужно послать запрос и получить ответ от сервера.
Ответ может содержать
в случае успеха:
запрашиваемые данные в JSON формате. В успешном ответе может содержаться ключ "status" со значением "success", а может и нет.
в случае неудачи:
либо JSON-ответ с обязательным ключом "status" и значением "error". В этом случае обязательно также будет ключ "error" и значением, содержащим тип ошибки.
Либо (что бывает часто, когда сервер перегружен) в ответ придет просто HTML страница:
Код:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
CloudFront attempted to establish a connection with the origin, but either the attempt failed or the origin closed the connection.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID:
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>
Требуется:
1. Если получен успешный JSON ответ или JSON ответ с любой ошибкой кроме ошибки типа "user_api_threshold", вернуть ответ из функции.
2. Если получена HTML-страница или JSON ответ с ошибкой типа "user_api_threshold", поспать N секунд, затем вызывать _GetResponseFromdA из нее же самой до выполнения п.1 либо пока не исчерпается число попыток (скажем, 5)
Вот что есть на этот момент. Сходу сложно, наверное, будет разобраться, не придумал как упростить. Надеюсь, комментарии помогут.
Код:
$iDelayPeriodWhenLimitExceededCounter = 0
Func _GetResponseFromdA ($sEndPoint, $sParams = "", $sMethod = "GET", $IsUsualPrefix = True)
$sCurlString = Execute (_Pref("sCurlFirstPartOfString")) & ' "' & _Pref("sAPIHost") ; ф-ция _Pref берет настройки из базы данных, конкретно в этой строке формирует первую часть строки параметров для cUrl
;~ If $IsUsualPrefix = True Then $sCurlString &= _Pref("sRequestUsualPrefix")
If $IsUsualPrefix = True Then
$sCurlString &= _Pref("sRequestUsualPrefix") ; дальше формируем строку для cUrl
$iSecondOfCheck = _TimeGetStamp()
If $iSecondOfCheck - _GetInfoFromDataBase ("SELECT refresh_time FROM accounts WHERE id = '" & _Pref("iActiveAccount") & "';") > _Pref("iRefreshTokensPeriodinMin") * 60 Then ; если необходимый для API вызовов access_token устарел (выдан более 55 минут назад), надо его обновить
ConsoleWrite ("надо рефрешить access_token" & @CRLF)
$sParamsToRefreshTokens = "grant_type=refresh_token&client_id=" & _GetInfoFromDataBase ("SELECT client_id FROM accounts WHERE id = " & _Pref("iActiveAccount") & ";") & "&client_secret=" & _GetInfoFromDataBase ("SELECT client_secret FROM accounts WHERE id = " & _Pref("iActiveAccount") & ";") & "&refresh_token=" & _GetInfoFromDataBase ("SELECT refresh_token FROM accounts WHERE id = " & _Pref("iActiveAccount") & ";") ; формируем строку параметров для cUrl для обновления access_token
$sRefreshResponse = _GetResponseFromdA ("token", $sParamsToRefreshTokens, "GET", False) ; обновляю access_token; первый случай рекурсии, с ней вроде справился, работает норм
$sStatusOfRefreshResponse = _GetJSONValue ($sRefreshResponse, "status")
If $sStatusOfRefreshResponse = "error" Then ; если в ответе обновлении access_token ошибка, то выходим
Return -1
ElseIf $sStatusOfRefreshResponse = "success" Then ; иначе пишем в БД новый access_token, refresh_token и время обновления
_GetInfoFromDataBase ("UPDATE accounts SET access_token = " & _SQLite_Escape (_EncryptData(_GetJSONValue ($sRefreshResponse, "access_token"))) & " WHERE id = " & _Pref("iActiveAccount") & ";")
_GetInfoFromDataBase ("UPDATE accounts SET refresh_token = " & _SQLite_Escape (_EncryptData(_GetJSONValue ($sRefreshResponse, "refresh_token"))) & " WHERE id = " & _Pref("iActiveAccount") & ";")
_GetInfoFromDataBase ("UPDATE accounts SET refresh_time = " & _SQLite_Escape($iSecondOfCheck) & " WHERE id = " & _Pref("iActiveAccount") & ";")
EndIf
EndIf
$sParams &= "&access_token=" & _GetInfoFromDataBase ("SELECT access_token FROM accounts WHERE id = '" & _Pref("iActiveAccount") & "';") ; дописываем к строке параметров cUrl рабочий access_token, взятый из БД
EndIf
;~ Дописываем к строке параметров cUrl метод запроса - POST или GET
$sCurlString &= _Pref("sRequestAuthPrefix") & $sEndPoint & '" -d "' & $sParams & '"' & ' -X '
If $sMethod = "GET" Then
$sCurlString &= 'GET -G'
ElseIf $sMethod = "POST" Then
$sCurlString &= 'POST'
EndIf
; А вот это бы как-нибудь красиво зарекурсить
$sAnswerFromdA = _AskdA ($sCurlString)
$sStatusOfAnswer = _GetJSONValue ($sAnswerFromdA, "status")
$sErrorType = ""
If $sStatusOfAnswer = "error" Then $sErrorType = _GetJSONValue ($sStatusOfAnswer, "error")
If StringRegExp ($sAnswerFromdA, "(?ms)^<\!DOCTYPE\sHTML.*<\/HTML>$") Or $sErrorType = "user_api_threshold" Then
_WaitWhenLimitExceeded ()
$sAnswerFromdA = _AskdA ($sCurlString)
Else
$iDelayPeriodWhenLimitExceededCounter = 0
EndIf
; Конец А вот это бы как-нибудь красиво зарекурсить
Return $sAnswerFromdA
EndFunc ; _GetResponseFromdA
Func _AskdA ($sStringForCurl)
; отпрвляю запрос с помощью cUrl
$iPID = Run($sStringForCurl, '', @SW_HIDE, 6)
ProcessWaitClose($iPID)
$sResponseFromdA = StdoutRead($iPID)
;~ Если получена HTML страница или JSON -ответ с ошибкой, выводим MsgBox и пишем в лог
If StringRegExp ($sResponseFromdA, "(?ms)^<\!DOCTYPE\sHTML.*<\/HTML>$") Or _GetJSONValue ($sResponseFromdA, "status") = "error" Then
If _Pref("bShowErrorsFromdAMsgBoxes") = 1 Then MsgBox(16, "DevianArt вернул ошибку:", "Запрос: " & $sStringForCurl & @CRLF & @CRLF & "Ответ: " & $sResponseFromdA)
If _Pref("bWriteErrorsInLog") = 1 Then
$hErrorsLogFile = FileOpen (Execute (_Pref ("sErrorsLog")), 1)
FileWrite ($hErrorsLogFile, "T: " & _Now () & @CRLF & "Q: " & $sStringForCurl & @CRLF & "A: " & $sResponseFromdA & @CRLF & "======================================" & @CRLF)
FileClose ($hErrorsLogFile)
EndIf
EndIf
Return $sResponseFromdA
EndFunc ; _AskdA
Func _WaitWhenLimitExceeded ()
$iDelayPeriodWhenLimitExceededCounter += 1
$iSecondsToSleep = _Pref("iDelayPeriodWhenLimitExceededStart") * _Pref("fpDelayPeriodWhenLimitExceededMultiplier") * $iDelayPeriodWhenLimitExceededCounter
$ifSecondsElapsed = 0
While 1
$iSecondsRemained = Ceiling ($iSecondsToSleep - $ifSecondsElapsed)
;~ TrayTip ("Превышен лимит запросов", "Спим. Осталось " & $iSecondsRemained & " сек.", 2, 2)
Sleep (1000)
$ifSecondsElapsed += 1
If $iSecondsRemained <= 0 Then ExitLoop
WEnd
If $iDelayPeriodWhenLimitExceededCounter >= _Pref("iDelayPeriodWhenLimitExceededMaxCount") Then $iDelayPeriodWhenLimitExceededCounter = 0
;~ TrayTip("", "", 0)
Return
EndFunc ; _WaitWhenLimitExceeded