I have an Entity storing some informations of a third party API.
I would like to store the fully recieved Response Body into a TBlob field into this Entity.
Each record represents an API-Call.
What are best practices to do so before XData generates instances out of the normaly recieved JSON?
wlandgraf
(Wagner Landgraf)
January 27, 2026, 2:34pm
2
Here is a sample middleware that logs request and response:
unit Server.Middleware.InternalError;
interface
uses
System.SysUtils,
System.Classes,
System.Diagnostics,
Sparkle.Http.Headers,
Sparkle.HttpServer.Context,
Sparkle.HttpServer.Module,
Sparkle.HttpServer.Response;
type
TServerErrorInfo = class;
TInternalServerErrorMiddleware = class(THttpServerMiddleware)
strict private
procedure SaveServerError(Context: THttpServerContext; RequestContent: TBytes;
ServerErrorInfo: TServerErrorInfo);
public
procedure ProcessRequest(Context: THttpServerContext; Next: THttpServerProc); override;
end;
TServerErrorInfo = class
strict private
FResponseStream: TBytesStream;
FStopWatch: TStopWatch;
public
constructor Create(AResponseStream: TBytesStream);
procedure StartWatch;
procedure StopWatch;
function ElapsedMilliseconds: Int64;
function ResponseContent: TBytes;
function InternalServerError: string;
end;
THttpRawResponseLoggingStream = class;
THttpRawResponseBodyLoggingWriter = class(TInterfacedObject, IHttpResponseBodyWriter)
strict private
FStream: THttpRawResponseLoggingStream;
public
constructor Create(AResponse: THttpServerResponse; AStream: TBytesStream);
destructor Destroy; override;
function Stream: TStream;
procedure Flush;
end;
THttpRawResponseLoggingStream = class(THttpRawResponseStream)
strict private
FStream: TBytesStream;
public
constructor Create(AResponse: THttpServerResponse; AStream: TBytesStream);
function Write(const Buffer; Count: Longint): Longint; override;
function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
end;
implementation
uses
Bcl.Json,
Bcl.Json.Classes;
{ THttpRawResponseLoggingStream }
constructor THttpRawResponseLoggingStream.Create(AResponse: THttpServerResponse;
AStream: TBytesStream);
begin
inherited Create(AResponse);
FStream := AStream;
end;
function THttpRawResponseLoggingStream.Seek(const Offset: Int64;
Origin: TSeekOrigin): Int64;
begin
FStream.Seek(Offset, Origin);
inherited;
end;
function THttpRawResponseLoggingStream.Write(const Buffer;
Count: Longint): Longint;
begin
FStream.Write(Buffer, Count);
inherited;
end;
{ THttpRawResponseBodyLoggingWriter }
constructor THttpRawResponseBodyLoggingWriter.Create(
AResponse: THttpServerResponse; AStream: TBytesStream);
begin
inherited Create;
FStream := THttpRawResponseLoggingStream.Create(AResponse, AStream);
end;
destructor THttpRawResponseBodyLoggingWriter.Destroy;
begin
FStream.Free;
inherited;
end;
procedure THttpRawResponseBodyLoggingWriter.Flush;
begin
FStream.Flush;
end;
function THttpRawResponseBodyLoggingWriter.Stream: TStream;
begin
Result := FStream;
end;
{ TInternalServerErrorMiddleware }
procedure TInternalServerErrorMiddleware.ProcessRequest(
Context: THttpServerContext; Next: THttpServerProc);
var
RequestContent: TBytes;
ResponseStream: TBytesStream;
ServerErrorInfo: TServerErrorInfo;
LogRequest: Boolean;
begin
LogRequest := False;
SetLength(RequestContent, 0);
ResponseStream := TBytesStream.Create;
ServerErrorInfo := TServerErrorInfo.Create(ResponseStream);
try
Context.Response.OnHeaders(
procedure(Resp: THttpServerResponse)
var
Writer: IHttpResponseBodyWriter;
begin
LogRequest := Resp.StatusCode >= 500;
if not LogRequest then
Exit;
RequestContent := Context.Request.Content;
Writer := THttpRawResponseBodyLoggingWriter.Create(Resp, ResponseStream);
Context.Response.RawWriter := Writer;
end
);
ServerErrorInfo.StartWatch;
Next(Context);
ServerErrorInfo.StopWatch;
if LogRequest then
SaveServerError(Context, RequestContent, ServerErrorInfo);
finally
ServerErrorInfo.Free;
ResponseStream.Free;
end;
end;
procedure TInternalServerErrorMiddleware.SaveServerError(
Context: THttpServerContext; RequestContent: TBytes;
ServerErrorInfo: TServerErrorInfo);
begin
end;
{ TServerErrorInfo }
constructor TServerErrorInfo.Create(AResponseStream: TBytesStream);
begin
FResponseStream := AResponseStream;
end;
function TServerErrorInfo.ElapsedMilliseconds: Int64;
begin
Result := FStopWatch.ElapsedMilliseconds;
end;
function TServerErrorInfo.InternalServerError: string;
begin
Result := '';
try
FResponseStream.Position := 0;
var Json := TJson.Deserialize<TJObject>(FResponseStream);
try
if not Json.Contains('error') then
Exit;
if not Json['error'].IsObject then
Exit;
if not Json['error'].AsObject.Contains('message') then
Exit;
if not Json['error'].AsObject['message'].IsString then
Exit;
Result := Json['error'].AsObject['message'].AsString;
Result := Result.Replace('Internal server error: ', '', [rfReplaceAll]);
finally
Json.Free;
end;
except
Result := '';
end;
end;
function TServerErrorInfo.ResponseContent: TBytes;
begin
Result := FResponseStream.Bytes;
SetLength(Result, FResponseStream.Size);
end;
procedure TServerErrorInfo.StartWatch;
begin
FStopWatch := TStopwatch.StartNew;
end;
procedure TServerErrorInfo.StopWatch;
begin
FStopWatch.Stop;
end;
end.
your given Example looks like it could not be used within XData-Client.
Or maybe I don’t see how a Middleware could be used on client-side.
wlandgraf
(Wagner Landgraf)
January 31, 2026, 1:00pm
4
For client-side you can use this approach:
A response content stream (or bytes) is unidirectional. If you read the content, you have to provide a new response object with the same content. Here is an example:
Client.OnResponseReceived := procedure(Req: THttpRequest; var Resp: THttpResponse)
begin
Resp := THttpCachedResponse.Create(Resp);
var TextResponse := TEncoding.UTF8.GetString(Resp.ContentAsBytes);
end;
system
(system)
Closed
February 3, 2026, 4:00pm
5
This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.