ATL Server Web
Projects
Without a doubt, the most dramatic recent
addition to the ATL library is a suite of classes and tools
collectively termed ATL Server. ATL Server accounts for nearly all
of the fourfold increase in the overall size of ATL from ATL 3.
This extensive class library provides comprehensive support for
building web applications and XML web services. Although
traditional ASP and the ASP.NET platform offer compelling and
easy-to-use frameworks for web-based development, many application
developers must still resort to raw ISAPI programming for
applications that demand low-level control and maximum performance.
ATL Server is designed to provide the performance and control of
ISAPI with the feel and productivity of ASP. To that end, ATL
Server follows the design model that has made conventional ATL
development so effective over the years: namely small, fast,
flexible code.
VS provides excellent
wizard support for building web applications and web services.
Walking through the numerous options available for ATL Server
projects is actually quite insightful in understanding both the
architecture and the sheer scope of the support provided. VS
provides a wizard to help you get started building a web
application with ATL Server. You launch this wizard by selecting
the ATL Server Project option from the Visual C++ folder of the New
Project dialog box.
The Project Settings tab shown in Figure 1.21 displays the selected
options for generating and deploying the DLLs that comprise our web
application.
By default, ATL Server generates two projects in
your solution: a web application DLL and an ISAPI extension DLL.
ISAPI extension DLLs are loaded into the IIS process
(inetinfo.exe) and logically sit between IIS and your web
application DLL. Although ISAPI extensions can handle HTTP requests
themselves, it is more common for them to provide generic
infrastructure services such as thread pooling and caching, leaving
web application DLLs to provide the real HTTP response logic. The
ATL Server Project Wizard generates an ISAPI extension
implementation that communicates with special functions in your web
application called handlers. Figure 1.22 depicts this arrangement.
The Generate Combined DLL check box enables you
to combine everything into a single DLL. This might be an
appropriate option if the ISAPI extension is not intended to be
used in other web applications. Conversely, developers can opt to
leverage ATL Server's extensibility features by creating
specialized ISAPI extensions with such options as custom thread
pooling, highly tuned caching schemes, or optimized state
management. These ISAPI extensions would then likely be reused
across multiple web applications. Furthermore, keeping the ISAPI
extension as a separate DLL gives us the flexibility to add
handlers to our web application without restarting the web server
(handler classes are discussed shortly). We'll leave the box
unchecked for our first web application and allow VS to generate
separate projects.
The Deployment Support check box enables the VS
web-deployment tool. With this option selected, the Visual Studio
build process automatically performs additional steps for properly
deploying your web application so that it is served by IIS. You'll
see in a moment how convenient these integrated deployment features
can be. A brief word of caution at this point is in order, however.
The default setting to enable deployment support causes VS to
deploy the built project files in a subdirectory of your default
web site, typically <drive>:\inetpub\wwwroot. In a
real-world development scenario, it might be more desirable to
deploy in a different directory on the machine (such as the project
directory). Several steps are required to accomplish this, so for
now, we're sticking with the default setting just so that we can
focus on developing our application.
The Server Options tab
shown in Figure 1.23
enables you to select various performance-oriented options for your
web application. Several types of caching are supported, including
support for arbitrary binary data (Blob cache), file caching, and
database connection caching (Data source cache). Additionally,
high-availability sites rely upon robust session-state management.
ATL Server provides two mechanisms for persisting session state.
The OLE DB-backed session-state services radio button includes
support for persisting session state in a database (or other OLE DB
data source), which is an option suited to applications running on
web farms.
Figure
1.24 shows the selections available under the Application
Options tab. Validation Support generates the necessary code for
validating items in the HTTP request from the client, such as query
parameters and form variables. Stencil Processing Support generates
skeleton code for using HTML code templates known as server
response files (SRF). These text files (also known as stencils) end
with an .srf extension and intermix static HTML content
with special replacement tags that your code processes to generate
dynamic content at runtime. With stencil processing enabled, the
wizard also allows you to select the locale and codepage for
properly localizing responses. This simply inserts locale and
codepage tags into the generated SRF file. (More on using SRF files
comes shortly.) The Create as Web Service option also is discussed
further in the following section. Because we're developing a web
application, we leave this box unchecked for now.
The remaining set of
options for your ATL Server project appears under the Developer
Support Options tab, shown in Figure 1.25. Generating TODO comments simply helps
alert the developer to regions of code where additional
implementation should be provided. If you select Custom Assert and
Trace Handling Support, debug builds of your project will include
an instance of the CDebugReportHook class, which can
greatly simplify the process of debugging your web applicationeven
from a remote machine.
Pressing Finish causes the wizard to generate a
solution that contains two projects: one for your web application
DLL (with a name matching the <projectname> entered
in the New Project dialog box) and one for your ISAPI extension
(with a name <projectname>Isapi). Let's take a look
at the code generated in the ISAPI extension project. The generated
.cpp file for our ISAPI extension looks like the
following:
class CPiSvrWebAppModule :
public CAtlDllModuleT<CPiSvrWebAppModule> {
public:
};
CPiSvrWebAppModule _AtlModule;
typedef CIsapiExtension<> ExtensionType;
// The ATL Server ISAPI extension
ExtensionType theExtension;
// Delegate ISAPI exports to theExtension
//
extern "C"
DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB) {
return theExtension.HttpExtensionProc(lpECB);
}
extern "C"
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer) {
return theExtension.GetExtensionVersion(pVer);
}
extern "C" BOOL WINAPI TerminateExtension(DWORD dwFlags) {
return theExtension.TerminateExtension(dwFlags);
}
// DLL Entry Point
//
extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason,
LPVOID lpReserved) {
hInstance;
return _AtlModule.DllMain(dwReason, lpReserved);
}
Because the ISAPI extension
uses the services of ATL for object creation, it needs an ATL
Module object. Also included in the generated code are
implementations of the three well-known entry points IIS uses to
communicate HTTP request information to the ISAPI extension:
HttpExtensionProc, GetExtensionVersion, and
TerminateExtension. These implementations simply delegate
to a global instance of CIsapiExtension, whose definition
is given here:
template <
class ThreadPoolClass=CThreadPool<CIsapiWorker>,
class CRequestStatClass=CNoRequestStats,
class HttpUserErrorTextProvider=CDefaultErrorProvider,
class WorkerThreadTraits=DefaultThreadTraits,
class CPageCacheStats=CNoStatClass,
class CStencilCacheStats=CNoStatClass
>
class CIsapiExtension :
public IServiceProvider,
public IIsapiExtension,
public IRequestStats
{... }
This class provides boilerplate functionality
for implementing the ISAPI extension. The template parameters to
this class provide pluggable implementation for things such as
threadpool management, error reporting, and caching statistics. By
replacing this class in the .cpp file with your own
CIsapiExtension-derived class and providing your own
classes as template parameters, you can highly customize the
behavior of your ISAPI extension. Techniques for doing this are
presented in Chapter
13, "Hello, ATL Server." The default implementation of the
ISAPI extension is suitable for our demonstration purposes
here.
Most of the action takes place in the web
application project. The wizard generated a skeleton SRF file for
us and placed it in the project. The HTML editor integrated into VS
provides a convenient means of viewing and manipulating the
contents of this file.
<html>
{{ handler PiSvrWebApp.dll/Default }}
<head>
</head>
<body>
This is a test: {{Hello}}<br>
</body>
</html>
Items that appear within double braces indicate
commands that are passed to the stencil processor. The
{{handler}} command specifies the name of the DLL that
houses our handler classes for processing replacement tags that
appear in the SRF file. The /Default specifier identifies
the default request-handler class to use for processing replacement
tags. In general, an application DLL can contain multiple handler
classes for processing SRF commands, and these classes can even
exist in multiple DLLs. We use only a single handler class in a
single application DLL, so all commands destined for handler
classes will be routed to the same handler class. In the earlier
wizard-generated skeleton, the {{Hello}} tag will be
passed on to a handler class and replaced by the HTML produced from
that class's replacement method.
ATL Server uses several macros to map commands
in the SRF file to handler classes in our application DLL. The
class definition generated for us in the
<projectname>.h file shows how these macros are
used:
class CPiSvrWebAppHandler
: public CRequestHandlerT<CPiSvrWebAppHandler>
{
public:
BEGIN_REPLACEMENT_METHOD_MAP(CPiSvrWebAppHandler)
REPLACEMENT_METHOD_ENTRY("Hello", OnHello)
END_REPLACEMENT_METHOD_MAP()
HTTP_CODE ValidateAndExchange() {
// Set the content-type
m_HttpResponse.SetContentType("text/html");
return HTTP_SUCCESS;
}
protected:
HTTP_CODE OnHello(void) {
m_HttpResponse << "Hello World!";
return HTTP_SUCCESS;
}
};
The CRequestHandlerT base class
provides the implementation for a request-handler class. It uses
the REPLACEMENT_METHOD_MAP to map the strings in
replacements in the SRF file to the appropriate functions in the
class.
In addition to the request-handler class itself,
in the handler DLL's .cpp file, you'll find this
additional global map:
BEGIN_HANDLER_MAP()
HANDLER_ENTRY("Default", CPiSvrWebAppHandler)
END_HANDLER_MAP()
The HANDLER_MAP is
used to determine which class to use to process substitutions given
with a particular name. In this case, the string "Default"
as used in the handler tag in the SRF file is mapped to the
CPiSvrWebAppHandler class. When the {{Hello}} tag
is encountered in the SRF file, the OnHello method is
invoked (via the REPLACEMENT_METHOD_MAP). It uses an
instance of CHttpResponse declared as a member variable of
the CRequestHandlerT to generate replacement text for the
tag.
Let's modify the wizard-generated code to
display pi to the number of digits specified in the query string of
the HTTP request. First, we modify the SRF file to the
following:
<html>
{{ handler PiSvrWebApp.dll/Default }}
<head>
</head>
<body>
PI = {{Pi}}<br>
</body>
</html>
We then add a replacement method called
OnPi to our existing handler class and apply the
[tag_name] attribute to associate this method with the
{{Pi}} replacement tag. In the implementation of the
OnPi method, we retrieve the number of digits requested
from the query string. The CHttpRequest class stored in
m_HttpRequest member variable exposes an instance of
CHttpRequestParams. This class provides a simple
Lookup method to retrieve individual query parameters from
the query string as name-value pairs, so processing requests such
as the following is a simple matter:
http://localhost/PiSvrWebApp/PiSvrWebApp.srf?digits=6
The OnPi method implementation to field
such requests follows:
class CPiSvrWebAppHandler {
...
HTTP_CODE OnPi(void) {
LPCSTR pszDigits = m_HttpRequest.m_QueryParams.Lookup("digits");
long nDigits = 0;
if (pszDigits)
nDigits = atoi(pszDigits);
BSTR bstrPi = NULL;
CalcPi(nDigits, &bstrPi);
m_HttpResponse << CW2A(bstrPi);
return HTTP_SUCCESS;
}
...
};
When we build our
solution, VS performs a number of convenient tasks on our behalf.
Because this is a web application, simply compiling the code into
DLLs doesn't quite do the trick. The application must be properly
deployed on our web server and registered with IIS. This involves
creating a virtual directory, specifying an appropriate level of
process isolation, and mapping the .srf file extension to
our ISAPI extension DLL. Recall that when we created the project,
we chose to include deployment support on the Project Settings tab
of the ATL Server Project Wizard, shown previously in Figure 1.25. As a result, VS
invokes the VCDeploy.exe utility to automatically perform all the
necessary web-deployment steps for us. Simply compiling our
solution in the normal manner places our application DLL, our ISAPI
extension DLL, and our SRF file in a directory under our default
web site, typically ending up in the directory
<drive>:\inetpub\wwwroot\<projectName>. VS
uses our web application project name as the virtual directory
name, so browsing to
http://localhost/PiSvrWebApp/PiSvrWebApp.srf?digits=50
produces the result in Figure
1.26.
For more information about building ISAPI
applications, including web services, with ATL Server, see
Chapter 13, "Hello,
ATL Server."
|