What to expect from IIS7 custom error module

IIS7 custom error module work in SendResponse stage with priority high (priority high in send response actually means lowest priority) which makes it one of the last modules to run in the pipeline. It produces custom errors only if current statusCode > 400. IIS7 custom error module produces four kinds of custom error responses. These are

1. Custom – This error is produced as per settings in the system.webServer/httpErrors section as seen for the requested URL. For information on how a merged view of section for current URL is produced, go here.
2. Detailed – These errors are produced by custom error module based on information available from IHttpResponse::GetStatus call and some static data. Error response include some internal pipeline information as well and is expected to be viewed by server administrators only.
3. One liners – If custom error module detects that we are about to return a blank response, it sends a one-liner error response in few cases. One line responses are hardcoded for various status codes and are not configurable. These are close to “default” errors of IIS6.
4. Original –If custom error module see a non-blank response text, it assumes that module which changed the status code also produced a useful error message and can return that.

Lets understand various properties available in system.webServer/httpErrors section first before going to special gotchas you should care about when dealing with custom error module. Always remember that custom error module will read a merged view of system.webServer/httpErrors section for current URL configuration path and make decisions based on values seen in this merged view.

This is the first property custom error module uses to decide if it should do detailed error or custom error. If effective value of errorMode is “Detailed”, custom error module ignores the custom error settings and generate a detailed error report which contains information like physicalPath, pipeline stage, module which generated the error etc. This property can have following values.
    – Custom – When errorMode is set to “Custom”, custom error module will generate error as per custom effective responseMode and path both for local and remote clients.
    – Detailed – If errorMode is “Detailed”, custom error module generates a detailed error both for local and remote clients.
    – DetailedLocalOnly – This is the default value and custom error module generates different responses for local and remote clients. If request is from local machine effective errorMode is “Detailed” but if request is from a remote machine, effective errorMode is “Custom”.

Value of this section level property tells custom error module what to do when response text is not blank. If a module call IHttpResponse::SetStatus to set an error code and also sets up response text, existingResponse property tells if custom error module should replace current response text with its own error text or should it let the current response text pass through. Asp.Net and WCF are example of modules which sets error response text. This property can be set to following three values.
    – Replace – This value make custom error module to always replace the error information with text generated by custom error module. If existingResponse is set to “Replace”, errors/exceptions generated by Asp.Net/WCF are replaced by IIS7 errors.
    – PassThrough – If existingResponse is seen as “PassThrough”, custom error module will always pass through responses from modules. This setting will make custom error module return blank response if modules don’t set any text.
    – Auto – This is the default value and tells custom error module to do the *right* thing. Actual error text seen by clients will be affected depending on value of fTrySkipCustomErrors returned in IHttpResponse::GetStatus call. When fTrySkipCustomErrors is set to true, custom error module will let the response pass through but if it is set to false, custom errors module replaces text with its own text. Asp.Net/WCF call IHttpResponse::SetStatus with fTrySkipCustomErrors true so that IIS doesn’t override their errors with its own. When effective errorMode is “Detailed” and response is non-empty, this value of existingResponse will act as “PassThrough” regardless of value of fTrySkipCustomErrors.

responseMode, path
These properties affect error response only when effective value of errorMode is “Custom”. Custom error module look for error configuration for statusCode.subStatusCode in the merged view of the section. If entry for the current statusCode.subStatusCode is not found, custom error module looks for an entry for current statusCode with subStatusCode -1. If this is not found as well, it reads section level defaultResponseMode and defaultPath as value of responseMode and path. If section level defaultPath is also not set, custom error module will produce a one-liner reply regardless of value of defaultResponseMode. Here are few path gotchas for different response modes.

    – path is treated as a file path. In addition to setting the response, custom error module make sure that right content-type header is set based on extension and mime-map configuration.
    – prefixLanguageFilePath property helps in doing localized custom errors based on request information. If a request contains Accept-Language header, custom error module will build a list of language packs installed on the machine and try to find a match with value of Accept-Language header (based on which appears first and its q value). If a match is found this language code is used. Else a default language code is used depending on default locale. Language code and path value is appended to prefixLanguageFilePath to build effective error file path.
    – If path is set to blank, module will generate a one-liner reply. This can only happen when <error> entry is not found and section level defaultPath is effective because path contains a non-empty validation in schema.

    – path is treated as URL to execute. Response returned by executing the URL is returned to client as response. On successful child execute, client will see a 200 response. Child request will enter the pipeline in the same worker process. If this request is supposed to be executed by some other application pool, IIS core will generate 403.14. If child execute produce some other error, that error code along with child execute response is returned.
    – If path starts with “http://”, exact URL specified by path is executed. Hostname has to be current hostname. Else server will produce 404.
    – When path starts with “/”, it is assumed as URL for the current site and do a child execute on that.
    – If path doesn’t start with “/” or “http://”, server will produce 404 when trying to execute the URL.

    – Custom error module blindly uses the path as location header value and send a 302 response. If path is set to “/otherpath/otherpage.htm”, this will be used as the destination and most browsers send this request back to the same host. If you set path to www.iis.net, browsers do the right thing and send the new request to www.iis.net instead.
    – IIS doesn’t handle application relative paths both for “Redirect” and “ExecuteURL” as handled by Asp.Net (e.g.  ~/error/404.htm).

What to expect in case of invalid configuration
Read this article to understand when custom error module will see configuration errors. If custom error modules fails to read system.webServer/staticContent or system.webServer/httpErrors configuration section, it will always override errors from other modules with the configuration error. If this happens, you cannot control the behavior of custom error module by setting properties in httpErrors configuration section as module won’t be able to read the configuration. Custom error module will only generate a 500.19 and will assume default values for responseMode, errorMode, existingResponse which are “File”, “DetailedLocalOnly” and “Auto”. So local clients will always see a detailed error message and remote clients will see a one-liner (because responseMode is “File” path is seen as blank). Detailed errors will have blank detailed information link as this information is obtained from the configuration section. If the original request was sent to Asp.Net page or WCF service, there is a bug in custom error module which makes it return blank responses in case of configuration errors. This will be fixed in next service pack.



How configuration system merges sections

One of the things which was not clear to me when IIS7 configuration system was written was how configuration system merges all the configuration data available and then decide what values are effective for the current request. One of confusions came from the fact that I assumed that IIS modules try to read the values for the current URL and then keep moving up till it finds the attribute explicitly defined in a configuration file. This is part true for the configuration system but never for the modules. All modules do is ask the configuration system for a merged view of a particular configuration section for a configuration path which usually corresponds to the URL of the requested page. If you are familiar with AhAdmin, this corresponds to the following call.

IAppHostAdminManager::GetAdminSection(<sectionName>, <configPath>);

Section names are full section names as defined in schema.xml and configuration path is of the form “MACHINE/WEBROOT/APPHOST/Default Web Site/Application/VirtualDirectory/Folder/file.php”. Configuration system gives a merged view of the section for the configuration path passed in GetAdminSection call. Modules can ask for a non-merged view of a section as well using IAppHostConfigFile::GetAdminSection but this is not done in any of the modules which is shipped as part of IIS. If invalid configuration is detected (may be because xml is invalid or section is locked at parent level), configuration system returns an error to modules. Modules don’t try to read the configuration at parent level paths if see an error while reading configuration for current URL. Some modules do try to read the configuration at server/site level rather than configuration for current URL path. One reason for this is because some sections are supposed to be “AppHostOnly” and are valid at server level only. HttpCompression and isapiCgiRestrictionList sections are such sections and are marked “AppHostOnly” in the section definitions by IIS setup. If you define these sections at site level or below, configuration system will generate an error. You can get around this error by changing allowDefinition attribute in section definition of these sections but that won’t make modules read the section at levels below server. Another reason why modules sometimes don’t read configuration for current URL is that few properties cannot be varied per URL and are constant for an application pool. An example of such property is system.webServer/caching@maxCacheSize which defines maximum memory size which can be used by caching module. As this property is constant for an application pool, caching module just read this property at server level and not for request URL.

When a merged view of a section is requested, here is what happens in the configuration system. On call to IAppHostAdminManager::GetAdminSection, configuration system reads all configuration files up to the URL path starting with machine.config, root web.config, redirection.config, and applicationHost.config. If allowSubDirConfig property of virtual directory is set to false, configuration system don’t read configuration files below virtual directory paths. As soon as the configuration files are read, few global validations are performed which are done irrespective of which section is requested. If any of global validations fail, configuration system generates an error as soon as it encounters one. Here are the global validations which are made by the configuration system.
   è Schema errors, invalid or duplicate section groups or section definitions are global errors.
è Multiple <configSections>, <configProtectedData> elements in applicationHost.config or if they are not the first and second child elements of the root <configuration> in applicationHost.config, it is reported as error.
è Errors in sites section are seen when requesting any section as configuration system reads this section to get list of virtual directories.
è If configuration system finds a configuration file which doesn’t contain a <configuration> tag or which contains invalid xml, it will result in a configuration error regardless of which section is requested.
è If a section for which section definition is missing is used in a configuration file, a configuration error is generated.
è A section defined more than once for a configuration path is treated as global error. If none of the global errors are seen by the configuration system, it proceeds to build merged view of the requested section.

Configuration system starts from applicationHost.config and builds a IAppHostConfigElement object based on defaults defined in schema. It then reads the section at root level using IAppHostConfigFile::GetAdminSection and also reads the section under all location paths defined in that file using IAppHostConfigLocation::get_Item and replace existing values with new ones whenever a property which is explicitly defined is seen. If a section level error is encountered a configuration error is generated. Examples of errors which are generated only when requesting a section with error are invalid attribute/element, section locked at parent level, duplicate element in a collection, missing required attributes etc. This makes configuration system report errors at root first followed by errors in sections defined under location paths and then sections in web.config files at lower levels. Child elements and collections are handled similarly. Add/remove/clear for collections gets evaluated from root to the current path. If a duplicate element is found for a collection at any path till current path, an error is generated.

I hope this blog helps you debug configuration errors more easily and understand what to expect from IIS configuration system. This blog post might be little difficult to understand. Let me know if you have some suggestions to make it more readable.