2007. június 23., szombat

ISAPI FILTER* LOADER - On the fly updating of your ISAPI filter without restarting web services


Problem/Question/Abstract:

Writing Filters and updating them on the server is even more a pain in the butt than ISAPI extensions. If you are using Personal web server then that means you have to reboot your machine for every update. For IIS you have to go into config (on win2K) and restart web services. Doing so will first take too much of your time, and also will stop web traffic and visitors get an error trying to connect to your site. The main deal though is the pain of updating an ISAPI filter during development.

Answer:

My solution is identical in concept as my ISAPI extension loader. This isapi filter loader is an isapi filter that loads and calls your isapi filer. When you have an update, your isapi filter will be unloaded and the new one will be loaded... all on the fly without interupting your users.  

How to use:

Compile or use the already compiled version of this DLL and rename it to the same name as your existing filter.

Now - rename your existing filter with a .run extension. The loader will look for this file and will load it.

Thats all, but now for the update part. When you have an update, you change the extension of your new filter to .update. The loader will look for this file and if it is found, then will unload the .run file, rename it to .backup then rename the .update to .run then load the new .run filter.

If you already had a .backup then it will be overwritten.

If you need to revert back for some reason then simply rename the .backup to .update.

The performance hit of this loader is very small I think.

One thing this loader does do, it registers most all events with the server then calls your filter only with the events you specified.


Source Listing
3 units.

FilterLoader.dpr - Main project file
EggFilterLoader.pas - The update engine.
Fn_GetModuleName.pas - Utility to return the name of the module.

FilterLoader.dpr

library FilterLoader;
{
  Author
    William Egge
    egge@eggcentric.com

  Version 1.0
  Original FileName FilterLoader.dpr
  Date: Sep 9, 2001
  Website http://www.eggcentric.com/ISAPIFilterLoader.htm
  This source code is free to distribute and modify.

  This is the Filter Loader DLL main project file.  The applications
  intention is to be a loader for ISAPI filters to reduce development time
  and headache of updating your isapi filters.

  Check my website at http://www.eggcentric.com/ISAPIFilterLoader.htm
  for updates or further explaining.
}

uses
  ISAPI2,
  Windows,
  EggFilterLoader;

{$R *.RES}
var
  GEggFilterLoader: IEggFilterLoader = nil;

function GetFilterVersion(var pVer: HTTP_FILTER_VERSION): BOOL; export; stdcall;
begin
  try
    GEggFilterLoader := nil; // Free prev if any
    GEggFilterLoader := CoCreateEggFilterLoader;
    Result := GEggFilterLoader.GetFilterVersion(pVer);
  except // Dont crash IIS
    Result := False;
  end;
end;

function HttpFilterProc(var pfc: THTTP_FILTER_CONTEXT;
  NotificationType: DWORD;
  pvNotification: Pointer): DWORD; export; stdcall;
begin
  try
    Result := GEggFilterLoader.HttpFilterProc(pfc, NotificationType, pvNotification);
  except // Dont crash IIS
    Result := SF_STATUS_REQ_NEXT_NOTIFICATION;
  end;
end;

exports
  GetFilterVersion,
  HttpFilterProc;

begin
end.

EggFilterLoader.pas

unit EggFilterLoader;
{
  Author
    William Egge
    egge@eggcentric.com

  Version 1.0
  Original FileName EggFilterLoader.pas
  Date: Sep 9, 2001
  Website http://www.eggcentric.com/ISAPIFilterLoader.htm
  This source code is free to distribute and modify.

  This is the core updating part of my isapi filter loader.  Its purpose
  is to check for updates of a new isapi filter then unload the current
  one and load the new one.  To use, simply Create it by calling the
  function CoCreateEggFilterLoader then your main isapi filter
  application should forward all extension calls to the object.
}

interface
uses
  ISAPI2,
  Fn_GetModuleName,
  SysUtils,
  Windows;

type
  IEggFilterLoader = interface
    function GetFilterVersion(var pVer: HTTP_FILTER_VERSION): BOOL;
    function HttpFilterProc(var pfc: THTTP_FILTER_CONTEXT;
      NotificationType: DWORD;
      pvNotification: Pointer): DWORD;
  end;

function CoCreateEggFilterLoader: IEggFilterLoader;

implementation
const
  // This is the time that must pass between update checks
  WAIT_BEFORE_CHECK = 10000; // 10 seconds

  SF_NOTIFY_SEND_RESPONSE = $00000040;
  SF_NOTIFY_END_OF_REQUEST = $00000080;
  ALL_FLAGS =
    //       SF_NOTIFY_READ_RAW_DATA
      {or} SF_NOTIFY_PREPROC_HEADERS
  or SF_NOTIFY_URL_MAP
    or SF_NOTIFY_AUTHENTICATION
    or SF_NOTIFY_ACCESS_DENIED
    or SF_NOTIFY_SEND_RESPONSE
    //    or SF_NOTIFY_SEND_RAW_DATA
  or SF_NOTIFY_END_OF_REQUEST
    or SF_NOTIFY_LOG
    or SF_NOTIFY_END_OF_NET_SESSION
    or SF_NOTIFY_ORDER_DEFAULT
    or SF_NOTIFY_SECURE_PORT
    or SF_NOTIFY_NONSECURE_PORT;

type
  TEggFilterLoader = class(TInterfacedObject, IEggFilterLoader)
  private
    FLastTimeCheck: LongWord;
    FCheckSync: TMultiReadExclusiveWriteSynchronizer;
    FDLLSync: TMultiReadExclusiveWriteSynchronizer;
    FDLL: HModule;
    FCallbackVersion: TGetFilterVersion;
    FCallbackProc: THttpFilterProc;
    FBackupDLLName, FRunDLLName, FUpdateDLLName: string;
    FFilterFlags: DWord;
    procedure ReloadDLL;
    procedure DoUpdateIfNeeded;
  public
    constructor Create;
    destructor Destroy; override;
    function GetFilterVersion(var pVer: HTTP_FILTER_VERSION): BOOL;
    function HttpFilterProc(var pfc: THTTP_FILTER_CONTEXT;
      NotificationType: DWORD;
      pvNotification: Pointer): DWORD;
  end;

function CoCreateEggFilterLoader: IEggFilterLoader;
begin
  Result := TEggFilterLoader.Create;
end;

{ TEggFilterLoader }

constructor TEggFilterLoader.Create;
var
  ThisModule: string;
begin
  inherited Create;
  FDLLSync := TMultiReadExclusiveWriteSynchronizer.Create;
  FCheckSync := TMultiReadExclusiveWriteSynchronizer.Create;
  ThisModule := GetModuleName;
  FBackupDLLName := ChangeFileExt(ThisModule, '.backup');
  FRunDLLName := ChangeFileExt(ThisModule, '.run');
  FUpdateDLLName := ChangeFileExt(ThisModule, '.update');
end;

destructor TEggFilterLoader.Destroy;
begin
  // unload DLL
  if FDLL <> 0 then
    FreeLibrary(FDLL);
  FDLLSync.Free;
  FCheckSync.Free;
  inherited;
end;

procedure TEggFilterLoader.DoUpdateIfNeeded;
var
  NeedCheck, NeedLoad: Boolean;
begin
  // Quick Check
  FCheckSync.BeginRead;
  try
    NeedCheck := (GetTickCount - FLastTimeCheck) >= WAIT_BEFORE_CHECK;
  finally
    FCheckSync.EndRead;
  end;

  if NeedCheck then
  begin
    FCheckSync.BeginWrite;
    try
      // Recheck in case another thread has updated
      FDLLSync.BeginRead;
      try
        NeedCheck := (FDLL = 0) or ((GetTickCount - FLastTimeCheck) >=
          WAIT_BEFORE_CHECK);
      finally
        FDLLSync.EndRead;
      end;

      if NeedCheck then
      begin
        FLastTimeCheck := GetTickCount;
        FDLLSync.BeginRead;
        try
          NeedLoad := (FDLL = 0) or FileExists(FUpdateDLLName);
        finally
          FDLLSync.EndRead;
        end;
        if NeedLoad then
          ReloadDLL;
      end;
    finally
      FCheckSync.EndWrite;
    end;
  end;
end;

function TEggFilterLoader.GetFilterVersion(
  var pVer: HTTP_FILTER_VERSION): BOOL;
begin
  DoUpdateIfNeeded;
  FDLLSync.BeginRead;
  try
    pVer.dwFilterVersion := MakeLong(0, 1);
    pVer.lpszFilterDesc := 'Eggcentric Filter Loader.';
    pVer.dwFlags := ALL_FLAGS;
    Result := True;
  finally
    FDLLSync.EndRead;
  end;
end;

function TEggFilterLoader.HttpFilterProc(var pfc: THTTP_FILTER_CONTEXT;
  NotificationType: DWORD; pvNotification: Pointer): DWORD;
begin
  DoUpdateIfNeeded;
  FDLLSync.BeginRead;
  try
    // Check Notification bit to make sure the DLL should be called
    if Assigned(FCallbackProc) and ((NotificationType and FFilterFlags) <> 0) then
      Result := FCallbackProc(pfc, NotificationType, pvNotification)
    else
      Result := SF_STATUS_REQ_NEXT_NOTIFICATION;
  finally
    FDLLSync.EndRead;
  end;
end;

procedure TEggFilterLoader.ReloadDLL;
var
  ShouldReload: Boolean;
  pVer: THTTP_FILTER_VERSION;
begin
  FDLLSync.BeginWrite;
  try
    // First Determine if we really should
    ShouldReload := (FDLL = 0) or FileExists(FUpdateDLLName);
    if ShouldReload then
    begin
      // First unload the DLL
      if FDLL <> 0 then
      begin
        FreeLibrary(FDLL);
        FDLL := 0;
        FCallbackVersion := nil;
        FCallbackProc := nil;
      end;

      // check for update file, if exists then rename things;
      if FileExists(FUpdateDLLName) then
      begin
        SysUtils.DeleteFile(FBackupDLLName);
        RenameFile(FRunDLLName, FBackupDLLName);
        RenameFile(FUpdateDLLName, FRunDLLName);
      end;

      // Now load the .run file if it exists
      if FileExists(FRunDLLName) then
      begin
        FDLL := LoadLibrary(PChar(FRunDLLName));
        if FDLL <> 0 then
        begin
          FCallbackVersion := GetProcAddress(FDLL, 'GetFilterVersion');
          FCallbackProc := GetProcAddress(FDLL, 'HttpFilterProc');
          if Assigned(FCallbackVersion) then
          begin
            FCallbackVersion(pVer);
            FFilterFlags := pVer.dwFlags;
          end
          else
            FFilterFlags := 0;
        end;
      end;
    end;
  finally
    FDLLSync.EndWrite;
  end;
end;

end.

Fn_GetModuleName.pas

unit Fn_GetModuleName;
{
  Author
    William Egge
    egge@eggcentric.com

  Version 1.0
  Original FileName Fn_GetModuleName.pas
  Date: Sep 9, 2001
  Website http://www.eggcentric.com
  This source code is free to distribute and modify.

  Very simple function, it returns the full path and file name of the module
  it is running in.
}

interface
uses
  Windows;

function GetModuleName: string;

implementation

function GetModuleName: string;
var
  FileName: array[0..MAX_PATH] of char;
begin
  FillChar(FileName, SizeOf(FileName), #0);
  GetModuleFileName(HInstance, FileName, SizeOf(FileName));
  Result := FileName;
end;

end.


Component Download: http://www.eggcentric.com/Download/ISAPIFilterLoaderSource.zip

Nincsenek megjegyzések:

Megjegyzés küldése