rick.james

  • Fun with http.sys Caching

    There are many caches available to web applications. Today I will play with the http.sys URI cache. It is a simple, fast, kernel-mode cache.

    How it Works

    First IIS must decide if the response can be stored in the http.sys' cache (devs: See HttpSendHttpResponse's pCachePolicy argument). There are about twenty reasons for IIS to decide something should not get in the cache (because of the cache's simple design). To find out why a specific request is not being cached, enable "Failed Request Tracing" and look for the HTTPSYS_CACHEABLE event's reason field.

  • IIS 7 C++ Module API Sample: Changing Handlers

    IIS 7 C++ Module API Sample: Changing Handlers

    Introduction

    Today's modules looks at how you can programmatically change handlers. RQ_EXECUTE_REQUEST_HANDLER is by far the most common notification used in the IIS 7 pipline. This makes sense because it is where the response is generated. We know how many different response generation techonologies there are out there. The RQ_EXECUTE_REQUEST_HANDLER notification is only delivered to the modules configured in system.webServer/handlers for the request. This is unlike all other request (RQ_*) notifications, where all modules in system.webServer/modules, that have registered for a notification receive it. The system.webServer/handlers configuration syntax is pretty flexible, but obviously cannot do everything for everyone. This post assumes you've already got the hello world module working, so it jumps straight into API discussion.

    Pipeline

    Here is the first portion of request pipeline. (from the top of httpserv.h) You can see there's an entire notification dedicated to handler selection. You can also see that by this point in the pipeline the request is already authenticated and authorized.
    #define RQ_BEGIN_REQUEST               0x00000001
    #define RQ_AUTHENTICATE_REQUEST        0x00000002
    #define RQ_AUTHORIZE_REQUEST           0x00000004
    #define RQ_RESOLVE_REQUEST_CACHE       0x00000008
    #define RQ_MAP_REQUEST_HANDLER         0x00000010
    #define RQ_ACQUIRE_REQUEST_STATE       0x00000020
    #define RQ_PRE_EXECUTE_REQUEST_HANDLER 0x00000040 
    #define RQ_EXECUTE_REQUEST_HANDLER     0x00000080
    ...
    

    Deciding When to Change Handlers

    The whole reason we're writing this module is because the built-in system.webServer/handlers configuration does not meet our needs. So we need our own logic. In this case we're going to target some special logic that Ruby on Rails seems to like: deliver that which can be delivered and render the rest unto Rails. First we check if we have a IScriptMapInfo already associated with the request. If we do it means that system.webServer/handlers could find a handler. If no handler could be found it means we should deliver it to Rails. If we can find a handler then we need some additional checks: first we use IHttpContext::GetFileInfo to check if this request maps to a file on the file system. Finally, some handlers do not need file system backing, so we compare IScriptMapInfo::GetResourceType to 3. (3 is from the system.webServer/handlers definition in %WinDir%\system32\inetsrv\config\schema\iis_schema.xml)
    MODULE::OnMapRequestHandler(
        IHttpContext*           pContext,
        IMapHandlerProvider*    pProvider
    )
    {
        ...
    
        IScriptMapInfo* pScriptMap = pContext->GetScriptMap( );
    
        ...
    
        if( NULL != pScriptMap )
        {
            if(
                NULL != pContext->GetFileInfo( ) ||
                3 == pScriptMap->GetResourceType( )
            )
            {
                //
                // if we have a script map AND
                //      - a file info
                //      - or resourceType=Unspecified (3)
                // then no need to change handlers
                //
                goto Finished;
            }
        }
    

    Choosing the New Handler

    We now know that we want to deliver the request to Rails. One solution at this point would be to implement IScriptMapInfo and set the IScriptMapInfo::GetModules to be our Rails handler module. The problem with this solution is that there is more than one way to run Rails on IIS, so we don't know which module should handle Rails. So we need to expose some configuration to the web server administrator. At this point we could get into schema extensibility, but there's already a perfectly good configuration section that does almost exactly what we want: system.webServer/handlers! :-). So we tweak the URL to make it look like something system.webServer/handlers would understand (replace the stuff after the last slash with index.rb) and then we ask IIS to do the work for us:
        //
        // Get handler for pszScriptName/APPEND_PATH
        //
        hr = pContext->MapHandler(
            pSite->GetSiteId( ),
            pSite->GetSiteName( ),
            pszFullAppPath,
            pRequest->GetHttpMethod( ),
            &pScriptMap,
            FALSE
        );
        if( FAILED( hr ) )
        {
            goto Finished;
        }
    
        //
        // Now make that the handler for this request
        //
        pProvider->SetScriptMap( pScriptMap );
    

    Deployment

    Since this is a C++ module it needs to be in system.webServer/globalModules. Since this module uses RQ_* notifications is need to be in the application module list (system.webServer/modules). This later requirement means you now have a method for turning this custom handler selection on/off, cause system.webServer/modules is configurable at the application level. (e.g. only add the module in web.configs where you want Rails-style handler mapping). Finally, cause we're using the system.webServer/handlers configuration, we should add an entry for *.rb, mine looks like:
        <location path="" overrideMode="Allow">
            <system.webServer>
                <handlers accessPolicy="Read, Script">
                    ...
                    <add 
                        name="rails (currently broken)" 
                        path="*.rb" 
                        verb="GET,HEAD,POST" 
                        modules="FastCgiModule" 
                        scriptProcessor="c:\ruby\ruby.exe" 
                        resourceType="Unspecified" 
                    />
                    ...
    

    Expected Behaviour

    • http://localhost/a.rb should get an HTTP 500 with detailed error of "<handler> scriptProcessor could not be found in <fastCGI> application configuration." (I still haven't got Rails working as a FastCGI on IIS 7, I'll blog about it when I do)
    • http://localhost/app1/recipe/new should get the same HTTP 500 if our module is running and a regular HTTP 404 if not
    • "Failed Request Tracing" log should have something like this:
      NOTIFY_MODULE_START 
          ModuleName="RubynatorModule"
          Notification="MAP_REQUEST_HANDLER"
      HANDLER_CHANGED 
          OldHandlerName="StaticFile"
          NewHandlerName="rails (currently broken)"
          NewHandlerModules="FastCgiModule"
          NewHandlerScriptProcessor="c:\ruby\ruby.exe"
          NewHandlerType=""
      NOTIFY_MODULE_END 
          ModuleName="RubynatorModule"
          Notification="MAP_REQUEST_HANDLER"
      

    Getting Ruby on Rails Working

    This is beyond the scope of this article, checkout this excellent wiki entry.

  • IIS 7 Talks: Slides

    Will and I had the pleasure of presenting IIS 7 to audiences in 6 countries in the last 2 weeks. Sorry to people who found out about the presentations too late to attend. I've attached a copy of the slides we used. You may also want to see the shared hosting on IIS 7 document.