rtcisapi.pas

来自「Delphi快速开发Web Server」· PAS 代码 · 共 475 行

PAS
475
字号
{
  @html(<b>)
  ISAPI Extensions Provider
  @html(</b>)
  - Copyright (c) Danijel Tkalcec
  @html(<br><br>)

  This unit implements methods for executing ISAPI extensions received with @Link(TRtcDataServer).
}
unit rtcISAPI;

{$INCLUDE rtcDefs.inc}

interface

uses
  Windows, SysUtils, Classes,

  isapi2, ComObj, ActiveX, // needed for ISAPI

  rtcLog, rtcSyncObjs,
  rtcInfo, rtcConn,
  rtcDataSrv,

  rtcThrPool;

type
  {@abstract(ISAPI Extensions loader and executer)
   TRtcISAPI declares "class methods" for executing ISAPI extensions
   received as requests for TRtcDataServer. }
  TRtcISAPI=class
  public
    // Start up ISAPI extensions provider: call for "OnListenStart"!
    class procedure StartUp;
    // Shut down ISAPI exensions provider: call for "OnListenStop"!
    class procedure ShutDown;

    // Unload all ISAPI extensions (when needed)
    class procedure UnLoadAll;

    { @html(
      Execute ISAPI Request: Call for "OnDataReceived" event! <br><br>

      Preconditions: <br>
      Con.Request.Info['ROOT'] = Document Root for this Host <br>
      Con.Request.Info['DLL'] = ISAPI DLL File name with full path <br>
      Con.Request.Info['PATH'] = "Path" defined behind the ISAPI DLL file name) }
    class procedure Execute(Con:TRtcDataServer);
    end;

implementation

type
  TIsapi = class
  public
    FName:string;
    init:boolean;
    Handle:THandle;
    Term:TTerminateExtension;
    ExtProc:THttpExtensionProc;
    ExtVer:TGetExtensionVersion;
    end;

  TRtcECB=class(TRtcObject)
  public
    ecb:PEXTENSION_CONTROL_BLOCK;

    constructor Create;
    destructor Destroy; override;

    procedure Kill; override;
    end;

  TIsapiDLLs = class
  private
    FList:TStringList;
    CS:TRtcCritSec;

  public
    constructor Create;
    destructor Destroy; override;

    function Get(FileName:string):TIsapi;
    procedure Close(isapi:TIsapi);

    procedure CloseAll;
  end;

var
  IsapiDLLs:TIsapiDLLs;
  Isapi_Ready:boolean=False;

{ TRtcECB }

constructor TRtcECB.Create;
  begin
  inherited;
  New(ecb);
  FillChar(ecb^, sizeof(ecb^), 0);
  end;

destructor TRtcECB.Destroy;
  begin
  FreeMem(ecb);
  inherited;
  end;

procedure TRtcECB.Kill;
  begin
  Free;
  end;

constructor TIsapiDLLs.Create;
  begin
  inherited;
  FList:=TStringList.Create;
  FList.Sorted:=True;
  CS:=TRtcCritSec.Create;
  end;

destructor TIsapiDLLs.Destroy;
  begin
  CloseAll;
  FList.Free;
  CS.Free;
  inherited;
  end;

function TIsapiDLLs.Get(FileName:string):TIsapi;
  var
    i:integer;
    h:THandle;
  begin
  FileName:=UpperCase(FileName);
  CS.Enter;
  try
    i:=FList.IndexOf(FileName);
    if i>=0 then
      Result:=TIsapi(FList.Objects[i])
    else
      begin
      XLog('----------------------------------------------------------'#13#10+
           'Loading ISAPI Extension "'+FileName+'" ...','ISAPI');

      h:=LoadLibrary(PChar(FileName));
      if h=0 then
        begin
        XLog('Error loading ISAPI Extension "'+FileName+'".','ISAPI');
        Result:=nil;
        Exit;
        end;

      Result:=TIsapi.Create;
      Result.Handle:=h;
      Result.FName:=FileName;
      Result.Init:=False;
      Result.ExtVer:=GetProcAddress(h, 'GetExtensionVersion');
      Result.ExtProc:=GetProcAddress(h, 'HttpExtensionProc');
      Result.Term:=GetProcAddress(h, 'TerminateExtension');

      FList.AddObject(FileName, Result);

      XLog('ISAPI Extension "'+FileName+'" loaded.','ISAPI');
      end;
  finally
    CS.Leave;
    end;
  end;

procedure TIsapiDLLs.Close(isapi:TIsapi);
  var
    Buff:array [0..64] of char;
  begin
  CS.Enter;
  try
    if Assigned(isapi.Term) then
      begin
      XLog('Asking ISAPI Extension "'+isapi.FName+'" to terminate ...','ISAPI');
      isapi.Term(0);
      end;
    if GetModuleFileName(isapi.Handle, Buff,63)>0 then
      FreeLibrary(isapi.Handle);
    isapi.Free;
  finally
    CS.Leave;
    end;
  end;

procedure TIsapiDLLs.CloseAll;
  var
    i:integer;
  begin
  CS.Enter;
  try
    for i:=0 to FList.Count-1 do
      Close(TIsapi(FList.Objects[i]));
    FList.Clear;
  finally
    CS.Leave;
    end;
  end;

function GetServerVariable(hConn: HCONN; VariableName: PChar; Buffer: Pointer; var Size: DWORD ): BOOL stdcall;
  var
    Con : TRtcDataServer;
    i : integer;
    s,v,vname : string;
    len: dword;
  begin
  con := TRtcDataServer(hConn);
  v:=UpperCase(VariableName);
  s:='';

  if v='SERVER_SOFTWARE' then
    s:='www.realthinclient.com'
  else if v='SERVER_SIGNATURE' then
    s:='RealThinClient Library (c) Danijel Tkalcec'
  else if v='SERVER_PROTOCOL' then
    s:='HTTP/1.1'
  else if v='REQUEST_METHOD' then
    s:=con.Request.Method
  else if v='QUERY_STRING' then
    s:=con.Request.Query.Text
  else if (v='REQUEST_URI') or (v='HTTP_URL') then
    s:=con.Request.URI
  else if (v='SCRIPT_FILENAME') or (v='SCRIPT_NAME') or (v='SELF') or (v='URL') then
    s:=con.Request.FileName
  else if (v='SERVER_NAME') then
    s:=con.Request.Host
  else if (v='SERVER_ADDR') then
    s:=con.LocalAddr
  else if (v='SERVER_PORT') then
    s:=con.LocalPort
  else if (v='REMOTE_ADDR') or (v='REMOTE_HOST') or (v='REMOTE_USER') then
    s:=con.PeerAddr
  else if (v='REMOTE_PORT') then
    s:=con.PeerPort
  else if (v='ALL_RAW') then
    s:=con.Request.HeaderText
  else if (v='ALL_HTTP') then
    begin
    s:='';
    for i:=0 to con.Request.ItemCount-1 do
      begin
      vname:=StringReplace(con.Request.ItemName[i],'-','_',[rfReplaceAll]);
      s:=s+'HTTP_'+vname+':'+con.Request.ItemValue[i]+#13#10;
      end;
    end
  else if (v='CONTENT_LENGTH') then
    s:=con.Request['CONTENT-LENGTH']
  else if (v='CONTENT_TYPE') then
    s:=con.Request['CONTENT-TYPE']
  else if (v='DOCUMENT_ROOT') then
    s:=con.Request.Info['ROOT']
  else
    begin
    for i:=0 to con.Request.ItemCount-1 do
      begin
      vname:=StringReplace(con.Request.ItemName[i],'-','_',[rfReplaceAll]);
      if v='HTTP_'+vname then
        begin
        s:=con.Request.ItemValue[i];
        Break;
        end;
      end;
    end;

  len:=length(s);

  if s='' then
    begin
    Result:=False;
    Size:=0;
    XLog('GetServerVariable('+VariableName+') = <empty>','ISAPI');
    end
  else if Size<=len then
    begin
    Result:=False;
    Size:=length(s)+1;
    XLog('GetServerVariable('+VariableName+') = <buffer too small>','ISAPI');
    end
  else
    begin
    Result:=True;
    Size:=length(s)+1;
    StrPCopy(PChar(Buffer),s);
    XLog('GetServerVariable('+VariableName+')="'+s+'"','ISAPI');
    end;
  end;

function WriteClient(ConnID: HCONN; Buffer: Pointer; var Bytes: DWORD; dwReserved: DWORD ): BOOL stdcall;
  var
    con : TRtcDataServer;
    s:string;
  begin
  con := TRtcDataServer(ConnID);
  SetString(s,PChar(Buffer),Bytes);
  con.Write(s);
  Result := True;
  end;

function ReadClient(ConnID: HCONN; Buffer: Pointer; var Size: DWORD): BOOL stdcall;
  begin
  Result:=False;
  end;

function ServerSupport(hConn: HCONN; HSERRequest: DWORD; Buffer: Pointer; Size: LPDWORD; DataType: LPDWORD ): BOOL stdcall;
  var
    con: TRtcDataServer;
    s:string;
  begin
  con:=TRtcDataServer(hConn);
  case HSERRequest of
    HSE_REQ_SEND_RESPONSE_HEADER:
      begin
      if Buffer<>nil then
        begin
        s:=PChar(Buffer);
        con.Response.Status(s);
        end;
      if DataType<>nil then
        begin
        s:=PChar(DataType);
        con.Response.HeaderText:=s;
        XLog('ServerSupport(HSE_REQ_SEND_RESPONSE_HEADER):'#13#10+s,'ISAPI');
        end;
      con.WriteHeader;
      Result := True;
      end;
    HSE_REQ_SEND_URL_REDIRECT_RESP:
      begin
      if Buffer<>nil then
        begin
        s:=PChar(Buffer);
        con.Response.StatusCode:=302;
        con.Response.StatusText:='Moved Termporarily';
        con.Response['LOCATION']:= s;
        con.WriteHeader;
        XLog('ServerSupport(HSE_REQ_SEND_URL_REDIRECT_RESP):'+s,'ISAPI');
        Result := True;
        end
      else
        Result:=False;
      end;
    HSE_REQ_DONE_WITH_SESSION:
      begin
      if con.Response['CONTENT-LENGTH']='' then
        begin
        con.Disconnect;
        XLog('ServerSupport(HSE_REQ_DONE_WITH_SESSION): No content length, DISCONNECTING!','ISAPI');
        end
      else
        XLog('ServerSupport(HSE_REQ_DONE_WITH_SESSION): Content Length='+IntToStr(con.Response.ContentLength)+'; Out='+IntToStr(con.Response.ContentOut),'ISAPI');
      Result:=True;
      end;
    else
      begin
      XLog('ServerSupport('+IntToStr(HSERRequest)+') - UNSUPPORTED FUNCTION!','ISAPI');
      Result := False;
      end;
    end;
  end;

class procedure TRtcISAPI.Execute(Con:TRtcDataServer);
  var
    isapi : Tisapi;
    Ver   : THSE_VERSION_INFO;
    FName : string;
  begin
  if not Con.Request.Complete then Exit;
  try
    FName:=Con.Request.Info['DLL'];

    isapi:=IsapiDLLs.Get(FName);
    if isapi=nil then
      begin
      con.Response.Status(404,'Not found');
      con.Write('Error loading ISAPI Extension "'+FName+'".');
      Exit;
      end;

    if assigned(isapi.ExtVer) then
      begin
      if not isapi.init then
        begin
        isapi.ExtVer(Ver);
        isapi.init:=True;
        XLog('----------------------------------------------------'#13#10+
             'InitDLL('+FName+'): '+Con.Request.Host+' > '+Con.Request.URI+' (Ver '+
             IntToStr(Ver.dwExtensionVersion shr 16)+','+inttostr(Ver.dwExtensionVersion and $FFFF)+' '+
             StrPas(Ver.lpszExtensionDesc)+')',
             'ISAPI');
        end;
      end;

    if not assigned(isapi.ExtProc) then
      begin
      con.Response.Status(500,'ISAPI Extension error');
      con.Write('Unable to find ISAPI Extension Procedure.');
      Exit;
      end;

    Con.Request.Info.Obj['ECB']:=TRtcECB.Create;

    with TRtcECB(Con.Request.Info.Obj['ECB']) do
      begin
      FillChar(ecb^, sizeof(ecb^), 0);
      ecb^.cbSize                := sizeof(ecb);
      ecb^.dwVersion             := HSE_VERSION_MAJOR shl 16 + HSE_VERSION_MINOR;
      ecb^.ConnID                := THandle(Con);
      ecb^.dwHttpStatusCode      := Con.Response.StatusCode;
      ecb^.lpszMethod            := PChar(Con.Request.Method);

      ecb^.lpszQueryString       := PChar(Con.Request.Query.Text);
      ecb^.lpszPathInfo          := PChar(Con.Request.Info.asString['PATH']);
      ecb^.lpszPathTranslated    := PChar(Con.Request.Info.asString['PATH']);

      ecb^.lpbData               := PChar(con.Read);
      ecb^.cbAvailable           := con.Request.ContentIn;
      ecb^.cbTotalBytes          := Con.Request.ContentIn;

      ecb^.GetServerVariable     := GetServerVariable;
      ecb^.WriteClient           := WriteClient;
      ecb^.ReadClient            := ReadClient;
      ecb^.ServerSupportFunction := ServerSupport;

      XLog('Execute('+FName+'): '+con.Request.Method+' '+con.Request.URI+#13#10+
           Con.Request.HeaderText,
           'ISAPI');
      isapi.ExtProc(ecb^);
      end;
  except
    on E:Exception do
      begin
      Con.Response.Status(200,'Internal ISAPI Error');
      Con.Write(#13#10+E.Message);
      XLog('Internal Exception in ISAPI "'+Con.Request.FileName+'": '+E.Message,'ISAPI');
      end;
    end;
  end;

class procedure TRtcIsapi.StartUp;
  begin
  if not Isapi_Ready then
    begin
    Isapi_Ready:=True;
    IsapiDLLs:=TIsapiDLLs.Create;
    end;
  end;

class procedure TRtcIsapi.ShutDown;
  begin
  if Isapi_Ready then
    begin
    Isapi_Ready:=False;
    if assigned(IsapiDLLs) then
      begin
      IsapiDLLs.Free;
      IsapiDLLs:=nil;
      end;
    end;
  end;

class procedure TRtcISAPI.UnLoadAll;
  begin
  if Isapi_Ready then
    IsapiDLLs.CloseAll;
  end;

initialization
CoInitFlags:=COINIT_MULTITHREADED; // needed for ISAPI
finalization
TRtcIsapi.ShutDown;
end.

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?