Implement inline processing of stream data in TXDataClient

Cheers,

I want to implement a specific processing on server on client side which is additionally applied to other processings. Server-side I would just create a new middleware and assign there a wrapper for Request.ContentStream.

I am not completely sure how to do this on the client side, as I need this also for ongoing transfers (larger ones). I searched in the forum and saw there that the OnResponseReceived EventHandler shall be used.

From the Online Help I learned that ContentAsStream can be read from. And here I have an understanding issue: If read the data from the stream and do changes, how can I achieve, that the XDataClient gets the changed stream, esp. when the client is still receiving data?

Please give me some advise.

Regards,

Thomas

Hi @Volker_Thomas, welcome to TMS Support Center!

It's not clear to me if you want to modify the response or request stream, and if you want to modify it server-side or client-side?

Hi Wagner,

on the server side the implementation is clear for me (maybe I come back to you - I will see).

Currently I want to modify the request stream and the response stream on the client side.

Thomas

In OnSendingRequest event you can simply set the Content property of the Request object.

In the OnResponseReceived, you should replace the THttpResponse object by your own, and replace it in the event.

If you check the EncryptionMiddleware demo, you will see it does that. Here is the key code in the demo:

procedure TForm1.Button1Click(Sender: TObject);
const
  cHeaderAuthorization = 'Authorization';
  cTokenPrefix = 'Bearer ';
  cUser = 'admin';
  cPassword = '67Uyvjvcp1bk25rXCZtC4xeFD5OIne';
var
  lClient: TXDataClient;
  lToken: string;
  lEncrypted: Boolean;
begin
  lEncrypted := CheckBox1.Checked;
  lClient := TXDataClient.Create;
  try
    lCLient.Uri := 'http://127.0.0.1:2001/TMS/AppServer';
    lClient.HttpClient.OnSendingRequest :=
      procedure(ARequest: THttpRequest)
      var
        pBytes: TBytes;
      begin
        if lToken <> '' then
          ARequest.Headers.SetValue(cHeaderAuthorization, cTokenPrefix + lToken);
        if ARequest.ContentLength > 0 then
        begin
          if lEncrypted then
          begin
            pBytes := TBytes.FromBuffer(ARequest.ContentBuffer^, ARequest.ContentLength);
            pBytes := TAESFunc.Encrypt(pBytes, TAESKeys.GetReceiveKey);
            ARequest.SetContent(pBytes);
          end
          else
            ARequest.Headers.AddValue(cCustomXDataHeader, TAESKeys.GetHeaderKey)
        end;
      end;
    lClient.HttpClient.OnResponseReceived :=
      procedure(ARequest: THttpRequest; var AResponse: THttpResponse)
      begin
        if not SameText(AResponse.Headers.Get(cCustomXDataHeader), TAESKeys.GetHeaderKey) then
        begin
          AResponse := TEncryptedHttpResponse.Create(AResponse,
            function(const AValue: TBytes): TBytes
            begin
              Result := TAESFunc.Decrypt(AValue, TAESKeys.GetSendKey);
            end);
        end;
      end;
     lToken := lClient.Service<IAppServer>.Connect(cUser, cPassword);
     Edit1.Text := lClient.Service<IAppServer>.Echo(Edit1.Text);
  finally
    lClient.Free;
  end;
end;

And for illustration purposes, here is the implementation of TEncryptedHttpResponse:

unit Sparkle.Encrypted.Response;

interface

uses
  System.SysUtils, System.Classes, Sparkle.Http.Headers, Sparkle.Http.Engine;

type
  TDecryptFunc = reference to function(const ABytes: TBytes): TBytes;

  TEncryptedHttpResponse = class(THttpResponse)
  strict private
    FBytes: TBytesStream;
    FOrig: THttpResponse;
  strict protected
    function CreateContentStream: TStream; override;
    function GetChunked: Boolean; override;
    function GetContentAsBytes: TBytes; override;
    function GetContentAsStream: TStream; override;
    function GetContentAsString: string; override;
    function GetContentEncoding: string; override;
    function GetContentLength: Int64; override;
    function GetContentType: string; override;
    function GetStatusCode: Integer; override;
    function GetStatusReason: string; override;
    procedure UpdateHeaders(AHeaders: THttpHeaders); override;
  public
    constructor Create(const AOrig: THttpResponse; const ADecrypt: TDecryptFunc);
    destructor Destroy; override;
  end;

implementation

type
  TUnprotectedHttpResponse = class(THttpResponse);

{ TEncryptedHttpResponse }

constructor TEncryptedHttpResponse.Create(const AOrig: THttpResponse; const ADecrypt: TDecryptFunc);
var
  lInfo: THttpHeaderInfo;
begin
  inherited Create;
  FOrig := AOrig;
  FBytes := TBytesStream.Create(ADecrypt(TUnprotectedHttpResponse(FOrig).GetContentAsBytes));

  for lInfo in AOrig.Headers.AllHeaders do
    Headers.AddValue(lInfo.Name, lInfo.Value);
end;

destructor TEncryptedHttpResponse.Destroy;
begin
  FOrig.Free;
  FBytes.Free;
  inherited Destroy;
end;

function TEncryptedHttpResponse.CreateContentStream: TStream;
begin
  Result := TBytesStream.Create(GetContentAsBytes);
end;

function TEncryptedHttpResponse.GetChunked: Boolean;
begin
  Result := FOrig.Chunked;
end;

function TEncryptedHttpResponse.GetContentAsBytes: TBytes;
begin
  Result := Copy(FBytes.Bytes, 0, FBytes.Size);
end;

function TEncryptedHttpResponse.GetContentAsStream: TStream;
begin
  Result := FBytes;
end;

function TEncryptedHttpResponse.GetContentAsString: string;
begin
  Result := StringOf(GetContentAsBytes);
end;

function TEncryptedHttpResponse.GetContentEncoding: string;
begin
  Result := FOrig.ContentEncoding;
end;

function TEncryptedHttpResponse.GetContentLength: Int64;
begin
  Result := FBytes.Size;
end;

function TEncryptedHttpResponse.GetContentType: string;
begin
  Result := FOrig.ContentType;
end;

function TEncryptedHttpResponse.GetStatusCode: Integer;
begin
  Result := FOrig.StatusCode;
end;

function TEncryptedHttpResponse.GetStatusReason: string;
begin
  Result := FOrig.StatusReason;
end;

procedure TEncryptedHttpResponse.UpdateHeaders(AHeaders: THttpHeaders);
begin
  TUnprotectedHttpResponse(FOrig).UpdateHeaders(AHeaders);
end;

end.