При работе с 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 и создание собственных обработчиков ошибок.
Ключевые моменты для успешной обработки ошибок:
Настройка HTTP-клиента для получения содержимого ошибок
Парсинг JSON-объектов ошибок для извлечения детальной информации
Разделение обработки сетевых ошибок и ошибок API
Логирование ошибок для последующего анализа
Пользовательское уведомление с понятными сообщениями
Использование этих подходов позволит создать более стабильные и удобные в сопровождении приложения для работы с REST API в среде Delphi.
Контекст описывает проблему получения и обработки объекта ошибки в методе PUT библиотеки MARS для Delphi, когда стандартный обработчик ошибок не предоставляет доступ к содержимому ответа сервера.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.