previous page
next page

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.

Figure 14.1. Some tasty beverages to sing about


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.


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)