Что нового

[Сеть, интернет] Рекурсивный вызов функции запроса ответа от сервера через API

Latoid

Знающий
Сообщения
95
Репутация
11
Здравствуйте.
Впервые в практике столкнулся с необходимостью рекурсивного вызова функции - и встал на этом.
Вкратце:
Скрипт работает с 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
 

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
Сделайте цикл от 1 до 5 вместо рекурсии
 
Автор
L

Latoid

Знающий
Сообщения
95
Репутация
11
inververs сказал(а):
Сделайте цикл от 1 до 5 вместо рекурсии

И вот интересно, почему я сам в эту сторону не подумал? :shok:
Ну да, проблема решается просто циклом. Просто встретилась рекурсивная по своей природе задача, и мозг даже не подумал в иную сторону. Наверное, хотелось разобраться, наконец, с рекурсией, чтоб повысить свой скил.
 

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
Повторение функции самой себя пока не будет ошибки - это не рекурсивная задача. И лучше обходится без нее там, где это возможно.
 
Автор
L

Latoid

Знающий
Сообщения
95
Репутация
11
inververs
Забыл поблагодарить вас за дельный совет. Спасибо :smile:
 
Верх