Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Apache and Script Mapping


:P
On this page:

I've been struggling with mapping scripts to a custom ISAPI handler in Apache for some  in West Wind Web Connection (which is a FoxPro based ISAPI interface). West Wind Web Connection works with Apache, but recently with version 2.2 Apache changed its module binary format and required recompilation so I ended up having to rebuild my existing mod_webconnection_isapi.so for version 2.2. While I was in there I once again reviewed and tried to figure out a way to get script mapping to work when mapping a URL that doesn't correspond to a matching file.

There are several ways to map scripts in Apache - one is via a content type and handler mapping which has always worked without any problems. However, this feature unfortunately only works against scripts that have an actual file on disk backing it - no file and Apache will throw a file not found error.

Apache also supports something called a ScriptAlias in the config file file which allows mapping a logical URL to a specific handler which in this case would be the West Wind Web Connection ISAPI DLL. However, in previous versions I have never been able to get this mapping to work correctly at least not routed through the the mod_isapi module on which mod_webconnection_isapi is based.

The good news is that in Apache 2.2 it looks like this finally works with the script redirecting all the way down into my ISAPI extension.

Here's an example of a httpd.conf setup that provides both kinds of script mapping (AddType/Action and ScriptAliasMatch):

LoadModule webconnection_isapi_module modules/mod_webconnection_isapi.so
Alias /apachedemo/ "C:/Program Files/Apache2.2/htdocs/ApacheDemo/"

<directory "C:/Program Files/Apache2.2/htdocs/ApacheDemo/">
  DirectoryIndex default.htm
  Options ExecCGI
  # AddHandler isapi-handler dll
  AddHandler webconnection-isapi-handler dll

  AddType application/webconnection-scriptmap .wcs .wcsx .wc .wwsoap .xx .ap
  Action application/webconnection-scriptmap "/apachedemo/bin/wc.dll"
</directory>

ScriptAliasMatch (?i)^/apachedemo/.*.(wcs|wcsx|wc|wwsoap|xx|ap)$ "C:\Program Files\Apache2.2\htdocs\ApacheDemo\bin\wc.dll"


There are two kinds of mappings in this configuration: The AddType/Action configuration in the actual virtual directory which kicks in as long as a file exists on disk and the ScrtipAliasMatch which only fires if there is no file found on disk. The former has always worked reliably, but the latter I've always had issues with.

The latter basically uses a RegEx expression to map matched logical path to the physical path of the script that handles the request. This seems a bit of an odd choice - I would have expected the path specified to be a logical path in the same way the Action is specified. <shrug>

Note that the Regular Expression is case sensitive by default so make sure you use (?i) to make it case insensitive (unless you really want it to be case sensitive)!

ScriptAliasMatch works to route the request to my custom module and ISAPI extension which is great, but there are a few peculiarities in the way the paths are represented. Specifically when this re-routing occurs the PHYSICAL_PATH server variable now points at the dll rather than the originally requested URL which makes sense since there is in effect no 'physical' file at the specified path. PATH_TRANSLATED also is not set in this scenario so the code has no idea what the original script path was. However, IIS DOES return a physical path for a file that doesn't exist in its ISAPI structure and in West Wind Web Connection I've always taken advantage of that fact to use the Physical Path as the routing mechanism for requests by figuring out which script is requested (ie. translating the name of the script to a handler method for example).

There's no generic way around this issue that I can see - I couldn't find anything in Apache that does the equivalent of a Server.MapPath() (in ASP.NET) which attempts to translate a logical path name to a physical one. I also couldn't find a way to determine the base/application directory for the given request - Apache doesn't really see virtual directories as seperate entities but just as mapped directories in the whole of the server. Bummer...

My workaround for this issue has been to make some assumptions if the logical path and physical path don't match and the physical path points at the DLL, to basically assume the DLL to be either in the root of the virtual or in the bin directory and based on that figuring out the base path. From there the logical path can be applied to get a full physical path. This is a bit ugly, but it should work for my application specific scenario:

 

************************************************************************
* wwApacheRequest :: GetPhysicalPath
****************************************
***  Function: Works around some Apache Issues in how 
***            the physical path is returned. This bug
***            affects a number of the various path related
***            Server variables from apache
***    Assume: http://www.west-wind.com/wiki/kb.wiki?wc~Edit~ConfiguringWebConnectionWithApache
************************************************************************
FUNCTION GetPhysicalPath()
LOCAL lcPathInfo, lcLogical, lnAt, lcAppPath

IF EMPTY(THIS.cPhysicalPath)   
	lcPathInfo = THIS.ServerVariables("PATH_TRANSLATED")
	IF EMPTY(lcPathInfo)
		lcPathInfo = THIS.ServerVariables("PHYSICAL_PATH")
   ENDIF   
   this.cPhysicalPath = STRTRAN( STRTRAN(lcPathInfo,"/","\"),"\\","\")

   IF UPPER(JUSTEXT(this.cPhysicalPath)) = "DLL"
 	   *** We have a ScriptAlias forwarding directive - the physical path is teh DLL
	   *** and not the actual script file so let's try and fix it up
       lcLogical = this.GetLogicalPath()
       IF UPPER(JUSTEXT(lcLogical)) # "DLL"
       	   lnAt = ATC("\bin\",this.cPhysicalPath)
       	   IF lnAt > 0
       	      lcAppPath = SUBSTR(this.cPhysicalPath,1,lnAt -1)
       	   ELSE
       	      lcAppPath = JUSTPATH(this.cPhysicalPath) 
       	   ENDIF

		   this.cPhysicalPath = STRTRAN(lcAppPath + lcLogical,"/","\")
	   ENDIF
   ENDIF
ENDIF

RETURN THIS.cPhysicalPath   
ENDFUNC
*  wwApacheRequest :: GetPhysicalPath

Overall I'm stoked to have this issue finally resolved though. This has been an annoying difference between IIS and Apache's operation and this removes one of the more obvious differences. Further it looks like Apache 2.2 provides a number of fixes to the base mod_isapi code (including a at least one that are based on bugs I submitted many years ago that deals with handling raw HTTP responses that include HTTP Headers). In the past my ISAPI extension has required a number of workarounds for Apache code - most of these are no longer needed (although I probably have to leave them in the code for a while longer to still support Apache 2.0).

BTW, I thought that given that ISAPI functionality has improved I was curious to see if mod_isapi (or even my modded version) would work for running ASP.NET and the aspnet_isapi.dll. Gave that a quick try but unfortunately it's not as easy as that - Internal Server Errors pursued quickly, no doubt due to the differences in the ISAPI implementation. However, it looks like there is an ASP.NET specific module implementation, but I didn't look at this further. Of course Mono might be a better choice in this scenario anyway. 

Posted in C++  FoxPro  IIS  Web Connection  

The Voices of Reason


 

Josh Stodola
June 11, 2007

# re: Apache and Script Mapping

Hi Rick,

I've got ASP.NET 2.0 running on Apache and for the most part it works great. I plan on running my blog on it, as a matter of fact.

I kinda followed Ohad Israeli's blog post: http://weblogs.asp.net/israelio/archive/2005/09/11/424852.aspx

I have been looking for some good start-to-finish tutorials on how to get ASP.NET to work on Mono. Do you know of any?

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024