мета-данные страницы
  •  

Различия

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

Ссылка на это сравнение

Предыдущая версия справа и слева Предыдущая версия
Следующая версия Следующая версия справа и слева
agbis_idempotency [31.05.2022 10:44]
agbis_idempotency [31.05.2022 10:44]
Anatoly [Обработка в агенте]
Строка 1: Строка 1:
 +====== Идемпотентность запросов ======
  
 +Можно ознакомится со статьей от Яндекс:​ [[https://​habr.com/​ru/​company/​yandex/​blog/​442762/​]]
 +----
 +[[#​Введение|Введение]]\\
 +[[#​Обработка в агенте|Обработка в агенте]]\\
 +[[#​Обработка на клиенте|Обработка на клиенте]]\\
 +[[#​Пример МП Courier|Пример МП Courier]]\\
 +[[#​Пример Бонусы Онлайн|Пример Бонусы Онлайн]]\\
 +----
 +
 +
 +===== Введение =====
 +
 +Идемпотентность - это операция,​ которая при многократном вызове возвращает один и тот же результат. Или метод HTTP является идемпотентным,​ если повторный идентичный запрос,​ сделанный один или несколько раз подряд,​ имеет один и тот же эффект,​ не изменяющий состояние сервера.
 +----
 +===== Обработка в агенте =====
 +
 +  * Новые property:
 +    * Himstat.IdempotencyText - ключ идемпотентности,​ берется из заголовка запроса и устанавливается на прямую (доступно на чтение и запись);​
 +    * Himstat.IdempotencyHash - хэш ключ идемпотентности,​ устанавливается при установке IdempotencyText (доступно только на чтение);​
 +  * в модуле <font color="​blue"​ size="​+1">​ErrorRes.pas</​font>​ новый тип Exception TIdenpotencyException.
 +
 +
 +**Пример запроса:​**
 +
 +<sxh Delphi>
 +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;
 +</​sxh>​
 +----
 +==== Клиент ====
 +  - Организовать уникальность запроса;​
 +  - Обработку код Error = 5.
 +
 +----
 +===== Обработка на клиенте =====
 +
 +===== Пример МП Courier =====
 +
 +<sxh Delphi>
 +...
 +
 +...
 +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);
 +... 
 +
 +...
 +</​sxh>​
 +----
 +===== Пример Бонусы Онлайн =====
 +<sxh Delphi>
 +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;
 +</​sxh>​
 +----