⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 rtcfunction.pas

📁 Delphi快速开发Web Server
💻 PAS
📖 第 1 页 / 共 2 页
字号:
{
  @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 + -