Link to home
Start Free TrialLog in
Avatar of heathprovost
heathprovostFlag for United States of America

asked on

Need some expert advice...

Hello all. I have developed an ISAPI filter dll for IIS using Delphi 6. The filter will be running on a server we use for development purposes (my company is a website development firm). Its purpose is to remap URLs to sort of emulate host-header redirection without having to set it up manually on the web server. Basically, if you type in:

http://something.ourdomain.com/

the filter will modify the request to be

http://www.ourdomain.com/something/

The filter works as expected, but I am curious about a few things that are mostly Delphi specific (and have nothing really to do with filter dlls):

1. What does the global variable isMultiThreaded actually do and do I need to set it to true?  I have read that it is required by the memory manager to be set to true in multi-threaded apps, but is it necessary in something like this? Although my code is not multi-threaded, the dll can be called by multiple threads at the same time, so I am not sure if that is the same thing as far as the memory manager goes.

2. My code has to make a few calls involving passing some local variables as pchars... Which is the best way to do this.

var
  myBuffer: array [0..MAX_BUFFER] of char;
  buffSize: integer;
begin
  buffSize := MAX_BUFFER;
  GetApiProc(@myBuffer, @buffSize) //procedure expects pchar and pointer to an integer.
  //do something with myBuffer
  SetApiProc(@myBuffer);
end;

OR

var
  myBuffer: string;
  buffSize: integer;
begin
  buffSize := MAX_BUFFER;
  setLength(myBuffer, buffSize);
  GetApiProc(pchar(myBuffer), @buffSize) //procedure expects pchar and pointer to an integer.
  //do something with myBuffer
  uniquestring(buffSize);
  SetApiProc(pchar(myBuffer));
end;

or is there another way that is better... My goal here is for this routine to go as fast as possible, and I need to reduce the number of memory allocations to the bare minimum... So basically my question is are string types slower to allocate than character arrays???


Any other advice on getting this to run as fast as possible would be greatly appreciated.

Heath


 
Avatar of heathprovost
heathprovost
Flag of United States of America image

ASKER

Here is the source code for my filter if it helps... The ISAPIFilter.pas file is just type declarations and stuff, this is all the code.


library hhrfilter;

uses
  SysUtils,
  Windows,
  ISAPIFilters;

const
  C_BETA : pchar         = 'beta'#0;
  C_STAGE : pchar        = 'stage'#0;
  C_URLHEADER : pchar    = 'url'#0;
  C_SERVER_NAME          = 'SERVER_NAME';
  C_REMOTE_ADDR          = 'REMOTE_ADDR';
  C_FILTER_DESC          = 'TGG Host Redirect Filter, v1.0';
  C_MAX_URL_LEN          = 4096;

function GetFilterVersion(var pVer : HTTP_FILTER_VERSION) : BOOL; export; stdcall;
begin
  try
    pVer.dwFilterVersion := makelong(HTTP_FILTER_MINOR,HTTP_FILTER_MAJOR);
    pVer.dwFlags := (    SF_NOTIFY_NONSECURE_PORT
                      or SF_NOTIFY_PREPROC_HEADERS
                      or SF_NOTIFY_ORDER_DEFAULT
                    );
    StrPCopy(pVer.lpszFilterDesc, C_FILTER_DESC);
    result := True;
  except
    result := False;
  end;
end;

function HttpFilterProc( var pfc          : HTTP_FILTER_CONTEXT;
                         NotificationType : DWORD;
                         pvNotification   : LPVOID)
                         : DWORD; export; stdcall;
var
  pHeaders               : HTTP_FILTER_PREPROC_HEADERS;
  filterHost             : string;
  filterUrl              : string;
  returnUrl              : string;
  getSrvVar              : TGetServerVariable;
  i                      : Cardinal;

begin
  case NotificationType of
  SF_NOTIFY_PREPROC_HEADERS:
    begin
      try
        pHeaders := HTTP_FILTER_PREPROC_HEADERS(pvNotification^);
        i := C_MAX_URL_LEN;
        setlength(filterUrl, i);
        pHeaders.GetHeader(pfc, C_URLHEADER, pchar(filterUrl), @i);
        i := C_MAX_URL_LEN;
        getSrvVar := pfc.GetServerVariable;
        setlength(filterHost, i);
        GetSrvVar(pfc, C_SERVER_NAME, pchar(filterHost), @i);
        i := pos('.', filterHost);
        if i <> 0 then
        begin
          returnUrl := '/' + copy(filterHost, 1, i - 1) + filterUrl;
          uniquestring(returnUrl);
          pHeaders.SetHeader(pfc, C_URLHEADER, pchar(returnUrl));
        end;
        result := SF_STATUS_REQ_NEXT_NOTIFICATION;
      except
        result := SF_STATUS_REQ_ERROR;
      end;
    end;
  else
    result := SF_STATUS_REQ_NEXT_NOTIFICATION;
  end;
end;

exports
  HttpFilterProc,
  GetFilterVersion;

begin
  IsMultiThread := true;
end.
hello heathprovost, I don't think that IsMultithread needs to be set to anything, you can read it to see if there has been another thread created. I also think that a static array like
myBuffer: array [0..MAX_BUFFER] of char;
will be faster, and less cpu time, because array size does not need to be allocated. If you are a performance freak then you should look at the API QueryPerformanceCounter to get real small time measurements.


var
PerFreq, PerCount2, PerCount3: Int64;


if not QueryPerformanceFrequency(PerFreq) then
PerFreq := 0;
{QueryPerformanceFrequency gets the number of times a second
this PerformanceCounter ticks, it can be above a million ticks
per second}
QueryPerformanceCounter(PerCount2);

//test code goes here
  buffSize := MAX_BUFFER;
 setLength(myBuffer, buffSize);
 GetApiProc(pchar(myBuffer), @buffSize) //procedure expects pchar and pointer to an integer.
 //do something with myBuffer
 uniquestring(buffSize);
 SetApiProc(pchar(myBuffer))

        {the PerformanceCounter is a very fast high-resolution counter
        which can be used to find out the amount of time between 2 calls
        to QueryPerformanceCounter( )}
QueryPerformanceCounter(PerCount3);


Time := Int2Str(((PerCount3-PerCount2)*10000) div PerFreq);
        Insert('.',Time,Length(Time));
TextOut(FormDC,6,ScrolRect1.Top+6,PChar(Time), Length(Time));
        {this will show the milliseconds between the PerCount2 and PerCount3}

I forgot to add the test
if PerFreq <> 0 then Exit;
Thank you for the response Slick812. I will go ahead and test the code as you suggested. Anyone else interested in commenting, please do so. I will post points for anyone who gives me any relevant and useful info.
Slick812, As I want to leave this question open, you can pick up your points here

https://www.experts-exchange.com/jsp/qManageQuestion.jsp?ta=delphi&qid=20315819
Avatar of 333
333

1. unless you call in your dll BeginThread, IsMultiThread will always be false, even if your dll was called from different threads of same process.  when IsMultiThread is true, delphi's memory manager locks other threads while one of them is using memory manager. so even if you set IsMultiThread to true, it won't do anything in your case, because there is only one thread.
2. using static arrays will be faster.
Thanks 333. I follow your explanation, but what I am really getting at is do I need to worry about the memory manager screwing up when being called from multiple threads? Since it will NOT be doing any kind of locking internally is there potential for something to go wrong? The reason I set IsMultiThread to true was I was hoping that this would FORCE the memory manager to act like it was being called from seperate threads... If setting it doesnt achieve the desired affect (making the heap thread-safe), what would, or do I even need to worry about it?
somehow i missed earlier that you are using strings.
dll uses calling thread's stack, but allocates memory from process address space. so there may be a problem when two threads called your dll at the same time and you are using memory manager.
memory manager is used for: GetMem, ReallocMem, FreeMem, New and Dispose, allocation and deallocation of objects, allocation and deallocation of dynamic strings. in your case you should worry about dynamic strings. you need to set IsMultiThread to true.

also, i found this article http://community.borland.com/article/0,1410,15657,00.html , that  says you need to set IsMultiThread:=True in ISAPI dll, because otherwise it won't handle multiple connections.
1. I don't agree with other comment telling you not to set IsMultiThreaded to true. You need to set IsMultiThreaded to true, since your code will actually run in multiple treads (even if the tread were not created by your DLL). This flag enables the global lock used by the memory manager, which is needed to avoid corruption of memory manager structures when concurrent memory operations occur. Another alternative may be the use of a thread-safe replacement memory manager.

2. This depends on the actual API call. Some API calls (especially in ISAPI) return the minimum size of the buffer needed when an operation fails. In such a case, the SetLength method is better to use. However, if you know the max length needed and the buffer is not more than about 2k (half of one memory page on the stack), is a little faster because is does do less calls to the memory manager (which is usually slow).

If you need some assistance with ISAPI filter issues, let me know, I have written several filters and may be able to help out.
Thanks AvonWyss. That makes alot more sense to me. Since you mentioned that you have written filters before, I have discovered an issue with mine...

If you look at my code, you will see that I am intercepting SF_NOTIFY_PREPROC_HEADERS in order to rewrite the url during a request. Although this works as intended, the one issue we are having is that the PATH_INFO variable shows the url path as it is after I have modified it.  Since we use Cold Fusion and sometimes use this varaible in order to determine the current url path for the purpose of creating links, this would create problems because in that case we need to create the link using the munged path. I decided the easiest way to deal with this was to create a new header using addHeader. I called it HTTP_PATH_INFO_MUNGED and it is supposed to contain the url as the client should see it.  The problem is what do I do when the request involves accessing the default document?  if the request is for http://something.ourdomain.com/, my code will rewrite it to http://www.something.ourdomain.com/, but at some later point the server adds "index.cfm" to that path since that is the default document.  I need to also update this value in the HTTP_PATH_INFO_MUNGED variable, but cant seem to figure out a way to do it....

Heath
Heath,

interesting enough, I have also done an URL remapper (which loads its patters from a database and allows multiple replacements with conditions, variables etc. in the URL).

The best way to prevent the problem you describe is to write your pages and scripts in such a matter that they use and emit (to the browser in the HTTP document) relative paths exclusively. Becuase, when you are using relative paths, the browser will rebuild the new URLs for you without ever knowing anything about the mungling of the URLs, thus always requesting the correct URLs.

You could also hook the SF_NOTIFY_URL_MAP notification. Cold Fusion will use the server's NormalizeUrl server support call to resolve virtual paths to physical ones. This call will trigger a URL_MAP event, which you could "abuse" to react in certain conditions to return a reverse-mungled (*g*) path, like, whtn the URL starts with "&" or any other char unlikely ever used in a real path.

Note that you have to be careful in the PREPROC_HEADERS event. To this point, the server has already done some work even though the event could lead to the assumtion that this is not the case. In fact, the site/host ID has already been determined, and I think to remember that default documents also already have been applied if found on the given path. I'd suggest to use the Event Log or a log file to log debug information which you can then examine without the hassle of real-time debugging and it's difficulties in conjunction with IIS.
Also, by the way, when you declare constant PChars, the trailing #0 is implicitly added by Delphi and thus not needed explicitly. In fact, you can even supply a constant string right away when a PChar is requested as parameter. Also, you don't need to call UniqueString before a cast when the PChar will not be modified (read-only). Also, every string modification will create a unique string (concat, copy, setlength etc.), so that you only have to use UniqueString on strings which have not been assinged to or from (!) after being altered.

I have written pretty smart (I think) wrapper classes for filters, which allow me to concentrate on the important stuff while keeping my code clean, safe (excpetions are handled etc.) and still fast. A copy of your filter would look about like this with my wrapper:

{URL Mapping}

library HHRFilter;

{$R *.RES}

uses
     Windows,
     SysUtils,
     BSISAPIFilters;

type
     TWebMappingGroup=class(TISAPIFilterGroup)
     public
          procedure Install; override;
     end;
     TPreProcFilter=class(TISAPIPreprocHeadersFilter)
     public
          procedure Execute(var FilterContext: Pointer); override;
     end;

{ TWebMappingGroup }

procedure TWebMappingGroup.Install;
begin
     Install('URL Mapping Filter', 1.0, [TPreProcFilter], fpBoth, fpLow);
end;

{ TPreProcFilter }

procedure TPreProcFilter.Execute(var FilterContext: Pointer);
var
     FilterHost: string;
     I: Integer;
begin
     FilterHost:=GetServerVariable('Host');
     I:=Pos('.', FilterHost);
     if I>0 then
          SetHeader('URL', '/' + Copy(FilterHost, 1, I-1) + GetHeader('URL'));
end;

begin
     InstallFilter:=TWebMappingGroup;
end.

Such a wrapper does make your code much easier to use and support. All the initialisations, entry-point and callback functions etc. are done in the wrapper. In my model, every kind of filter has its own class type with all the necessary declarations (like, GetHeader, SetHeader, DeleteHeader, GetServerVariable, ExitNotificationHandled, ExitAccessDenied, ExitAuthenticationRequired, etc.). Also, I have added support for all new notifications up to IIS5 (the ISAPI file which ships with Delphi is from IIS3 headers I think).
Thanks for the comments AvonWyss. As for the url thing, unfortunately my design goals require this filter to work regardless of how the pages are coded.  Baically, what I am trying to do is emulate host-header redirection, and I need it to act for the most part the same way as IIS's internal mechanism. I realize their are issues with doing it this way, such as logging and stuff, but logging isnt required on the server this will be running on.

I will try as you said to hook the URL_MAP stuff and see if that does the trick. I think my solution will have to involve both hooks...

Also, thanks for the tips with strings and such. I am not at all familiar with using them as pchars like this, that helped alot.

Your wrapper sounds nice, but I think I prefer doing it "by-hand" at this point. It helps me learn the details of what I am doing a bit better. Thanks for the help.  
ASKER CERTIFIED SOLUTION
Avatar of AvonWyss
AvonWyss
Flag of Switzerland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial