HOWTO: Access POST form data with ISAPI

This is a frequently asked question about IIS6 extensibility - how to access the request entity body on the way in - as well as how to configure IIS.

Question:

I want to catch all incoming requests, add some header and watch it when its out. Therefore I used a filter which can't be use alone in IIS 6 cause of the post data and so I added the wildcards. I'm using the same DLL for both.

Since I'm interested in all incoming requests I thought its best to define a global filter and ScriptMap.

What do you think ?

Thanks,

Answer:

I am just going to give the logical answers now. At the moment, I am a little short on time, so I cannot post code samples showing how to do this with ISAPI Filter and Extension (yes, I write and test my code before publishing it publicly - you guys do want functional and correct code samples, not merely code I whipped together on the side, right? ;-) ). However, if you really want the code samples, you can ask via blog entry comments for the code sample, and I will see what I can do and link it in...

There are two ways to access request entity body with ISAPI on IIS6:

  1. ISAPI Filter subscribing to SF_NOTIFY_READ_RAW_DATA and configured as a Global ISAPI Filter, which runs on all requests. It requires:
    • Run IIS6 in IIS5 Compatibility Mode (you lose tremendous benefits of Application Pools and process/application isolation)
    • Since SF_NOTIFY_READ_RAW_DATA is a streaming event (i.e. it triggers on every Network read), the ISAPI code must buffer and parse that data (including de-chunking) at an HTTP level to determine what is "request entity body POSTed by an HTML FORM"
    • In general, this parsing is non-trivial for an ISAPI to do correctly 100% of the time and comes with severe caveats, such as you cannot do this over SSL (request buffering in SF_NOTIFY_READ_RAW_DATA is not compatible with SSL). See this blog entry for more related details
  2. ISAPI Extension calling HSE_REQ_EXEC_URL and configured as a wildcard application mapping. There are no difficult requirements, but since it is an application mapping which operates at a different point in request processing versus ISAPI Filters (see this blog entry for an end-to-end view), you have to be aware of the resulting difference in expected behavior.
    • Application Mappings are invoked ONCE per request. You do not get a callback on every Network read. Thus, it does NOT offer streaming access to data (i.e. you get exactly once chance to read/manipulate the entity body prior to passing it on to the child request)

      This means that to manipulate large entity body, you HAVE to buffer it all in memory before manipulating it, and if you do not want to truncate the entity body, you HAVE to pass the buffer on to the child request on invoking HSE_REQ_EXEC_URL

    • Since ISAPI Extension operates an an application-level, you know that you get "request entity body POSTed by an HTML FORM" when you read ECB->lpbData for the pre-buffered data or call ReadClient() to get remaining data. No parsing, decrypting, or de-chunking required

    • This method works fine with SSL

    • The URL of the request determines the effective Application Mappings (including wildcard) of the request. Thus, it does not matter if you configure a wildcard application mapping at the global W3SVC/ScriptMaps level - if a child node has an overriding ScriptMaps setting which does NOT contain your wildcard application mapping, it will NOT execute for requests whose effective metadata comes from that overriding ScriptMaps setting.

      In other words, unlike Global ISAPI Filter which reliably triggers on every request, a "global" wildcard application mapping can be silenced by a child URL whose effective ScriptMaps property does NOT include that wildcard application mapping.

//David