мета-данные страницы
Идемпотентность запросов
Можно ознакомится со статьей от Яндекс: https://habr.com/ru/company/yandex/blog/442762/
Введение
Обработка в агенте
Обработка на клиенте
Пример МП Courier
Пример Бонусы Онлайн
Введение
Идемпотентность - это операция, которая при многократном вызове возвращает один и тот же результат.
Или метод HTTP является идемпотентным, если повторный идентичный запрос, сделанный один или несколько раз подряд, имеет один и тот же эффект, не изменяющий состояние сервера.
Обработка в агенте
- Новые property:
- Himstat.IdempotencyText - ключ идемпотентности, берется из заголовка запроса и устанавливается на прямую (доступно на чтение и запись);
- Himstat.IdempotencyHash - хэш ключ идемпотентности, устанавливается при установке IdempotencyText (доступно только на чтение);
- в модуле <font color="blue" size="+1">ErrorRes.pas</font> новый тип Exception TIdenpotencyException.
Пример запроса:
procedure THimstatDM. ...(Srvr: TRtcDataServer); var ... begin ... try ... try Himstat := THimstat(GlobalConnectionPoolClass.LockFreeConnection(THimstat)); // Получаем IdempotencyText -> (IdempotencyHash) Himstat.IdempotencyText := Srvr.Request.Query.Text; Himstat.Do...(SessionID, s); // Выполняем команду finally GlobalConnectionPoolClass.UnlockConnection(TObject(Himstat)); end; ... except on E: TIdenpotencyException do begin ... s := Format('{"error": ' + IntToStr(ErrorIdenpotency) + ', "Msg": "%s"}', [AEncodeURL(Utf8Encode('Повторный запрос. Обновите приложение!'))]); ... end; on E: Exception do begin ... end; end; ... end; procedure THimstat.Do...(const nSessionID: string; out Str: string); var ... begin ... try ... QueryIdempotency; // Записываем в таблицу Hash и время (* *Нет таблицы IDEMPOTENCY_QUERY - выходим из проверки * - получаем старый ОТВЕТ (предыдущего АНАЛОГИЧНОГО запроса) ИЛИ получаем ОТВЕТ="" - Идет запись * - ОТВЕТ=null - пытаемся записать HASH * - ERROR Присваиваем ОТВЕТ:="" - Идет запись * - FINALY * - (1) ЕСЛИ ОТВЕТ=null - то у нас уникальный запрос записанный в таблицу * - (2) ЕСЛИ ОТВЕТ<>null - запрос не уникален - возбуждаем исключение TIdenpotencyException.Create(ОТВЕТ); *) // Выполняем действия ... UpdateIdempotency(Str); // сохраняем ОТВЕТ в таблицу except on E: TIdenpotencyException do begin Str := e.Message; if Trim(Str) = '' then raise TIdenpotencyException.Create('Нет ответа на запрос - повторяющийся запрос!'); (* *Если исключение содержит НЕ ПУСТОЕ сообщение то это ОТВЕТ и мы его отправляем повторно *Если исключение содержит ПУСТОЕ сообщение то снова возбуждаем TIdenpotencyException и отправляем как ошибку ОДНОВРЕМЕННОГО ЗАПРОСА (ErrorCode = 5) *) end; on E: Exception do begin ... DeleteIdempotency; // Если ОШИБКА произошла в теле обработки до сохранения ОТВЕТа то HASH запроса удаляется end; end; ... end;
Клиент
- Организовать уникальность запроса;
- Обработку код Error = 5.
Обработка на клиенте
Пример МП Courier
... ... var PayIdempotency: string;//'Переменная сохраняет состояние текущей транзакции /запрос(dor_id, debet) + ответ(JSON)/' ... ... JSONAnswer := KassaFiscalRegistar.PrintFiscalCheck(JSONResultObj); //'Печать чека' ... except on E: Exception do begin PayIdempotency := ''; //'Если ошибка при печати - обнуляем текущую транзакцию' ... ... procedure TmoneyPayFramePanel.OnShow(curVisible: TuniFramePanel); begin PayIdempotency := '';// 'При входе обнуление предыдущей транзакции' ... ... ClnDM.GetFiscalCheck(sJSON, procedure (AJObject: TJSONObject) var CurrentPayIdempotency: string; //'Текущая транзакция' begin CurrentPayIdempotency := THashMD5.GetHashString(sJSON + '=' + AJObject.ToString); if PayIdempotency = CurrentPayIdempotency then //'Текущую сравниваем с запомненой' begin // 'Ничего не делаем если транзакция все еще ТА ЖЕ' WriteLog("TmoneyPayFramePanel.WorkOfPay PayIdempotency = CurrentPayIdempotency Abort"); end else begin //'Транзакция другая - выпонить оплату' PayIdempotency := CurrentPayIdempotency; AnswerFromWeb('GetFiscalCheck', '', 0, AJObject); end; end); ... ...
Пример Бонусы Онлайн
function BonusOnlineSend(const data: string; out j: TJsonObject): Boolean; var ... Idempotency_GUID: string; // Переменная сохраняет состояние текущей транзакции ReconIdempotencyCount: integer; json: TJsonValue; JSON_O: TJsonObject; begin Result := False; Idempotency_GUID := ''; // При входе обнуление предыдущей транзакции try CreateIdempotency_GUID(Idempotency_GUID); // Текущая транзакция ReconIdempotencyCount := 0; json := nil; while True do begin if not BonusPaySend(data, ApiPath, hmPOST, res, Idempotency_GUID) then begin WriteLog('Не получили данные о бонусе'); raise Exception.Create('Бонусы онлайн. Получить бонусы невозможно!' + #13#10 + 'Отсутствует связь с сервером!'); end; ... codeApi := JSON_O.Get('error').JsonValue.Value; ... else if codeApi = '5' then // Действия при 5 коде begin if ReconIdempotencyCount < 2 then // Проверяем количество провторов begin WriteLog('errorResult = 5, повторный запрос'); Sleep(500); inc(ReconIdempotencyCount); Continue; // Пробуем еще раз end else begin ReconIdempotencyCount := 0; // Обнулим счетчик и сообщим об ошибке WriteLog(_UTFDecode(JSON_O.Get('Msg').JsonValue.Value)); raise Exception.Create('Бонусы онлайн. Получить бонусы невозможно!' + #13#10 + _UTFDecode(JSON_O.Get('Msg').JsonValue.Value)); end; end else ... Break; end; ... Result := True; finally if Assigned(json) then FreeAndNil(json); Idempotency_GUID := ''; end; end; ... function BonusPaySend(const data, URL: string; Method: THttpMethod; out json: string; Idempotency_GUID: string = ''): Boolean; var i: Integer; http: T_Response; Headers: TStringList; url_data, ContentEncoding: string; begin Result := False; i := 0; while (i < 2) and not Result do begin Headers := TStringList.Create; try if Idempotency_GUID <> '' then // идентификатор запроса Headers.Add('XIdempotency-GUID: ' + Idempotency_GUID); // Передаем в запросе Headers.Add('Accept-Encoding: deflate'); Headers.Add('Content-Encoding: deflate'); Headers.Add('Content-Type: application/json'); ... finally if Assigned(Headers) then FreeAndNil(Headers); inc(i); end; end; end;