Server Response
Files
ATL Server provides a text-replacement system
called Server Response Files (referred to in the ATL Server code
and documentation occasionally as Stencil Files). An .srf
file is an HTML file with some replacement markers. Consider this
example, which is used to display the lyrics to a classic song:
<html>
{{handler Beverage.dll/Default}}
<head>
<title>The Beverage Song</title>
</head>
<body>
{{if InputValid}}
{{while MoreDrinks}}
<p/>
{{DrinkNumber}} bottles of {{Beverage}} on the wall, <br />
{{DrinkNumber}} bottles of {{Beverage}}.<br />
Take one down, pass it around,<br />
{{NextDrink}} bottles of {{Beverage}} on the wall.<br />
{{endwhile}}
{{else}}
<h1>You must specify a beverage and the number of them
in the query string.</h1>
{{endif}}
</body>
</html>
<head>
As we discussed in
Chapter 13, the items
within {{ }} are used for one of three purposes. They can
be directives to the stencil processor (the handler
directive), they can work as flow control (if and
while), or they can be replaced at runtime by the request
handler class. Any text outside the markers is simply passed
straight to the output.
The actual replacements are handled by a class
referred to a request handler. An
example request handler for the song follows:
class CBeverageHandler
: public CRequestHandlerT<CBeverageHandler> {
public:
BEGIN_REPLACEMENT_METHOD_MAP(CBeverageHandler)
REPLACEMENT_METHOD_ENTRY("InputValid", OnInputValid)
REPLACEMENT_METHOD_ENTRY("MoreDrinks", OnMoreDrinks)
REPLACEMENT_METHOD_ENTRY("DrinkNumber", OnDrinkNumber)
REPLACEMENT_METHOD_ENTRY("Beverage", OnBeverage)
REPLACEMENT_METHOD_ENTRY("NextDrink", OnNextDrink)
END_REPLACEMENT_METHOD_MAP()
HTTP_CODE ValidateAndExchange() {
m_numDrinks = 0;
m_HttpRequest.GetQueryParams().Exchange( "numdrinks",
&m_numDrinks );
m_beverage =
m_HttpRequest.GetQueryParams().Lookup("beverage");
m_HttpResponse.SetContentType("text/html");
return HTTP_SUCCESS;
}
protected:
HTTP_CODE OnInputValid( ) {
if( m_numDrinks == 0 || m_beverage.IsEmpty() ) {
return HTTP_S_FALSE;
}
return HTTP_SUCCESS;
}
HTTP_CODE OnMoreDrinks( ) {
if( m_numDrinks > 0 ) {
return HTTP_SUCCESS;
}
return HTTP_S_FALSE;
}
HTTP_CODE OnDrinkNumber( ) {
m_HttpResponse << m_numDrinks;
return HTTP_SUCCESS;
}
HTTP_CODE OnBeverage( ) {
m_HttpResponse << m_beverage;
return HTTP_SUCCESS;
}
HTTP_CODE OnNextDrink( ) {
m_numDrinks;
if( m_numDrinks > 0 ) {
m_HttpResponse << m_numDrinks;
} else {
m_HttpResponse << "No more";
}
return HTTP_SUCCESS;
}
private:
long m_numDrinks;
CStringA m_beverage;
};
Request handlers inherit
from the CRequestHandlerT base class. A request handler
needs to implement the ValidateAndExchange method, which
gets called at the start of processing the HTTP request. In
processing a form post, this is where you would process the
submitted form fields. If this function returns HTTP_FAIL,
the request is aborted and IIS sends back an HTTP 500 error to the
client.
If, as you would usually prefer,
ValidateAndExchange returns HTTP_SUCCESS, the
stencil processor starts rendering the SRF file. Each time a
replacement occurs, the processor calls back into the
response-handler object.
The REPLACEMENT_METHOD_MAP() macros in
the response-handler class are used to specify which methods should
be called for which replacement. In the previous code, this line
says that when the {{Beverage}} replacement is found in
the .srf file, the OnBeverage method should be
called:
REPLACEMENT_METHOD_ENTRY("Beverage", OnBeverage)
Actually generating the
output is fairly simple using the m_HttpResponse member,
which is inherited from the CRequestHandlerT base class.
This is an instance of the CHttpResponse class, already
initialized and ready to use. Figure 14.1 shows the result of this page
running.
Request-Handler
Routing
How does the stencil processor know which
response handler class to use? In the .srf file itself,
you might have noticed this line:
{{handler Beverage.dll/Default}}
The handler directive says which DLL
the handler is in (Beverage.dll, in this case) and what
the name of the handler is (Default). This might seem
strange because the name of our handler class isn't
Default; it's CBeverageHandler. ATL Server isn't
reading anybody's mind here. Instead, a global map in the response
DLL provides the mapping between the name you use in
the handler directive and the actual class. If you look in
your request handler project's .cpp file, you'll see
something like this at global scope:
// Beverage.cpp
...
BEGIN_HANDLER_MAP()
HANDLER_ENTRY("Default", CBeverageHandler)
END_HANDLER_MAP()
This is one way to get your handler into the
map: Simply add a new HANDLER_ENTRY macro to the map every
time you add a new request-handler class. However, this global map
is difficult to maintain over time. It sure would be nice to have
the handler name with the class that handles it.
Much like the COM_OBJECT_ENTRY_AUTO
macro for ATL COM classes, there's a macro that you can put in your
.h file instead: DECLARE_REQUEST_HANDLER. You use
it like this:
class CBeverageHandler : ... { ... };
DECLARE_REQUEST_HANDLER( "Default", CBeverageHandler,
::CBeverageHandler )
This macro uses similar linker tricks to the
COM_OBJECT_ENTRY_AUTO macro to stitch together the tables
at link time. The default project generated by the ATL Server
project template uses HANDLER_ENTRY; for your own
request-handler classes, I would recommend using
DECLARE_REQUEST_HANDLER instead. Unfortunately,
DECLARE_REQUEST_HANDLER is undocumented at this time. The
parameters to the macro are, in order, the handler name, the name
of the request-handler class without any namespaces, and the name
of the request handler including the namespaces.
Now that you've seen the various pieces, let's
look at the .srf-processing pipeline. The first stop for
the HTTP request is IIS. IIS checks its configuration and finds
that, for this virtual directory, it should route the request to
our ATL Server ISAPI Extension DLL.
So IIS loads (on the first request) the
extension DLL and calls the HttpExtensionProc method. This
immediately calls into the global instance of
CIsapiExtension.
CIsapiExtension takes the request,
builds a CServerContext object, places the request onto
its internal thread pool, and releases the IIS thread back to
handle another incoming request.
Meanwhile, the extension DLL's thread-pool
threads are hungrily waiting for work to come in. The first one
available pulls the request off the internal queue and hands it to
the working class (which is, by default,
CIsapiWorker).
The actual work is done in
the Execute() method:
void CIsapiWorker::Execute(AtlServerRequest *pRequestInfo,
void *pvParam, OVERLAPPED *pOverlapped) {
_ATLTRY {
(static_cast<IIsapiExtension*>(pvParam))->
DispatchStencilCall(pRequestInfo);
} _ATLCATCHALL() {
ATLASSERT(FALSE);
}
}
A pointer to the CIsapiExtension object
is passed in via the pvParam parameter. The worker object
then turns around and calls back into the CIsapiExtension
via the DispatchStencilCall method. Why go back to the
CIsapiExtension instead of doing the work within the
worker class? The following chunk of the
DispatchStencilCall method reveals the answer:
BOOL DispatchStencilCall(AtlServerRequest *pRequestInfo) {
...
HTTP_CODE hcErr = HTTP_SUCCESS;
if (pRequestInfo->dwRequestState == ATLSRV_STATE_BEGIN) {
BOOL bAllowCaching = TRUE;
if (TransmitFromCache(pRequestInfo, &bAllowCaching)) {
return TRUE;
}
...
}
...
}
The results of processing the SRF file are
stored in a cache and are regenerated only when needed. The cache
is stored in the ISAPI extension object so that it is available to
all the worker threads.
The DispatchStencilCall method takes
care of the details of the various states in which a request can
be. The request eventually ends up at a new instance of your
request-handler object, and that's where we go next.
Request
Handlers
All request handlers derive from the
CRequestHandlerT template:
template < class THandler,
class ThreadModel=CComSingleThreadModel,
class TagReplacerType=CHtmlTagReplacer<THandler> >
class CRequestHandlerT :
public TagReplacerType,
public CComObjectRootEx<ThreadModel>,
public IRequestHandlerImpl<THandler> {
public:
// public CRequestHandlerT members
CHttpResponse m_HttpResponse;
CHttpRequest m_HttpRequest;
ATLSRV_REQUESTTYPE m_dwRequestType;
AtlServerRequest* m_pRequestInfo;
CRequestHandlerT() ;
~CRequestHandlerT() ;
void ClearResponse() ;
// Where user initialization should take place
HTTP_CODE ValidateAndExchange();
// Where user Uninitialization should take place
HTTP_CODE Uninitialize(HTTP_CODE hcError);
// HandleRequest is called to perform default processing
// of HTTP requests. Users can override this function in
// their derived classes if they need to perform specific
// initialization prior to processing this request or
// want to change the way the request is processed.
HTTP_CODE HandleRequest(
AtlServerRequest *pRequestInfo,
IServiceProvider* /*pServiceProvider*/);
HTTP_CODE ServerTransferRequest(LPCSTR szRequest,
bool bContinueAfterTransfer=false,
WORD nCodePage = 0, CStencilState *pState = NULL);
...
}
The CRequestHandlerT class provides the
m_HttpRequest object as a way of accessing the request
data, and the m_HttpResponse object that is used to build
the response to go back to the client. The previous code block
shows some of the more useful methods of this class. Some, such as
ServerTransferRequest, are available for you to call from
your request handler. Others, such as ValidateAndExchange,
exist to be overridden in your derived class.
The actual processing of
the stencil file is handled via the TagReplacerType
template parameter, which defaults to ChtmlTagReplacer.
This class is itself a template:
template <class THandler, class StencilType=CHtmlStencil>
class CHtmlTagReplacer :
public ITagReplacerImpl<THandler>
{ ... }
There's also a second layer of templates here.
The CHtmlTagReplacer actually exists to manage the stencil
cache. For each .srf file, a stencil object is created the
first time. The .srf file is then parsed into a series of
StencilToken objects, which are stored in an array in the
stencil object. Rendering the HTML is done by walking the array and
rendering each token. That stencil object is then stored in the
cache for later use. This way the parsing is done only once.
By default, the type of stencil object created
is CHtmlStencil. This class knows about all the
replacement tags that can occur in .srf files. However, it
is a template parameter and, as such, can be overridden to add new
replacement tags. This is your opportunity to customize the stencil
replacement system: Create a new stencil class (which should derive
from CStencil) and override the parsing methods to add new
tags to the processing.
|