Previous Section  < Day Day Up >  Next Section

17.7. HTTP Pipeline

The purpose of this section is to provide background information about the components and classes that come into play between the time a Web server receives a request and the time it finishes crafting a response. An understanding of this HTTP pipeline can be crucial to improving Web application performance, because it enables a developer to create components that affect how ASP.NET responds to requests. The references to HTTP modules and handler factories can make the subject appear daunting, but underneath there is a simple logic at work that is rooted in event handling.

Processing a Request in the Pipeline

As a request makes its way to a server, several events occur. ASP.NET permits a developer to interact with the request or response at the point of these events梩hrough special code referred to as an HTTP module or by script in a global.asax file. Both the module and file function as event handlers, and the nature of what goes on in these event handlers is the topic of this section. Figure 17-11 depicts how a request flows from the beginning of the HTTP pipeline to the endpoint, where a handler provides a response.

Figure 17-11. Handling a request within the HTTP pipeline


Let's take a simplified look at the sequence of events that occur.

  1. Request is received by Web server. The HTTP request for the bmi.aspx resource is passed to the ASP.NET Worker Process (aspnet_wp.exe). It creates an instance of the HttpRuntime class, which initiates processing of the request.

    The processing begins with the creation of an HttpContext object. You can think of this as the central repository of all information pertaining to the request. When another class needs information about the request, it can find it in an HttpContext field or property. In addition to the HttpRequest and HttpResponse fields, the class also exposes state information through HttpSessionState and HttpApplicationState properties.

  2. HttpApplicationFactory provides the HttpApplication object to process the request. After the HttpContext object is created, the HttpRuntime calls on the static factory method HttpApplicationFactory.GetApplicationInstance to create or find an HttpApplication object to process the request.

  3. HttpApplication is initialized with HTTP modules that filter request and response. HTTP modules are used to process a request and response as they pass through the pipeline. Predefined ASP.NET modules provide authentication, caching, and session state services.

  4. Locate handler factory. After the HttpApplication object takes over the processing from the HttpRuntime, it locates the HandlerFactory that finds or creates the HTTP handler object. It does this by examining the type of request (.aspx) and searching the machine.config file or web.config for the name of the handler that maps to this request type. Different handlers are needed for the different types of resources that may be requested. For example, a request for a Web page is handled differently than one for a text or XML file.

  5. Handler processes request. In our example, the handler class is the requested .aspx page. It may seem strange that the page is a handler, but the main requirement for a handler is that it implement the IHttpHandler interface梬hich the .aspx page does via its Page class.

  6. Response is constructed and returned. The ProcessRequest method of the handler is called to generate the response. This method takes an HttpContext object parameter that gives it access to the requested information needed to complete the processing.

As requests and responses move through the HTTP pipeline, an ordered chain of deterministic events梥tarting with the HttpApplication.BeginRequest event and concluding with the HttpApplication.EndRequest event梔efines each stage of the processing. In addition, random error events can arise at any time from unhandled exceptions.

A developer can greatly improve the robustness and effectiveness of an application by selectively developing handlers for these events. Handlers are typically used to trap error events and direct them to a custom error page, log session lengths and number of users, authenticate users, and adorn pages with common header and footer information. These events can be handled using either a custom application class or custom HTTP modules. We'll look at both, and see how they compare.

A third place to customize the pipeline is at the endpoint, where an HTTP handler processes a resource request. As we will see, different resources, such as .aspx or .soap files, use their own handler components. ASP.NET makes it easy to write your own handler to manage special resources

HttpApplication Class

The HttpApplication object handles most of the duties required to process a request. Its properties expose caching and application/session state information, as well as a collection of predefined HttpModules. Equally important are the set of events it exposes. We can customize an application by including code in a global.asax file to handle these events.

Table 17-5 lists the HttpApplication events in the order that they occur in the HTTP pipeline. The Error event is an exception because it can occur at any time during the process.

Table 17-5. HttpApplication Events

Event

Description

BeginRequest

Fires when ASP.NET receives a request. A request can be modified here before it reaches the page.

AuthenticateRequest

The identity of the user has been established.

AuthorizeRequest

User authorization has been verified.

ResolveRequestCache

Fires when ASP.NET determines whether to handle request from a cached page or pass request to a handler.

AcquireRequestState

Acquires the state of the current session.

PreRequestHandlerExecute

Fires before the request is sent to a handler. In the case of a request for an .aspx file, the handler is the page itself.

PostRequestHandlerExecute

Fires after the handler has packaged a response. The response object contains the text returned to the client.

ReleaseRequestState

Fires after all request handlers have been executed and ASP.NET is ready to store session state information.

UpdateRequestCache

Data being sent to client is stored in the cache for future requests.

EndRequest

Occurs when request has been processed.

Error

Occurs in response to unhandled application exception.


Your code can provide a handler for these events by creating a component that uses a delegate to subscribe to the event or define event handlers in the global.asax file. We'll demonstrate the former approach when discussing HTTP modules, but first let's look at how to use the global.asax file.

global.asax File

This file is stored in the root of the virtual directory containing the application(s) it is to be associated with. Its primary purpose is to hold the handlers that respond to the Application object's events. It is similar to an .aspx file in that it is compiled into an assembly when the Web application is first accessed. Unlike the Web page, however, it inherits from the HttpApplication class.

Here is a simple global.asax file that displays copyright information at the bottom of each page returned by an application in its virtual directory. It does this by implementing a custom handler for the EndRequest event. Notice that the format of the event handler for this, and all Application events in the global.asax file, is Application_eventname.


<%! file:global.asax %>

<%@ Application Language="C#" %>

<script runat=server>

protected void Application_EndRequest(object sender, EventArgs e)

{

   this.Context.Response.Output.Write(

      "<br>&copy2005 BMI Software");

}

</script>


Global.asax supports four other important events that are not members of the HttpApplication class: Application_Start, Application_End, Session_Start, and Session_End. The purpose of these events, which should be clear from their name, is to mark the beginning and end of an application and the sessions using it. They are the most frequently implemented event handlers in the global.asax file梡erforming the bookend operations of state initialization and cleanup. To illustrate this, let's extend the preceding global.asax example to keep track of the number of sessions that request an application and display this session number below the copyright.

The session counters are kept in an HttpApplicationState object represented by the Application property of the HttpApplication base class. It acts like a dictionary to hold information during the lifetime of an application (refer to Section 17.4, "Maintaining State").

As shown in Listing 17-10, counters that track the number of sessions and sessions cancelled are initialized to zero when the Application_Start event fires. The session counter is incremented when a Session_Start occurs, and the counter for terminated sessions is incremented when a Session_Ends event occurs.

Listing 17-10. Using global.asax to Count Sessions

<%! global.asax %>

<%@ Application Language="C#" %>

<script language = "C#" runat=server>

// Can also use Application_OnStart syntax

void Application_Start(Object Sender, EventArgs e)

   {

      // Initialize counters with application scope

      Application["Sessions"] = 0;

      Application["TerminatedSessions"] = 0; 

   }

void Session_Start(Object Sender, EventArgs e)

   {

      Application.Lock();

      Application["Sessions"]=(int) Application["Sessions"] + 1;

      Application.UnLock();

   }

void Session_End(Object Sender, EventArgs e)

   {

      Application.Lock();

      Application["TerminatedSessions"] = 

         (int) Application["TerminatedSessions"] + 1;

      Application.UnLock();

   }

protected void Application_EndRequest(object sender,

                                      EventArgs e)

   {

      string sessionCt= "Session #: 

            "+Application["Sessions"].ToString();

      // Display copyright and current session number

      this.Context.Response.Output.Write(

            "<br>&copy2005 BMI Software<br>"+sessionCt);

   }

</script>


Using a Code-Behind File with global.asax

As with the .aspx file, code can be separated from the global.asax file and placed in a code-behind file. The code is compiled into a DLL and placed it in the \bin subdirectory of the application. The Inherits attribute of the Application directive points to the class implemented in the DLL.

To convert the preceding example to use a code-behind file, we remove code from the global.asax file, leaving only the Application directive:


<%! file:global.asax %>

<%@ Application  Inherits="sessionctr" %>


The code-behind file must be implemented as a class that inherits from HttpApplication. Here is a segment of the code:


// file: sessionctr.cs

using System;

using System.Web;

public class sessionctr: HttpApplication

{

   void Application_OnStart(Object Sender, EventArgs e)

   {

      Application["Sessions"] = 0;

      Application["TerminatedSessions"] = 0; 

   }

   // Remainder of assembly code goes here

}


HTTP Modules

An HttpModule is a component that implements the IHttpModule interface. The role of the module is similar to that of the global.asax file: It processes events (refer to Table 17-2) associated with response and request messages in the HTTP pipeline. In many cases, you can implement the same functionality using either the module or global.asax file. We discuss the factors that affect your choice later in this section.

Listing 17-11 shows the predefined modules that ship with ASP.NET. The physical modules (assemblies) are stored in the Global Assembly Cache (GAC) and are made available by listing them in the <httpModules> section of the machine.config file. They perform such diverse tasks as user authentication, caching, and state management. Although you do not work with the modules directly, they should give you an idea of the type of pre- and post-request processing that can be performed.

Listing 17-11. HTTP Modules in machine.config File

<httpModules>

   <add name="OutputCache" 

      type="System.Web.Caching.OutputCacheModule" />

   <add name="Session" 

      type="System.Web.SessionState.SessionStateModule" />

   <add name="WindowsAuthentication" 

      type="System.Web.Security.WindowsAuthenticationModule" />

   <add name="FormsAuthentication" 

      type="System.Web.Security.FormsAuthenticationModule" />

   <add name="PassportAuthentication" 

      type="System.Web.Security.PassportAuthenticationModule" />

   <add name="UrlAuthorization" 

      type="System.Web.Security.UrlAuthorizationModule" />

   <add name="FileAuthorization" 

      type="System.Web.Security.FileAuthorizationModule" />

   <add name="ErrorHandlerModule" 

      type="System.Web.Mobile.ErrorHandlerModule,

      System.Web.Mobile, Version=1.0.5000.0, Culture=neutral,

      PublicKeyToken=b03f5f7f11d50a3a" />

</httpModules>


Implementing a Custom HttpModule

There are two minimal requirements for building a functioning HttpModule: It must contain a class that implements the IHttpModule interface and it must register an event handler to process one of the HttpApplication events. An example of this is shown in Listing 17-12. The code adds copyright information to the bottom of all pages returned by the application梕xactly the same function performed by the earlier global.asax file.

The IHttpModule interface consists of two methods?tt>Init and Dispose梩hat must be accounted for. We use Init to register the event handler that will be called when the EndRequest event occurs. Note that access to the events is provided through the HttpApplication object that Init receives as a parameter.

Listing 17-12. Module to Append Copyright to Web Pages

using System;

using System.Web;

namespace CustomModules{

public class FooterModule: IHttpModule

{

   public void Init(HttpApplication httpApp)

   {

      httpApp.EndRequest += new EventHandler(this.OnEndRequest);

   }

   public void Dispose() {}

   // 

   public void OnEndRequest (Object obj, EventArgs e)

   {

      HttpApplication httpApp = (HttpApplication) obj;

      httpApp.Context.Response.Output.Write(

               "<br>&copy2005 BMI Software");

   }

}

}


Deploying a Custom HttpModule

To use the module with a Web application, compile it and place it in the \bin subdirectory of the application or in the Global Assembly Cache (GAC). Next, the assembly must be registered in either the web.config or machine.config file. In this example, we use a web.config file, which is placed in the root of the application's virtual directory. A portion of the file is shown here:


<configuration>

   <system.web>

      <httpModules>

         <add name="copyright"

         type="CustomModules.FooterModule, footmodule" />

      </httpModules>

   </system.web>

</configuration>


The key part is the <add> element that takes the form:


<add name=friendly name  type=class name, assembly />


This element is enclosed by the <httpModules> elements. Multiple <add> elements can be used to specify multiple modules.

URL Rewriting with a Custom Module

URL rewriting is the process of changing a requested URL so that it is redirected to another resource. This is commonly used to ensure that bookmarked links continue to work when directories and resource names are changed on a Web server.

An HTTP module provides a convenient and easy-to-use approach for rewriting a URL. The implementation logic in the module is straightforward:

  1. An HttpApplication event invokes event handler code.

  2. The event handler examines the Context.Request.Path property for the requested path.

  3. If the path URL needs to be redirected, the Context.RewritePath method is called with the substitute path.

The most important issue to be resolved in developing a rewriter is selecting an event in the request's lifecycle where the URL checking and modification should occur. The three HttpAppliction event candidates are shown in Table 17-6, along with the criteria for selecting them. (Authentication is discussed in Section 17.5.)

Table 17-6. Event Choices for Implementing a URL Rewriter Module

Event

Criteria for Selecting Event

BeginRequest

Use unless forms authentications is also used.

AuthenticateRequest

Use if Windows authentication is used.

AuthorizeRequest

Use if Forms Authentication is used and Windows authentication is not.


In general, if no authentication is being used, place the rewrite logic in an event handler for the BeginRequest event.

Listing 17-13 contains code for a simple rewriter module with the rewriting logic included in the AuthorizeRequest event handler. Look closely at this code. It first calls a static method Rewrites.getRewrites() that returns a hash table in which the keys are old URLs and the associated value is the replacement URL. The hash table is checked to see if it contains the currently requested path and if it's in the table, a call is made to Context.RewritePath. The new URL is passed to it, and the method automatically takes care of details such as including any query string with the new URL.

Listing 17-13. Custom HTTP Module to Rewrite a URL

using System;

using System.Web;

using System.Collections;

namespace CustomModules

{

   public class ReWriteModule: IHttpModule

   {

      public void Init(HttpApplication httpApp)

      {

         httpApp.AuthorizeRequest += new 

               EventHandler(this.OnAuthorizeRequest);

      }

      public void Dispose() {}

      // Determine if requested URL needs to be rewritten

      public void OnAuthorizeRequest( Object obj, EventArgs e)

      {

         // Get hash table containing old and replacement URLs

         Hashtable urls = Rewrites.getRewrites();

         HttpApplication httpApp = (HttpApplication) obj;

         // Get path of requested URL

         string path = httpApp.Context.Request.Path.ToUpper();

         // See if path is in hash table 

         if(urls.ContainsKey(path))

            path= (string)urls[path];

         httpApp.Context.RewritePath(path);   // Rewrite URL

      }

   }

}


The module is registered by placing the following entry in the web.config file:


<add name="redirector"

       type="CustomModules.ReWriteModule, rewritemodule" />


The process of identifying URLs to be rewritten can be implemented in various ways. This example stores the old and replacement URLs in an XML file using the following format:


<!--  File: redirector.xml   -->

<RewriterConfig>

   <RewriterRules>

      <Rule>

         <OldPath>/ideas/calculator.aspx</OldPath>

         <NewPath>/utilities/calculator.aspx</NewPath>

      </Rule>

      <Rule>

         <OldPath>/ideas/bmi.aspx</OldPath>

         <NewPath>/utilities/bminew.aspx</NewPath>

      </Rule>

   </RewriterRules>

</RewriterConfig>


This information could also be stored in the web.config file, although some overhead is required to do this. For details, refer to "Adding a Custom Configuration Section" on page 824.

Choosing Between an HTTP Module and global.asax

In many cases, the functionality you want to add to a Web server environment can be implemented using HTTP modules or the global.asax file. As a guide to making the choice, let's review their features:

  • Both can contain handlers to service HttpApplication events.

  • Global.asax can receive notification of session and application start and end events. An HTTP module cannot service these events.

  • Modules are deployed at the machine (machine.config) or virtual directory (web.config) level; global.asax operates at a directory level only.

Either can be used to handle HttpApplication events. If the feature being added applies to all applications on the server, a module is the better choice; if the feature is specific to an application, use a global.asax file.

HTTP Handlers

When a request is made for a resource, ASP.NET directs control to the appropriate handler for that resource. For example, if the request is for an .aspx file, control passes to the PageHandlerFactory component, which returns the requested .aspx page. The default ASP.NET handlers are registered in the machine.config file within the <httpHandlers> section. An extract of that file shows how the resource type is mapped to the class capable of creating a handler for it:


<httpHandlers>

   <add verb="*" path="trace.axd" 

           type="System.Web.Handlers.TraceHandler" />

   <add verb="*" path="*.aspx"

           type="System.Web.UI.PageHandlerFactory" />

   <add verb="*" path="*.ashx"

           type="System.Web.UI.SimpleHandlerFactory" />

   <add verb="GET,HEAD" path="*"

           type="System.Web.StaticFileHandler" />

   ...

</httpHandlers>


An HTTP handler is a component that implements the System.Web.IhttpHandler interface. Unlike an HTTP module, only one handler is called to process a request. ASP.NET maps a request to the target handler based on information in a machine.config or web.config file. The request can be mapped directly to a handler or to a handler factory that creates or retrieves the appropriate handler. As shown in the preceding machine.config file, .aspx requests are handled by a PageHandlerFactory class. We'll look at both techniques.

Implementing a Custom HTTP Handler

ASP.NET provides handlers for .aspx, .soap, .asmx (Web services), and other standard ASP.NET file types. So why create a custom handler? The primary motivation is to support new file extensions or existing file extensions that require added processing features. For example, when a text file (.txt) is requested, ASP.NET simply returns the contents of the file. As an exercise, let's improve this with a handler that accepts a URL with the name of the text file and a query string parameter that specifies a keyword to search for in the file. The output from the handler is a listing of the text file with all instances of the keyword highlighted, as well as a count of the number of times the keyword occurs in the document.

The IHttpHandler interface contains two members that must be implemented in our custom handler class: the ProcessRequest method and the Reuseable property. As shown in Listing 17-14, ProcessRequest contains the code that processes the HTTP request; IsReusable indicates whether the instance of the handler can be reused for other requests. This is applicable only when a handler factory is providing handlers and pools them for reuse.

This handler responds to URL requests that contain a text file and query string containing a keyword to search for in the file:


HTTP://www.scilibrary.com/films.txt?kwd=Bogart


The context object provides access to the file name through the Request.PhysicalPath property. An attempt is made to open the text file and read it line by line. Each line is searched for the keyword using Regex.Match. If a match is found, the method BoldWord is called to place HTML bold (<b>) tags around the text. This method also increments the word counter.

Listing 17-14. Custom HTTP Handler to Display a Text File

// file: txthandler.cs

using System;

using System.Web;

using System.IO;

using System.Text.RegularExpressions;

public class TextHandler: IHttpHandler

{

   static int wordCt=0;

   static string BoldWord(Match m) 

   {

      // Get the matched string and place bold tags around it

      string kwd = m.ToString();

      kwd="<b>"+kwd+"</b>";

      wordCt += 1;

      return kwd;

   }

   public void ProcessRequest(HttpContext ctx)

   {

   // Get file to be opened

   string filePath= ctx.Request.PhysicalPath;

   int ndx= filePath.LastIndexOf(@"\");

   string fileName = filePath.Substring(ndx+1);

   // Keyword to search for in file

   string keyWord = ctx.Request["kwd"];

   // Create HTML response

   ctx.Response.Output.Write("<html><body >");

   ctx.Response.Output.Write("File: "+fileName+

            " &nbsp;Keyword: <b>"+keyWord+"</b><hr size=1>");

   string line;

   try {

      StreamReader reader= new StreamReader(filePath);

      // Read lines of file and display in response 

      while ((line = reader.ReadLine()) != null) 

      {

         if (keyWord!= null) {

         // search for keyword and highlight

            string newLine = Regex.Replace(line, keyWord,

                 new MatchEvaluator(TextHandler.BoldWord), 

                                    RegexOptions.IgnoreCase);

            line= newLine;

         }

         ctx.Response.Output.Write("<br>"+line);

      }

      reader.Close();

   } catch (Exception e)

   {

      ctx.Response.Output.Write("<br>"+e.Message);

   }

   // Display number of matches

   ctx.Response.Output.Write("<br><br>Word Matches: " + 

          wordCt.ToString());

   ctx.Response.Output.Write("</body></html>");

   wordCt=0; // reset since it is static

   }

   public bool IsReusable

   {

      get {return false;}

   }

}


The final code is compiled and placed in the \bin subdirectory of the application.

Deploying a Custom HTTP Handler

For ASP.NET to be aware of the handler, an entry is made in the machine.config or application's web.config file. In this example, we place an <add> element in the web.config file. This element contains three attributes that define the handler and the conditions for using it. Verb specifies the type of request to be handled, such as GET or POST. The * is a wildcard character that represents any type request. The path attribute indicates the requested resource name that is mapped to the handler; and type specifies the handler class or handler factory and its containing assembly.


<httpHandlers>

   <add verb="*" path="*.txt" type="TextHandler,txthandler"/>

</httpHandlers>


The final step in deployment is to make IIS (Internet Information Service) aware that requests for the .txt extension are to be handled by ASP.NET. This is done by using Internet Services Manager to map the file extension to the ISAPI extension DLL (aspnet_isapi.dll). Follow these steps:

1.
Invoke Internet Services Manager and right-click Default Web Site.

2.
Select Properties ?Home Directory ?Configuration.

3.
Click Add and a pop-up window appears.

4.
Fill in the Executable field with the path to the aspnet_isapi.dll file. This file should be in the version directory of the Framework installation.

5.
Fill in the Extension field with *.txt.

With these steps completed, TextHandler is now called to process all requests for .txt files. Figure 17-12 shows output from a sample request.

Figure 17-12. Output from HTTP handler that displays text files


Core Note

If an extension used by an HTTP handler is not mapped in IIS to ASP.NET, IIS attempts to return the contents of any file requested having that extension. ASP.NET never sees the request.


    Previous Section  < Day Day Up >  Next Section