Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
KANSoftWare

Как получить объект ошибки для обработки в методе MARS REST API

Delphi , Синтаксис , API реализация

 

Введение

При работе с REST API в Delphi, особенно используя библиотеку MARS (Microservice Application REST Server), разработчики часто сталкиваются с необходимостью корректной обработки ошибок. Вопрос, поднятый пользователем KostasR на форуме, касается важной проблемы: как получить объект ошибки (error-object) при возникновении HTTP-ошибок в методе PUT библиотеки MARS.

Эта проблема актуальна для всех разработчиков, использующих MARS или подобные REST-библиотеки в своих проектах. В данной статье мы подробно рассмотрим как стандартное решение, так и альтернативные подходы к обработке ошибок в REST-запросах.

Постановка проблемы

Рассмотрим исходный код, представленный KostasR:

rsUpdateLicence.PUT(
    procedure (AContent: TMemoryStream)
    var LJSONObject: TJSONObject;
        BirthDateStr, FormattedDate: string;
    begin
      JSONValueToStream(LJSONObject, AContent);
    end,

    procedure (AResponse: TStream)
    var
      LResponse: TJSONObject;
    begin
      LResponse := StreamToJSONValue(AResponse) as TJSONObject;
      try
        if Assigned(LResponse) then
        begin
          LicenseRespose := LResponse.ToRecord<TLicenseRespose>();
          oResult := WriteData(LicenseRespose);
        end;
      finally
        LResponse.Free;
      end;
    end,

    procedure (AErr: Exception)
    begin
      ShowMessage('Error: ' + AErr.Message);
      // Как получить объект ошибки?
    end);

Проблема заключается в том, что обработчик ошибок OnAfterExecute (третий параметр метода PUT) получает только сообщение об ошибке через Exception.Message, но не получает сам объект ответа от сервера, который содержит детализированную информацию об ошибке в формате JSON.

Решение от разработчика MARS

Андреа Маньи (Andrea Magni), создатель библиотеки MARS, сообщил, что данная функциональность была реализована в клиентской библиотеке MARS. Согласно его сообщению от 22 июля, теперь можно получать содержимое ответа сервера даже при возникновении HTTP-ошибок.

Демонстрационный пример

Для понимания решения рассмотрим пример из репозитория MARS:

// Пример обработки ошибок в новой версии MARS
procedure TForm1.HandleRESTError(const AResponse: TStream; const AException: Exception);
var
  LErrorObj: TJSONObject;
  LErrorMessage: string;
  LErrorCode: Integer;
begin
  try
    if Assigned(AResponse) then
    begin
      LErrorObj := StreamToJSONValue(AResponse) as TJSONObject;
      if Assigned(LErrorObj) then
      begin
        // Извлекаем информацию об ошибке из JSON
        LErrorMessage := LErrorObj.GetValue<string>('message', '');
        LErrorCode := LErrorObj.GetValue<Integer>('code', 0);

        ShowMessage(Format('Server Error: %s (Code: %d)', [LErrorMessage, LErrorCode]));
      end;
    end
    else
    begin
      // Обработка стандартной ошибки
      ShowMessage('Connection Error: ' + AException.Message);
    end;
  except
    on E: Exception do
      ShowMessage('Error parsing error response: ' + E.Message);
  end;
end;

Альтернативные решения

Решение 1: Настройка TIdHTTP для получения содержимого ошибок

Как показал Die Holländer, можно настроить TIdHTTP для получения содержимого ошибок:

function TForm1.PerformPUTRequest(const AURL: string; const AJSONData: string): Boolean;
var
  LHTTP: TIdHTTP;
  LRequest: TStringStream;
  LResponse: TStringStream;
  LErrorObj: TJSONObject;
begin
  Result := False;
  LHTTP := TIdHTTP.Create(nil);
  LRequest := TStringStream.Create(AJSONData, TEncoding.UTF8);
  LResponse := TStringStream.Create('');

  try
    try
      // Настройка HTTP-клиента для получения содержимого ошибок
      LHTTP.Request.ContentType := 'application/json';
      LHTTP.HTTPOptions := LHTTP.HTTPOptions + [hoNoProtocolErrorException, hoWantProtocolErrorContent];

      LHTTP.Put(AURL, LRequest, LResponse);

      // Проверка кода ответа
      if (LHTTP.ResponseCode div 100) = 2 then
      begin
        Result := True;
        // Обработка успешного ответа
        ProcessSuccessResponse(LResponse);
      end
      else
      begin
        // Обработка ошибки с содержимым ответа
        ProcessErrorResponse(LResponse);
      end;
    except
      on E: EIdHTTPProtocolException do
      begin
        // Обработка протокольных ошибок HTTP
        if Assigned(E.ErrorMessage) and (E.ErrorMessage <> '') then
        begin
          try
            LErrorObj := TJSONObject.ParseJSONValue(TEncoding.UTF8.GetBytes(E.ErrorMessage), 0) as TJSONObject;
            if Assigned(LErrorObj) then
            begin
              ProcessErrorObject(LErrorObj);
              LErrorObj.Free;
            end;
          except
            ShowMessage('Error parsing error JSON: ' + E.ErrorMessage);
          end;
        end
        else
          ShowMessage('HTTP Error: ' + IntToStr(E.ErrorCode) + ' - ' + E.Message);
      end;
    end;
  finally
    LResponse.Free;
    LRequest.Free;
    LHTTP.Free;
  end;
end;

procedure TForm1.ProcessErrorResponse(const AResponse: TStringStream);
var
  LErrorObj: TJSONObject;
begin
  try
    AResponse.Position := 0;
    LErrorObj := TJSONObject.ParseJSONValue(TEncoding.UTF8.GetBytes(AResponse.DataString), 0) as TJSONObject;

    if Assigned(LErrorObj) then
    begin
      try
        ProcessErrorObject(LErrorObj);
      finally
        LErrorObj.Free;
      end;
    end;
  except
    on E: Exception do
      ShowMessage('Error processing error response: ' + E.Message);
  end;
end;

procedure TForm1.ProcessErrorObject(const AErrorObj: TJSONObject);
var
  LMessage, LDetails: string;
  LCode: Integer;
begin
  LMessage := AErrorObj.GetValue<string>('message', 'Unknown error');
  LCode := AErrorObj.GetValue<Integer>('code', -1);
  LDetails := AErrorObj.GetValue<string>('details', '');

  ShowMessage(Format('Error %d: %s%s', [LCode, LMessage, 
    IfThen(LDetails <> '', sLineBreak + 'Details: ' + LDetails, '')]));
end;

Решение 2: Создание собственного обработчика ошибок для MARS

Если вы используете более старую версию MARS или хотите иметь больше контроля над обработкой ошибок, можно создать собственный обработчик:

type
  TErrorHandlingProc = procedure(const AResponse: TStream; const AException: Exception) of object;

  TMARSErrorHandler = class
  private
    FOnSuccess: TProc<TStream>;
    FOnError: TErrorHandlingProc;
  public
    constructor Create(ASuccessProc: TProc<TStream>; AErrorProc: TErrorHandlingProc);
    procedure HandlePUT(const AURL: string; const AContent: TStream);
  end;

constructor TMARSErrorHandler.Create(ASuccessProc: TProc<TStream>; AErrorProc: TErrorHandlingProc);
begin
  inherited Create;
  FOnSuccess := ASuccessProc;
  FOnError := AErrorProc;
end;

procedure TMARSErrorHandler.HandlePUT(const AURL: string; const AContent: TStream);
var
  LHTTP: TIdHTTP;
  LResponse: TMemoryStream;
begin
  LHTTP := TIdHTTP.Create(nil);
  LResponse := TMemoryStream.Create;

  try
    try
      LHTTP.Request.ContentType := 'application/json';
      LHTTP.HTTPOptions := LHTTP.HTTPOptions + [hoNoProtocolErrorException, hoWantProtocolErrorContent];

      LHTTP.Put(AURL, AContent, LResponse);

      if (LHTTP.ResponseCode div 100) = 2 then
      begin
        LResponse.Position := 0;
        if Assigned(FOnSuccess) then
          FOnSuccess(LResponse);
      end
      else
      begin
        LResponse.Position := 0;
        if Assigned(FOnError) then
          FOnError(LResponse, nil);
      end;
    except
      on E: Exception do
      begin
        if Assigned(FOnError) then
          FOnError(nil, E);
      end;
    end;
  finally
    LResponse.Free;
    LHTTP.Free;
  end;
end;

// Использование обработчика
procedure TForm1.PerformRequest;
var
  LHandler: TMARSErrorHandler;
  LContentStream: TStringStream;
begin
  LContentStream := TStringStream.Create('{"data": "test"}', TEncoding.UTF8);

  LHandler := TMARSErrorHandler.Create(
    procedure(const AResponse: TStream)
    begin
      // Обработка успешного ответа
      ProcessSuccessResponse(AResponse);
    end,

    procedure(const AResponse: TStream; const AException: Exception)
    begin
      // Обработка ошибки
      ProcessErrorResponse(AResponse, AException);
    end
  );

  try
    LHandler.HandlePUT('https://api.example.com/license', LContentStream);
  finally
    LHandler.Free;
    LContentStream.Free;
  end;
end;

Практические рекомендации

1. Обработка различных типов ошибок

procedure TForm1.ProcessErrorResponse(const AResponse: TStream; const AException: Exception);
var
  LErrorObj: TJSONObject;
  LResponseStr: string;
  LResponseBytes: TBytes;
begin
  if Assigned(AException) then
  begin
    // Обработка сетевых ошибок
    if AException is EIdSocketError then
      ShowMessage('Connection error: ' + AException.Message)
    else if AException is EIdHTTPProtocolException then
      ProcessHTTPError(AException as EIdHTTPProtocolException)
    else
      ShowMessage('Unknown error: ' + AException.Message);
    Exit;
  end;

  if Assigned(AResponse) then
  begin
    // Обработка ошибок от API
    try
      AResponse.Position := 0;
      SetLength(LResponseBytes, AResponse.Size);
      AResponse.ReadBuffer(Pointer(LResponseBytes)^, AResponse.Size);
      LResponseStr := TEncoding.UTF8.GetString(LResponseBytes);

      LErrorObj := TJSONObject.ParseJSONValue(LResponseBytes, 0) as TJSONObject;
      if Assigned(LErrorObj) then
      begin
        try
          ProcessAPIError(LErrorObj);
        finally
          LErrorObj.Free;
        end;
      end
      else
        ShowMessage('Server error: ' + LResponseStr);
    except
      on E: Exception do
        ShowMessage('Error parsing server response: ' + E.Message);
    end;
  end;
end;

procedure TForm1.ProcessHTTPError(const AException: EIdHTTPProtocolException);
begin
  case AException.ErrorCode of
    400: ShowMessage('Bad Request: ' + AException.Message);
    401: ShowMessage('Unauthorized: ' + AException.Message);
    403: ShowMessage('Forbidden: ' + AException.Message);
    404: ShowMessage('Not Found: ' + AException.Message);
    500: ShowMessage('Internal Server Error: ' + AException.Message);
  else
    ShowMessage(Format('HTTP Error %d: %s', [AException.ErrorCode, AException.Message]));
  end;
end;

procedure TForm1.ProcessAPIError(const AErrorObj: TJSONObject);
var
  LErrorCode: Integer;
  LErrorMessage, LErrorType: string;
begin
  LErrorCode := AErrorObj.GetValue<Integer>('code', 0);
  LErrorMessage := AErrorObj.GetValue<string>('message', 'Unknown error');
  LErrorType := AErrorObj.GetValue<string>('type', 'General');

  ShowMessage(Format('API Error (%s) %d: %s', [LErrorType, LErrorCode, LErrorMessage]));
end;

2. Логирование ошибок

procedure TForm1.LogError(const AMessage: string; const ADetails: string = '');
var
  LLogFile: TextFile;
  LLogPath: string;
begin
  LLogPath := ExtractFilePath(ParamStr(0)) + 'error_log.txt';
  AssignFile(LLogFile, LLogPath);

  if FileExists(LLogPath) then
    Append(LLogFile)
  else
    Rewrite(LLogFile);

  try
    Writeln(LLogFile, FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + ' - ' + AMessage);
    if ADetails <> '' then
      Writeln(LLogFile, 'Details: ' + ADetails);
    Writeln(LLogFile, '');
  finally
    CloseFile(LLogFile);
  end;
end;

Заключение

Проблема получения объекта ошибки в методах REST API, особенно в библиотеке MARS, является важной для создания надежных клиентских приложений. Разработчик MARS Андреа Маньи уже реализовал эту функциональность в новых версиях библиотеки, что делает обработку ошибок более удобной и информативной.

Для разработчиков, использующих старые версии библиотеки или желающих иметь больше контроля над процессом, представлены альтернативные решения, включая настройку TIdHTTP и создание собственных обработчиков ошибок.

Ключевые моменты для успешной обработки ошибок:

  1. Настройка HTTP-клиента для получения содержимого ошибок
  2. Парсинг JSON-объектов ошибок для извлечения детальной информации
  3. Разделение обработки сетевых ошибок и ошибок API
  4. Логирование ошибок для последующего анализа
  5. Пользовательское уведомление с понятными сообщениями

Использование этих подходов позволит создать более стабильные и удобные в сопровождении приложения для работы с REST API в среде Delphi.

Создано по материалам из источника по ссылке.

Контекст описывает проблему получения и обработки объекта ошибки в методе PUT библиотеки MARS для Delphi, когда стандартный обработчик ошибок не предоставляет доступ к содержимому ответа сервера.


Комментарии и вопросы

Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.


:: Главная :: API реализация ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-12-22 20:14:06
2025-09-03 05:34:07/0.0041429996490479/0