ATL Server and Visual Studio .NET: Developing H...

From the October 2000 issue of MSDN Magazine.

We were unable to locate this content in de-de.

Here is the same content in en-us.

This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
MSDN Magazine

ATL Server and Visual Studio .NET: Developing High-Performance Web Applications Gets Easier

Shaun McAravey and Ben Hickman

This article assumes you�re familiar with C++, COM, and ATL
Level of Difficulty    1   2   3 
SUMMARY When developing high-performance applications for the Web, developers often must choose between performance and ease of development. With ATL Server, new with Visual Studio .NET, developers get the best of both worlds. ATL Server uses a tag replacement engine written in C++, provides a simple programming model, and promotes enhanced performance and easy debugging. This article presents an overview of the ATL Server architecture, then creates a basic ATL Server project. It then goes on to explain processing SRF files, HTTP streams, forms, cookies, and header files. Managing session state is also discussed, along with file uploads and performance monitoring. When developing Web applications with Microsoft® technologies there is sometimes a trade-off between development ease and performance. For example, Active Server Pages (ASP) provides the flexibility and ease-of-development of a script language, but is not as fast or powerful as C++. The Internet Services Application Programming Interface (ISAPI), on the other hand, provides the performance of compiled C++, but is not as easy to use as ASP. With the release of Visual Studio .NET and ATL Server, developers won't have to choose between performance and easy development; they will have the best of both worlds.
      Instead of a script-processing engine, ATL Server provides a high-performance tag replacement technology written entirely in C++. But tag replacement is only the beginning. Support for everything from cookies and form processing to session state management and thread pooling is also provided. An added bonus is integration with the Performance Monitor so ATL Server applications can easily provide performance metrics.
      This article is based on an early beta version of Visual Studio .NET and therefore the information is subject to change by the time the product actually ships.

What's Different About ATL Server?

      To illustrate some of the reasons you'll want to consider using ATL Server, we'll first examine the canonical HelloWorld application as implemented in ASP, ISAPI, and ATL Server. In each case, server-side script or code generates the HTML that is sent to the client. The following code implements HelloWorld in ASP:
<html>
<script language="VBScript" runt="Server">
Sub HelloWorld()
    Response.Write "Hello World from ASP."
End Sub
</script>
<body>
<% HelloWorld %>
</body>
</html>
      The HelloWorld subroutine in VBScript generates the HTML response for the client. ASP.DLL loads and interprets the HelloWorld.asp file each time the client requests http://server/HelloWorld.asp. This could also have been implemented using a COM component for the Response.Write code, but ASP.DLL would still be involved.
      HelloWorldISAPI.CPP (see Figure 1) is a HelloWorld implementation using an ISAPI extension DLL. Assuming this ISAPI extension is named MyExt.DLL, Microsoft Internet Information Services (IIS) loads MyExt.DLL if necessary, and calls the HttpExtensionProc when a client requests http://server/MyExt.DLL. HttpExtensionProc is the ISAPI extension DLL export that responds to client requests.
      HelloWorld.SRF is a HelloWorld implementation using ATL Server:
{{handler ATLServerHelloWorld.dll/Default}}
<html>
<body>
{{HelloWorld}}
</body>
</html>
In response to client requests for http://server/HelloWorld.SRF, IIS loads the ATL Server DLL (ATLServerHelloWorld.dll), and hands off the request to ATL Server. The SRF file is processed and the {{HelloWorld}} tag results in a specific call into the ATLServerHelloWorld.DLL to generate the HTML response for the client. Somewhere in ATLServerHelloWorld.DLL, there is a C++ class for replacing the {{HelloWorld}} tag with some arbitrary HTML. Later in this article you'll see how ATL Server applications use SRF files as templates for generating HTML.

A Quick Review of ISAPI and IIS

      The Internet Services API can be used to build high-performance Web applications with low-level control under IIS. You write a DLL using C/C++, and IIS uses a DLL to either filter incoming requests or respond to them. These two kinds of ISAPI DLLs are called filters and extensions, respectively.
      An ISAPI filter is a DLL that can receive event notifications from IIS as client requests are being processed. The filter can then modify the standard behavior of IIS. Filters can be used to provide compression, encryption, logging, and custom authentication schemes, among other things.
      An ISAPI extension is a DLL that can receive client requests and send responses. C++ code can often generate the HTML that is sent to the client. The extension DLL must export the GetExtensionVersion and HttpExtensionProc entry points (and optionally TerminateExtension). For every client request, an EXTENSION_CONTROL_BLOCK structure is passed from IIS to the ISAPI extension DLL through HttpExtensionProc. This structure is used to get HTTP header information, call IIS helper functions, and read and write to the client stream.
      With low-level ISAPI control comes responsibility. For example, useful ASP intrinsics, such as Session and Response objects, are not available in ISAPI, although similar functions can ultimately be accessed. Furthermore, when writing an ISAPI extension, you typically create a thread pool to respond to incoming client requests. For more information on ISAPI, see the MSDN® Online article "Developing ISAPI Extensions and Filters".

The ATL Server Architecture

      ATL Server projects are ISAPI extensions that use SRF files as HTML generation templates. The ATL Server uses SRF files to respond to client requests by using C++ classes for tag replacement.
      An ATL Server project comprises several DLLs: one ISAPI extension DLL along with one or more ATL Server application DLLs. The ISAPI extension DLL caches the loaded ATL Server DLLs and parsed SRF files (also known as Stencils) and contains the thread pool for responding to client requests. The ATL Server application DLL contains classes for parsing SRF files and replacing SRF file tags with HTML.
      Application DLLs contain one or more request handler classes. Request handler classes are derived from CRequestHandlerT and contain your code for replacing tags with HTML in replacement methods. Two different maps are used to associate request handler classes with request handler DLLs and to associate replacement methods with SRF tags. The CRequestHandlerT class contains methods and member variables for accessing form variables, cookies, request streams, and response streams. Figure 2 shows the processing of a client request. (This example shows just one application DLL and one ISAPI extension DLL.) The processing of a client request follows these seven steps.
  1. The client browser first requests an SRF file via HTTP.
  2. IIS hands the request off to the extension through its HttpExtensionProc because the ISAPIEXT.DLL extension is the registered mapping for SRF files in IIS.
  3. ISAPIEXT.DLL queues the client request to its thread pool. The pool size is configurable, but the default is twice the number of processors on the machine. The ISAPIEXT.DLL also contains two caches as member variables of the CISAPIExtension class: a DLL and a Stencil cache. The DLL cache contains loaded ATL Server request handler DLLs and the Stencil cache contains loaded and token-parsed SRF files.
  4. A worker thread from the pool takes this queued request and opens the SRF file to determine which application DLL should receive the request.
  5. Using the HTML comment in the SRF file, the worker thread loads the application DLL (if it isn't already in the DLL cache) and passes the request off to the default request handler class.
  6. The SRF file is parsed into tokens as it is rendered. Replacement methods in the handler class are called as tag tokens are found. Replacement methods can access the response stream and write to it.
  7. The composed response is sent to the client.

SRF File Syntax

      As you have seen, an SRF file contains HTML and very simple script tags. The syntax of an SRF file supports method calls into C++, DLL function mappings, flow control, looping, include files, and localization.
      SRF tags are used to call methods in your C++ classes. To do this, an SRF file must contain a list of the application DLLs it uses, which is created using handler tags.
      As you saw in the code for HelloWorld.SRF, the initial string
{{handler ATLServerHelloWorld.dll/Default}}
is a handler tag because it identifies an application DLL and request handler class within the DLL to use.
      The /Default specifier in this handler tag identifies the default C++ request handler class to send in response to the SRF requests. The default handler tag must appear before any other handler tags, and the first handler tag must be found within the first 4096 bytes of an SRF file. Each request handler class exports a string name to identify it within an SRF file. Request handler classes can also be identified by specifying the programmer-defined handler class name instead of /Default.
      Multiple application DLLs can be used by one SRF file. When more than one DLL is used, there is at most one default DLL and multiple named DLLs. Any named DLLs are listed with IDs in the request comment and are also identified by ID in the replacement tags. This example shows the handler tag format {{handler HANDLER_DLL_NAME/HANDLER_NAME>}} for the default handler, and {{id=NAME handler=HANDLER_DLL_NAME/HANDLER_NAME>}} for additional handlers:
{{handler ATLServerHelloWorld.dll/Default}}
{{id=OtherObj handler=ATLServerHelloWorld.dll/Other}}
<html>
<body>
{{HelloWorld}}<br>
{{OtherObj.OtherMethod}}
</body>
</html>
As you can see, this code names the default request handler class in the ATLServerHelloWorld.dll, and the request handler class named Other in the same DLL.
      Replacement tags that do not specify an ID will be passed to the default handler. Double curly braces identify server-side tags to interpret or replace. If the string within the double curly braces is not recognized as an SRF keyword, it is passed to a handler DLL for replacement. In the example shown, the ATLServerHelloWorld.dll interprets the HelloWorld tag by mapping it to the default request handler class and calls the replacement method associated with the HelloWorld tag.
      ATLServerHelloWorld.dll also interprets the OtherObj.OtherMethod tag by mapping it to the request handler class named Other and calls the replacement method associated with the OtherMethod tag.
      All replacement methods have the same basic syntax, as shown in the following lines of code.
DWORD OnHello()
{
    CWriteStreamHelper os(m_pStream);
    os << "Hello world from ATL Server" << endl;
    return HTTP_SUCCESS;
}
      Branching in SRF files requires the if, else, and endif keywords (at the time of this writing, elseif was not supported). In this example, if is used with a replacement tag:
{{handler ATLServerHelloWorld.dll/Default}}
<html>
<body>
{{if UserIsAllowed}}
{{HelloWorld}}
{{endif}}
</body>
</html>
      In the fourth line, UserIsAllowed results in a call to a replacement method in ATLServerHelloWorld.dll. This method returns either HTTP_SUCCESS or HTTP_S_FALSE for Boolean true or false, respectively. For example, the replacement method for UserIsAllowed is shown in the following lines of code:
DWORD OnUserIsAllowed()
{
    if (CallSomeOtherFunction())    
        return HTTP_SUCCESS;
    else
        return HTTP_S_FALSE;
}
This method does not write any HTML to the response stream; it only serves as an argument to the if statement in the SRF file.
      Looping requires the while and endwhile keywords. The while keyword, which is identical to if, uses a replacement method's return value as the conditional, as you can see here:
{{handler ATLServerHelloWorld.dll/Default}}
<html>
<body>
{{while KeepLooping}}
{{HelloWorld}}
{{endwhile}}
</body>
</html>
      An include mechanism is also supported. Using the include keyword, other SRF or HTML files can be included in an SRF file. The argument for an include token is a URL specifying the location of the SRF file to include. This means you can pass arguments to the included file. The include token is especially useful as a way of including standard HTML headers and footers, for example. This example shows the syntax {{include FILENAME}}.
{{handler ATLServerHelloWorld.dll/Default}}
{{include menu.srf}}
<html>
<body>
{{HelloWorld}}
</body>
</html>
      The codepage tag can be used to indicate the character set used by the SRF file. Specifying the codepage allows the server response file to be parsed correctly if it contains characters that are not part of the ANSI character set.
{{codepage utf-8}}
      The locale tag can be used to indicate that the specified locale should be used for any response generated from that point on in the file.
{{locale 1033}}
      That's it for SRF files. There are no local variables, no CreateObjects, and no string manipulations. All the heavy lifting will be done in C++ replacement methods. SRF files are just the glue between HTML and C++.

Creating a Basic ATL Server Project

      ATL Server includes its own wizard for creating new projects: the ATL Server Project Wizard, as shown in Figure 3.
Figure 3 Selecting the ATL Server Project Wizard
Figure 3 Selecting the ATL Server Project Wizard

This wizard has a multitude of settings and tabs. The Object Settings tab allows you to specify general directory and names settings (see Figure 4). In addition, you can set the IIS server and virtual root for the compiled application.

Figure 4 The Object Settings Tab
Figure 4 The Object Settings Tab

      You can create a main ISAPI extension DLL, an ATL Server application DLL, or both. You may want to create multiple ATL Server application DLLs and access them all through a single ISAPI extension DLL. This allows you to partition an application into multiple DLLs without having to use multiple ISAPI extension DLLs. Then, any of your SRF files can use any collection of replacement methods from any of these application DLLs.
      The Application Options tab (see Figure 5) specifies the use of SRF file validation and parsing. All of these options are turned on by default and are most often left that way. Other tabs and settings of this wizard will be covered later in this article.

Figure 5 The Application Options Settings Tab
Figure 5 The Application Options Settings Tab

      Using the settings shown in Figures 4 and 5, plus all of the other defaults, the wizard creates one solution (a new name for a workspace in Visual Studio .NET) with two projects: a project for the ISAPI extension DLL, and a project for the ATL Server DLL.
      The ISAPI extension DLL contains boilerplate code for ATL Server projects and seldom needs to be changed. The ATL Server application DLL initially contains one request handler class for adding your code. Each replacement method and its associated an SRF tag will ultimately be in this DLL.
      Each handler class is derived from the CRequestHandlerT class. It implements IRequestHandler for responding to render requests and is derived from CHtmlTagReplacer. It has member variables for accessing request and response streams, the associated ISAPI extension, the DLL cache, and server context information. This is also the class that parses an SRF file into tokens and caches it in the ISAPI extension. CRequestHandlerT also renders the SRF to HTML by calling replacement methods.

Figure 6 Extension Mapping for an SRF File
Figure 6 Extension Mapping for an SRF File

      The build process creates the two DLLs. To associate SRF files with your ISAPI application, you must modify the properties of the IIS virtual root for the application by adding an extension mapping for SRF files, as shown in Figure 6.

Handler Methods and MAPs

      ATL Server projects use two different MAPs to associate SRF replacement tags with C++ replacement methods: HANDLER_MAPs and REPLACEMENT_METHOD_MAPs. The HANDLER_MAP associates programmer-defined names with C++ replacement handler classes, which are the names that appear at the end of handler tags, as shown in this example:
BEGIN_HANDLER_MAP()
    HANDLER_ENTRY("Default", CHelloWorld)
    HANDLER_ENTRY("Hello", CHelloWorld)
    HANDLER_ENTRY("Other", COther)
END_HANDLER_MAP()
      Each HANDLER_ENTRY lists a name to use in a handler tag in an SRF file, and the C++ class to which the name refers. There must be at least one HANDLER_ENTRY for the Default replacement handler class. A replacement handler class must be derived from CRequestHandlerT. There is one HANDLER_MAP per ATL Server DLL. Assuming the map in Figure 6 appears in ATLServerHelloWorld.dll, an SRF file could use the replacement handler classes in this code:
{{handler ATLServerHelloWorld.dll/Default}}
{{id=HelloObj handler=ATLServerHelloWorld.dll/Hello}}
{{id=OtherObj handler=ATLServerHelloWorld.dll/Other}}
<html>
<body>
<!--Use the default request handler class-->
{{SomeMethod}}
<br>
<!--Use the "Hello" request handler class-->
{{Hello.HelloWorld}}
</body>
</html>
      The REPLACEMENT_METHOD_MAP associates SRF replacement tags with C++ replacement methods of classes that appear in the HANDLER_MAP. Each replacement handler class will have its own REPLACEMENT_METHOD_MAP, like so:
BEGIN_REPLACEMENT_METHOD_MAP(CHelloWorld)
    REPLACEMENT_METHOD_ENTRY("HelloWorld", OnHelloWorld)
END_REPLACEMENT_METHOD_MAP()
Each REPLACEMENT_METHOD_ENTRY lists a replacement tag (as it appears in an SRF file) and the associated C++ replacement method to call.
      Replacement methods can take parameters in the form of a string. If you want replacement methods that take typed arguments (not just a string), you need to parse the string and, if necessary, convert to the required type. This means you need a parsing function as well as a replacement method. To specify this in the REPLACEMENT_METHOD_MAP, you need the REPLACEMENT_METHOD_ENTRY_EX macro. The syntax of the macro is as follows:
REPLACEMENT_METHOD_ENTRY_EX(methodName, methodFunc,
                            paramType, parseFunc)
This example passes an integer and a string to the replacement method:
{{handler ATLServerHelloWorld.dll/Default}}
<html>
<body>
{{FormatData(42,Some data)}}
</body>
</html>
Figure 7 shows the implementation of the replacement method and the parsing function.

Processing SRF Files

      The processing of SRF files is done in two phases. In the first phase, the SRF file is parsed into a list of tokens. Each token is stored in a StencilToken structure. In the second phase, the tokens of the parsed stencil are rendered. The class CStencil is responsible for parsing the SRF file and supplying the rendering algorithm for each of the known token types.
      To accomplish the rendering, however, a couple of other classes are involved. CStencil, CISAPIExtension, and CRequestHandlerT work in conjunction with each other to provide the stencil-rendering functionality.
      When a request is received, the default request handler for an SRF file is loaded. This request handler then loads and parses the SRF file. The first step is to check if this SRF file has already been cached. The cache is maintained by a global instance of CISAPIExtension. If not already cached, the SRF file is parsed, and any additional request handler DLLs are loaded and cached in the global instance of CISAPIExtension. The actual parsing is done by an instance of CStencil or a CStencil-derived class. Then as each token is rendered, the appropriate request handler DLL is retrieved from the cache and the rendering takes place. Tokens in a parsed stencil are shown in Figure 8.
      Recall that after an SRF file is parsed, the various parsed elements are stored as instances of StencilToken. The various tokens are, of course, rendered using different algorithms. A text token is not manipulated at all. The data for the token is simply written to the HTML stream.
      If the token to render is a simple replacement token, then the appropriate request handler and replacement method is located. The replacement methods themselves write appropriate HTML to the stream. To prevent unnecessary overhead, the parsed SRF files, in the form of stencils and request handler DLLs, are cached by an instance of CISAPIExtension. ATL Server checks to see if a stencil has been modified after it has been cached. If so, the new SRF file is parsed into a stencil and then cached.

HTTP Streams

      The IWriteStream interface is used to write to the HTTP response stream in your replacement handler methods.
interface IWriteStream
{
    virtual HRESULT WriteStream(LPCSTR szOut, int nLen,
                                DWORD *pdwWritten)=0;
    virtual HRESULT FlushStream()=0;
};
ATL Server has two techniques for using an IWriteStream pointer: a wrapper class and a member variable.
      ATL Server provides the CWriteStreamHelper class to wrap an IWriteStream interface pointer and to use C++ iostreams. A CWriteStreamHelper is constructed from an IWriteStream and provides overloaded Write methods as well as overloaded operator methods for writing fundamental types to the HTTP response stream. This implies implementing a replacement method, as shown in Figure 9.
      CRequestHandlerT also has a public member variable, m_HttpRequest, which can be used to write to the response stream. This member variable is an instance of the CHttpResponse class. This class buffers any writes to the response stream and is itself derived from IWriteStream and CWriteStreamHelper. Figure 10 shows the use of this member variable.

Forms Processing

      The CHttpRequest class provides access to the client request stream, including request parameters, form variables, query strings, request methods, user authentication, and cookies.
      Your replacement handler class inherits a public member variable, m_HttpRequest, from its CRequestHandlerT parent class. CHttpRequest exposes a CHttpRequestParams class to encapsulate form variables and query strings. If the request was a POST, the elements of the FormVars array are obtained from the body of the request. If it was a GET request, the elements are obtained from the query string. Using this one class, you can access form variables and query strings in the same manner.
      In Figure 11, a CHttpRequest object is used to read form variables from the request into local string variables. Here, m_HttpRequest.FormVars is of type CHttpRequestParams&. Usually, more code is needed to check for the existence of the form variable before retrieving it. In ATL Server, you simply request the form variable and use a CValidateContext instance to determine if any of the variable access failed. It is only necessary to call the CValidateContext::ParamsOK once after retrieving all the needed forms variables. Of course, if you still need individual variable checking, you can just call m_HttpRequest.FormVars.Lookup and check the resulting pointer.

Using Cookies

      As specified by the HTTP protocol (see RFC 2616 at http://www.ietf.org/rfc/rfc2616.txt), client requests typically have header fields that provide additional information about the request or the client. The request headers can also contain cookies, if the client supports them. This section looks at retrieving and using cookies, and the following section looks at using standard header fields.
      A Web server can store small amounts of data on a client using cookies. What makes cookies so convenient is that the data stored in the cookie is returned with every request from the client. From the HTTP perspective, a cookie is just a name-value pair (defined by the application) along with some standard attribute-value pairs that define how the browser (technically, the USER_AGENT) should interact with the cookie. The cookies are transmitted as part of the request and response headers. To see the gory details, check out RFC 2109 at http://www.ietf.org/rfc/rfc2109.txt.
      Not surprisingly, ATL Server has a class to simplify cookie management for the developer: CCookie. CCookie can be used to represent a cookie sent from the server or returned by the client. CCookie provides methods to get and set application-defined pairs as well as methods for manipulating the standard attribute-value pairs.
      Cookies that store a single value are called single-valued cookies; cookies that store multiple values are called dictionary cookies or multivalued cookies. CCookie allows you to treat the name-value pairs as a collection.
      The following code fragment demonstrates using CCookie to create a single-valued cookie and append it to the response.
DWORD SetupUID()
{
    CString strCommand;
    strCommand.Format("%s", m_UserID);
    CCookie cookie;
    cookie.SetName("userid");
    cookie.SetValue(strCommand);
    cookie.SetPath("/");
    m_HttpResponse.AppendCookie(&cookie);
}
      To create a multivalued cookie, you call AddValue, not SetValue. Calling AddValue after calling SetValue (or the reverse) will fail. To remove a cookie's value, call SetValue(NULL).
      Using a cookie in ATL Server is just as simple. Instead of using the CHttpResponse object, use the CHttpRequest object:
CString szUID;
If (!m_HttpRequest.Cookies("userid").GetValue(szUID))
{
    // respond appropriately
}
      As you can see, cookie support in ATL Server is fairly comprehensive, and relatively simple to use. Just remember that storing and retrieving cookie values is done with CHttpResponse and CHttpRequest, respectively. Also, remember to be conservative in the amount of data you put in the cookies. The bigger the header, the longer the download.

Using Standard Header Fields

      Using the standard header fields (also known as server variables) is even easier. CHttpRequest has a number of member functions that support the retrieval of corresponding fields. For example, a very common scenario is to sniff the browser type to see if the client is the required version. If not, the client is redirected to some appropriate location, as you can see in these lines of code:
CString strUserAgent;
M_HttpRequest.GetUserAgent(strUserAgent);
if (!strstr(strUserAgent,"IE 5"))
{
    RedirectTo(&m_HttpRequest,&m_HttpResponse,"unsupported.srf");
    Return HTTP_SUCCESS_NO_PROCESS;
}
      Figure 12 shows the methods that are available to retrieve server variables.

Managing Session State

      ATL Server would not be complete without session state management services. The Server Option tab of the ATL Server Wizard has options for using Session Services (see Figure 13). The session information can be stored in memory or in a database.

Figure 13 The Server Options Tab
Figure 13 The Server Options Tab

      The classes CPersistSessionDB and CPersistSessionMem do the bulk of the work. The CPersistSessionDB class is used to persist session variables in an OLE DB data source. The OLE DB connection string is passed as a parameter to the initialize function. The schema for the data source should be compatible with the information shown in Figure 14.
      CPersistSessionMem is used to persist session variables to memory. Both of these classes allow you to add, remove, and update session variables, as well as enumerating them. CPersistSessionDB or CPersistSessionMem are used as the template parameter for the class CSessionStateService. CSessionStateService is then used to read and write session variables. All the normal caveats apply with ATL Server's implementation of session state management. You can only use the memory-based session services on a single computer.

HTTP File Upload

      Forms can be sent to a Web server using either application/x-www-form-urlencoded, or multipart/form-data encoding. For application/x-www-form-urlencoded, there are a series of name-value pairs. The user who fills out the form supplies the values. Each field has a name. Within a given form, the names are unique. For multipart/form-data, there are a series of parts. Since any data can be transferred in each part, these parts are often saved as disk files.
      Parsing multipart/form-data is a nontrivial task, but more importantly, it is infrastructure that will have to be written and debugged. To simplify matters, ATL Server provides a class, CMultiPartFormParser, that parses multipart data into separate files, and another class, CHttpRequestFile, that keeps track of information about each file. CHttpRequest stores a list of CHttpRequestFile instances to keep track of each file uploaded. The ATL Server developer simply has to make use of the list of CHttpRequestFile instances to manipulate the uploaded files.
      ATL Server uses the system TEMP directory to store the uploaded files, so you may want to consider moving TEMP to a drive other than your system drive. Figure 15 shows how to move the uploaded files from the server's temporary directory to a well-known location. In this example, a client has uploaded a file to a personal photo gallery.
      First, check that at least one file was uploaded. Next, use the userid to generate a path. This path is where the uploaded files will be stored when moved from the TEMP directory. Then, iterate through the list of uploaded files and physically move them. The AddImage method just updates a database table with this information. To learn more about the subtleties of multipart/form-data, take a look at RFC 2388 on the IETF Web site (http://www.ietf.org/rfc/rfc2388.txt).
      Don't be lulled into a false sense of security by the simplicity of handling file uploads using ATL Server. Whenever you decide to accept files (or any other large request) there are issues to consider. First of all, performance can be affected since it takes time and consumes resources to read and parse a large request. Also, with file uploads you need to consider the overhead of disk I/O. What's worse, though, is the danger of a server crash if you do not handle the uploaded files correctly. For example, what happens if you exhaust disk space?
      If you decide you want to limit the size of all uploads (not just file uploads), you can override CRequestHandlerT::ValidateAndExchange and compare your maximum request size with the value returned by CHttpRequest::GetTotalBytes. You can reject any requests that exceed the maximum size by returning HTTP_REQUEST_ENTITY_TOO_LONG.

Thread Pooling

      As most serious IIS-based developers know, IIS has a thread pool available to service client requests. Once the pool reaches 100 percent utilization, IIS starts to queue user requests. In many cases this may be unnecessary since a thread in use may, for example, be blocked on I/O waiting for a database call to return. The result is that the thread is unavailable to other client requests until the current request completes.
      As a result of this, many developers seeking better performance would code ISAPI applications to spin off their own thread pool, thus offloading the IIS thread pool. Though not particularly difficult, this means yet another piece of infrastructure for developers to write to get the best possible performance.
      Not surprisingly, ATL Server helps developers in this area, too. The global instance of ClSAPIExtension has a member of type CThreadPool. The exact thread pool class is specified as a template parameter. The default class manages a simple I/O completion port-based thread pool.
      The thread pool defaults to two threads per processor, but by overriding the GetNumThreads method, you can control how many threads the pool creates in one of three ways: returning a positive number from GetNumThreads, which causes exactly that number of threads to be created; returning a value n that is less than zero, which causes the absolute value of n*(number of processors) threads to be created; and returning zero, which results in the default of two threads per processor.
      Similarly, by overriding other CThreadPool virtual functions, various thread characteristics can be controlled. For example, overriding GetStackSize can control the default stack size for each thread in the pool. Whether or not the pooled threads can make use of the C Runtime Library is controlled by a template argument for the CThreadPool class. Currently the pool size is static, but nothing prevents you from implementing your own thread pool manager class and any pool management algorithm you want.
      How do requests get from the exported global function HttpExtensionProc to the request handler on some worker thread? The answer is fairly simple. The global HttpExtensionProc simply delegates to CISAPIExtension::HttpExtensionProc. This function takes the electronic control block (ECB) and posts it to the I/O completion port queue. It then returns HSE_STATUS_PENDING, which tells IIS that the request is not yet complete and that you are waiting for further callbacks. Essentially, HSE_STATUS_PENDING tells IIS that the current thread is reusable, since you are waiting for a callback.
      When a thread in the thread pool becomes available to service the request, it is delegated to a worker class for actual servicing of the request. The Execute method of the Worker class typically finds the name of the default request handler for the SRF file and calls HandleRequest. This causes the CHttpRequest object to initialize, kicking off the rendering of the SRF file.

PerfMon Counters

      It is obvious that if you are writing high-performance Web applications, monitoring the health of the ISAPI application is a good idea. The Windows® 2000 performance monitor infrastructure is a natural candidate for this task.

Figure 16 Developer Support Options
Figure 16 Developer Support Options

      You should provide a custom set of PerfMon counters for your ATL Server application. Previously you had to write your own PerfMon extension DLL, but with ATL Server this is no longer the case. The Developer Support Options tab of the ATL Server Wizard has a checkbox for adding PerfMon support (see Figure 16). When selecting Performance Monitoring Support in the AppWizard, the default behavior is to provide support for the three counters seen in Figure 17.

Figure 17 Add Counters
Figure 17 Add Counters

      PerfMon Extension DLLs are required to export three functions: Open, Collect, and Close. These methods are called whenever a performance monitoring application wants to retrieve performance information. ATL Server provides a class named CPerfMon that provides an implementation of Open, Collect, and Close. An instance of this class is used in the ATL Server ISAPI Extension DLL. The three required exported functions are implemented as stubs that delegate to the instance of CPerfMon.
      Extending this default performance-monitoring infrastructure takes just a few steps:
  1. Derive a struct from CPerfObject and add member variables as necessary to maintain the various counters and objects you are interested in. For example:
    struct CMyPerfStatObject public CPerfObject
    {
        long m_loginFailures;
    };
  2. Derive a class from CPerfMon and populate the PERF_MAP, as shown in Figure 18. The DEFINE_PERF_OBJECT macro is used to define a new type performance object, and the DEFINE_PERF_COUNTER macro is used to define a new performance counter.
  3. Add a global instance of the CPerfMon-derived class to the ISAPI extension project.
  4. Add the necessary code in your request handlers to update the counters.
      There are, of course, guidelines to follow in tracking performance metrics. The most important is to make sure that gathering and calculating performance metrics does not have significant impact on CPU utilization or performance. Consult the Platform SDK documentation for more advice on custom performance counters and designing performance extension DLLs.

Conclusion

      At the time of this writing, much of the technology discussed here was available only on the Visual Studio® beta. Much of this information has since been announced at the Microsoft Professional Developers' Conference.
      Two things you should remember about the ATL Server technology are that it exposes the misconception that high-performance sites and rapid development are mutually exclusive. ATL Server brings Web site development in C++ on par with ASP. More importantly, ATL Server provides ease-of-development without imposing standard design decisions made by a tool development team on your behalf.
      The ATL Server team has provided a more than adequate base implementation, and complete flexibility in customizing the ATL Server framework. For example, if you want to extend the ATL Server syntax to add a new token type, you can derive a new class from CStencil and pass the new class name as a template argument to the CRequestHandlerT base class, CHtmlTagReplacer. Or you can change the thread pooling algorithm by designing a new thread pooling class and passing it as a template argument to the class CISAPIExtension. You can also add custom initialization or custom processing of requests by overriding CRequestHandlerT::HandleRequest and writing the code you need. This is, of course, not an exhaustive list of possibilities, but it should give you some idea of the flexibility of the ATL Server framework.
      You should seriously consider ATL Server as an implementation technology candidate. Not only does ATL Server provide a simple programming model, but it also provides the performance, ease of debugging, and flexibility of a modern object-oriented language.
For related articles see:
Understanding the IIS Architecture

For background information see:
ATL Server: High Performance Web Application with Visual C++

Shaun McAravey is the Vice President of Emerging Technology at STEP Technology (shaun@steptech.com). He lives in Portland, Oregon, and wonders how soon his three sons and daughter will know more about computers than he does.

Ben Hickman is a staff software engineer at STEP Technology (ben@hickman.net) in Portland, Oregon, where he longs for rain.

Page view tracker