previous page
next page

An Example Request Handler

Let's see how this comes together. Here's an example .srf file that's part of a simple online forum[2] system, to provide a list of forums available:

[2] Thanks to Joel Spolsky of www.joelonsoftware.com for the original forums (written in classic ASP) that inspired this example, and for letting me stealer, borrowthe idea for the book. He discusses his design decisions about the forums at www.joelonsoftware.com/articles/buildingcommunities-withso.html (http://tinysells.com/56).

<html>
{{handler SimpleForums.dll/ForumList}}
<head>
    <title>Forums</title>
</head>
<body>
<h1>ATL Server Simple Forums</h1>
<p>There are {{NumForums}} forums on this system.</p>
{{while MoreForums}}
    <h2><a href="{{LinkToForum}}">{{ForumName}}</a></h2>
    <p>{{ForumDescription}}</p>
    <p><a href="{{LinkToEditForum}}">Edit Forum Settings</a></p>
    <br />
{{NextForum}}
{{endwhile}}
</body>
</html>

This file uses not only the {{handler}} directive, but also textual replacements and the {{while}} loop.

So, we need a forum list handler. The handler class looks like this:[3]

[3] The forum system uses ADO to access the database of forum information. Because this isn't a book about databases, I've omitted the database code from the chapter. The complete version is available with the sample downloads for this book.

class ForumListHandler :
  public CRequestHandlerT<ForumListHandler> {
public:
  ForumListHandler(void);
public:
  virtual ~ForumListHandler(void);

public:
BEGIN_REPLACEMENT_METHOD_MAP(ForumListHandler)
  REPLACEMENT_METHOD_ENTRY("NumForums", OnNumForums)
  REPLACEMENT_METHOD_ENTRY("MoreForums", OnMoreForums)
  REPLACEMENT_METHOD_ENTRY("NextForum", OnNextForum)
  REPLACEMENT_METHOD_ENTRY("ForumName", OnForumName)
  REPLACEMENT_METHOD_ENTRY("ForumDescription",
    OnForumDescription)
  REPLACEMENT_METHOD_ENTRY("LinkToForum", OnLinkToForum)
  REPLACEMENT_METHOD_ENTRY("LinkToEditForum",
    OnLinkToEditForum)
END_REPLACEMENT_METHOD_MAP()

  HTTP_CODE ValidateAndExchange();

private:

  HTTP_CODE OnNumForums( );
  HTTP_CODE OnMoreForums( );
  HTTP_CODE OnNextForum( );
  HTTP_CODE OnForumName( );
  HTTP_CODE OnLinkToForum( );
  HTTP_CODE OnLinkToEditForum( );
  HTTP_CODE OnForumDescription( );

private:

  ForumList m_forums;
  CComPtr< _Recordset > m_forumsRecordSet;
};

The action starts for this class in the ValidateAndExchange method, which is called at the start of processing after the m_HttpRequest variable has been created.

#define AS_HR(ex) { \
  HRESULT_hr = ex; if(FAILED(_hr)) { return HTTP_FAIL; } }
HTTP_CODE ForumListHandler::ValidateAndExchange() {
    // Set the content-type
    m_HttpResponse.SetContentType("text/html");

    AS_HR( m_forums.Open( ) );
    AS_HR( m_forums.ReadAllForums( &m_forumsRecordSet ) );

    return HTTP_SUCCESS;
}

The return value, HTTP_CODE, is used to signal what HTTP return code to send back to the client. If this function returns HTTP_SUCCESS, the processing continues. On the other hand, if something is wrong, you can return a different value (such as HTTP_FAIL) to abort the processing and send an HTTP failure code back to the browser.

The HTTP_CODE type is actually a typedef for a DWORD, and it packs multiple data items into those 32 bits (much like hrESULT does). The high 16 bits contain the HTTP status code that should be returned. The lower 16 bits specify a code to tell IIS what to do with the rest of the request. Take a look at MSDN for the set of predefined HTTP_CODE macros.

In this example, we use the data layer object in the m_forums variable to go out to our forums database and read the list of forums. Assuming that this worked,[4] we store the list (an ADO recordset) as a member variable.

[4] The AS_HR macro used here is not part of ATL Server. It simply checks the returned hrESULT and, if it fails, returns HTTP_FAIL.

The replacement functions come in two varieties: textual replacement and flow control. The OnForumName method is an example of the former. When the {{ForumName}} token is found in the SRF file, this code is run:

HTTP_CODE ForumListHandler::OnForumName( ) {
    CComBSTR name;
    AS_HR( m_forums.GetCurrentForumName( m_forumsRecordSet,
        &name ) );
    m_HttpResponse << CW2A( name );
    return HTTP_SUCCESS;
}

Here, the m_HttpResponse member is used like a C++ stream class to output the name of the current forum. The CW2A conversion class is used because our data layer is returning Unicode, but the SRF file defaults to 8-bit characters.

The flow-control tokens use the same replacement map but work very differently. Within the replacement method, the return value is the important thing:

HTTP_CODE ForumListHandler::OnMoreForums( ) {
    VARIANT_BOOL endOfRecordSet;
    AS_HR( m_forumsRecordSet->get_adoEOF( &endOfRecordSet ) );
    if( endOfRecordSet == VARIANT_TRUE ) {
        return HTTP_S_FALSE;
    }
    return HTTP_SUCCESS;
}

Here, we're checking to see if we have any more records in our recordset. If so, we return HTTP_SUCCESS. If not, we return HTTP_S_FALSE. Much like S_FALSE is the "Succeeded, but false" hrESULT, HTTP_S_FALSE signals the stencil processor that the Boolean expression being evaluated is false, but the processing completed. In this case, the false return value causes the while loop to exit.


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