📄 rtcfunction.pas
字号:
{
@html(<b>)
Remote Functions
@html(</b>)
- Copyright (c) Danijel Tkalcec
@html(<br><br>)
This unit implements a set of components for WRITING remote functions
and forming groups of functions, which can be used with @Link(TRtcDataClient)
and/or @Link(TRtcDataServer) connection components, or directly by
calling @Link(TRtcFunctionGroup.ExecuteData) or @Link(TRtcFunctionGroup.CallFunction) methods,
provided by the @Link(TRtcFunctionGroup) component. By assigning a @Link(TRtcFunctionGroup)
component to @Link(TRtcServerModule) on the server-side (and linking that ServerModule to a
DataServer connection component), clients can remotely call all functions which that function
group provides. To give the client access to server-side remote functions, you can use the
@Link(TRtcClientModule) component.
Implementing a RTC Remote Function is as easy as writing a local function.
}
unit rtcFunction;
{$INCLUDE rtcDefs.inc}
interface
uses
rtcTrashcan,
Classes,
SysUtils,
memBinList,
rtcSyncObjs,
rtcConn,
rtcInfo,
rtcThrPool;
type
TRtcAbsCommand = class;
TRtcAbsFunction = class;
TRtcFunctionGroup = class;
// @exclude
TRtcFunctionList = class
private
FList:TList;
public
constructor Create;
destructor Destroy; override;
procedure Add(Value:TRtcAbsFunction);
procedure Remove(Value:TRtcAbsFunction);
procedure RemoveAll;
function Count:integer;
function Get(index:integer):TRtcAbsFunction;
end;
// @exclude
TRtcCommandInfo=class(TObject)
public
Sender: TRtcConnection;
Command: TRtcAbsCommand;
Group: TRtcFunctionGroup;
constructor Create;
end;
// @ebstract(Abstract Commands class)
TRtcAbsCommand=class(TRtcComponent)
protected
// @exclude
function Call_Execute(const CmdInfo:TRtcCommandInfo; const Param:TRtcFunctionInfo; const Res:TRtcValue):boolean; virtual; abstract;
// @exclude
function Function_Exists(const Function_Name:string):boolean; virtual; abstract;
end;
// @abstract(Abstract Function Provider class)
TRtcAbsFunction=class(TRtcAbsCommand)
private
FGroup,
FHelperGroup:TRtcFunctionGroup;
protected
// @exclude
function GetGroup: TRtcFunctionGroup;
// @exclude
procedure SetGroup(const Value: TRtcFunctionGroup);
// @exclude
function GetHelperGroup: TRtcFunctionGroup;
// @exclude
procedure SetHelperGroup(const Value: TRtcFunctionGroup);
public
// @exclude
constructor Create(AOwner:TComponent); override;
// @exclude
destructor Destroy; override;
end;
{ @abstract(Provide access to a group of remote functions)
To implement remote functions, you will need at least one FunctionGroup component
and link one or more TRtcFunction components to it. Function Group providers the means
to use function calls as parameters to other function calls from the same group.
It is primarily used by the TRtcServerModule and TRtcClientModule components to
hold implementations for their remote functions. @html(<br><br>)
This FunctionGroup will provide direct access to: @html(<br>)
1.) all functions which have this component as their ".Group" property set. @html(<br>)
2.) all functions of all child RtcFunctionGroups, meaning: RtcFunctionGroup components
which have this component assigned to their "ParentGroup" property. @html(<br>)
3.) all functions of the RtcFunctionGroup which is directly assigned to this
component's "HelperGroup" property, including its child components. It is safe to
assign the same HelperGroup to function groups at different levels (child/parent groups). }
TRtcFunctionGroup=class(TRtcAbsFunction)
private
FFunctions:TRtcFunctionList; // List of all Child functions and function groups
FGlobalUse:TRtcFunctionList; // List of FunctionGroup's using this FunctionGroup as their HelperGroup
protected
// @exclude
procedure AddFunction(Value:TRtcAbsFunction);
// @exclude
procedure RemoveFunction(Value:TRtcAbsFunction);
// @exclude
procedure RemoveAllFunctions;
// @exclude
procedure AddGlobalFunction(Value:TRtcAbsFunction);
// @exclude
procedure RemoveGlobalFunction(Value:TRtcAbsFunction);
// @exclude
procedure RemoveAllGlobalFunctions;
// @exclude
function Call_Execute(const CmdInfo:TRtcCommandInfo; const Param:TRtcFunctionInfo; const Res:TRtcValue):boolean; override;
// @exclude
function Function_Exists(const Function_Name:string):boolean; override;
public
// @exclude
constructor Create(AOwner:TComponent); override;
// @exclude
destructor Destroy; override;
{ Call Function and prepare the result.
If the function called does not exist, exception will be raised.
@param(Sender = connection component)
@param(Call = function call with all parameters)
@param(Res = object to receive the result)
@param(recursive = if TRUE, all results returned from any function called here,
will also be checked for functions and those functions will be executed)
@param(command = when assigned, will be used to execute all commands)
@return(TRUE if function found and executed) }
function CallFunction(CmdInfo:TRtcCommandInfo; Call:TRtcFunctionInfo; Res:TRtcValue; recursive:boolean=False):boolean;
{ Execute all Functions you can find in "Data" and prepare the result.
@param(CmdInfo = command info/parameters)
@param(Data = object holding data and function calls with all parameters)
@param(recursive = if TRUE, all results returned from any function called here,
will also be checked for funcfunctions and those functions will be executed)
@return(If there is a function that can not be found, exception will be raised.
If everything went OK, but the result couldn't be stored back in Data,
a new object will be created and returned as a result [Result<>Data],
leaving it up to the caller to decide what to do with the "old" Data object.
If you've used Data just to hold the call,
you should release it if Result<>nil and Result<>Data.) }
function ExecuteData(CmdInfo:TRtcCommandInfo; Data:TRtcValueObject; recursive:boolean=False):TRtcValueObject; overload;
{ Execute all Functions you can find in "Data" and prepare the result.
@param(Sender = RTC connection object)
@param(Data = object holding data and function calls with all parameters)
@param(recursive = if TRUE, all results returned from any function called here,
will also be checked for funcfunctions and those functions will be executed)
@return(If there is a function that can not be found, exception will be raised.
If everything went OK, but the result couldn't be stored back in Data,
a new object will be created and returned as a result [Result<>Data],
leaving it up to the caller to decide what to do with the "old" Data object.
If you've used Data just to hold the call,
you should release it if Result<>nil and Result<>Data.) }
function ExecuteData(Sender:TRtcConnection; Data:TRtcValueObject; recursive:boolean=False):TRtcValueObject; overload;
{ Returns TRUE if function with name "FunctionName" exists }
function FunctionExists(const FunctionName:string):boolean;
published
{ Using the ParentGroup property, you can define this group to be a child
of another RtcFunctionGroup component. All parent groups have full access to all
functions defined by any child group. }
property ParentGroup:TRtcFunctionGroup read GetGroup write SetGroup;
{ Using the HelperGroup property, you can assign another group of functions
to this group, to be able to use all functions from HelperGroup as
parameters or in combination with your functions, when your group is used
as the executing FunctionGroup. Executing function group is the group
on which you call the ExecuteData or CallFunction methods directly, or
for RtcClientModule and RtcServerModule, the one directly assigned to the
component that uses it.
HelperGroup should be a set of basic helper functions, which you want to
be able to use from all or most of other functions when passing parameters. }
property HelperGroup:TRtcFunctionGroup read GetHelperGroup write SetHelperGroup;
end;
{ @abstract(Remote Function component)
By setting the "FunctionName" property, linking the component to a FunctionGroup and
implementing the OnExecute event handler, your remote function is ready to be used.
When writing a remote function, you don't have to think about anything but your function.
In case of an exception (which you can also raise inside your OnExecute event handler),
client will get your exception message as a result, so you don't event have to
worry about that. @html(<br><br>)
You can combine multiple function calls in one request, or pass function calls as
parameters to other function calls. It makes no difference to the functions you implement,
because your function will always receive pure data, after all function calls (which the
client might have defined as parameters) are executed. @html(<br><br>)
And in case of serial function calls (more than one function called in one request),
if one call ends up with an exception, the result for THAT call will be rtc_Exception
(with the appropriate eror message), while any prior functions return their result
and the execution of the request is aborted. @html(<br><br>)
You can also return a TRtcFunctionInfo object as a result of your function call, in
which case that function will be sent to the other side and executed there.
All objects received from the client or from a function call will be checked
for Function Calls and any parameter that was a function call, will be replaced with
the result of that function. If you send pure data to the server (without any function calls),
you will receive that same data as a result to your request. }
TRtcFunction=class(TRtcAbsFunction)
private
FFuncName:string;
FOnExecute:TRtcFunctionCallEvent;
function GetFuncName: string;
procedure SetFuncName(const Value: string);
public
// @exclude
function Call_Execute(const CmdInfo:TRtcCommandInfo; const Param:TRtcFunctionInfo; const Res:TRtcValue):boolean; override;
// @exclude
function Function_Exists(const Function_Name:string):boolean; override;
published
{ Assign this Function to a function group, so it can be used
by the RtcClientModule and/or RtcServerModule components. }
property Group:TRtcFunctionGroup read GetGroup write SetGroup;
{ Function Name (string, not case sensitive) }
property FunctionName:string read GetFuncName write SetFuncName;
{ This event will be called to execute the function and prepare the return value.
You will receive the Result object as parameter, which will be initialy set to
a NULL value (Result.isType = rtc_null) and should be used to fill-in any data
your function has to return. For example, to return a string containing
the text "HELLO, WORLD!", simply set:@html(<br>)
Result.asString='HELLO, WORLD!'; @html(<br><br>)
The Sender parameter (server connection component) can be used to access
Session, Request or Response information, but NOT to Read the content Body
or to Write the content out directly to the connection component. Even
though there will be no exceptions raised in case you do use the Read or
Write methods from the Sender, the result produced by such an action will
result in content which Client will not be able to decode. In other words,
even if your server would send the content out, the Client which called
the function wouldn't be able to read the data and would most likely result
in an exception at client's side. }
property OnExecute:TRtcFunctionCallEvent read FOnExecute write FOnExecute;
end;
{ @abstract(Use to define events which will receive results from remote function calls) }
TRtcResult = class(TRtcComponent)
private
FOnReturn:TRtcResultEvent;
FOnAborted:TRtcResultEvent;
public
// @exclude
constructor Create(AOwner:TComponent); override;
// @exclude
destructor Destroy; override;
// @exclude
function Valid:boolean; virtual;
// @exclude
procedure Call_Return(Sender:TRtcConnection; Data:TRtcValue; Result:TRtcValue); virtual;
// @exclude
procedure Call_Aborted(Sender:TRtcConnection; Data:TRtcValue; Result:TRtcValue); virtual;
published
{ When used by a client in combination with @Link(TRtcClientModule) to call remote
functions using methods provided by @Link(TRtcClientModule), this event will be
used to process the result returned from the remote function call, if you pass
this TRtcResult component as parameter to the TRtcClientModule.@Link(TRtcClientModule.Call)
method.
The Sender parameter (server connection component) can be used to access
Session, Request or Response information, but NOT to Read the content Body
or to Write the content out directly to the connection component. }
property OnReturn:TRtcResultEvent read FOnReturn write FOnReturn;
{ When used by a client in combination with @Link(TRtcClientModule) to call remote
functions using methods provided by @Link(TRtcClientModule), this event will be
triggered if a remote Call was aborted and won't be re-sent.
The Sender parameter (server connection component) can be used to access
Session, Request or Response information, but NOT to Read the content Body
or to Write the content out directly to the connection component.
Result parameter will be NIL. }
property RequestAborted:TRtcResultEvent read FOnAborted write FOnAborted;
end;
implementation
{ TRtcFunctionList }
constructor TRtcFunctionList.Create;
begin
inherited;
FList:=TList.Create;
end;
destructor TRtcFunctionList.Destroy;
begin
FList.Free;
inherited;
end;
procedure TRtcFunctionList.Add(Value: TRtcAbsFunction);
var
idx:integer;
begin
idx:=FList.IndexOf(Value);
if idx>=0 then
FList.Delete(idx);
FList.Add(Value);
end;
function TRtcFunctionList.Count: integer;
begin
Result:=FList.Count;
end;
procedure TRtcFunctionList.Remove(Value: TRtcAbsFunction);
var
idx:integer;
begin
idx:=FList.IndexOf(Value);
if idx>=0 then
FList.Delete(idx);
end;
procedure TRtcFunctionList.RemoveAll;
begin
FList.Clear;
end;
function TRtcFunctionList.Get(index:integer): TRtcAbsFunction;
begin
if (index>=0) and (index<FList.Count) then
Result:=TRtcAbsFunction(FList.Items[index])
else
Result:=nil;
end;
{ TRtcAbsFunction }
constructor TRtcAbsFunction.Create(AOwner: TComponent);
begin
inherited;
FGroup:=nil;
FHelperGroup:=nil;
end;
destructor TRtcAbsFunction.Destroy;
begin
SetGroup(nil);
SetHelperGroup(nil);
inherited;
end;
function TRtcAbsFunction.GetGroup: TRtcFunctionGroup;
begin
Result:=FGroup;
end;
procedure TRtcAbsFunction.SetGroup(const Value: TRtcFunctionGroup);
var
MyGroup:TRtcFunctionGroup;
begin
if Value<>FGroup then
begin
if assigned(FGroup) then
begin
FGroup.RemoveFunction(self);
FGroup:=nil;
end;
if assigned(Value) then
begin
if Value=FHelperGroup then
raise Exception.Create('Can not use same Group as ParentGroup and HelperGroup.');
// Check for simple circular references before assigning!
MyGroup:=Value;
while (MyGroup<>nil) do
begin
if (MyGroup=self) or
(MyGroup.GetGroup=self) or
(MyGroup.GetHelperGroup=self) then
raise Exception.Create('Circular FunctionGroup reference!');
MyGroup:=MyGroup.ParentGroup;
end;
FGroup:=Value;
FGroup.AddFunction(self);
end;
end;
end;
function TRtcAbsFunction.GetHelperGroup: TRtcFunctionGroup;
begin
Result:=FHelperGroup;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -