Figure 3   HTTP Client Request Methods

Method

Action

GET

Retrieve data specified in the URL

HEAD

Return HTTP server response header informationonly

POST

Send information to the HTTP server for further action

PUT

Send information to the HTTP server for storage

DELETE

Delete the resource specified in the URL

LINK

Establish one or more link relationships between specified URLs

Figure 4   Some Common HTTP Response Codes

Code

Meaning

200

URL located, transmission follows

400

Unintelligible request

404

Requested URL not found

405

Server does not support requested method

500

Unknown server error

503

Server capacity reached

Figure 5   HTTP Response Code Groups

Group

Meaning

200-299

Success

300-399

Information

400-499

Request error

500-599

Server error

Figure 6   Sample MIME Data Types

Object Type

Subtype

Description

application

msword

Microsoft Word document

postscript

Postscript document

rtf

Rich Text Format document

zip

PKZIP compressed file

image

gif

CompuServe GIF format

jpeg

JPEG format

x-xbitmap

X Window bitmap format

message

RFC822

Mail message

text

html

HTML

plain

Unformatted text

video

mpeg

MotionPicturesExpertGroupformat

quicktime

Apple QuickTime format

x-msvideo

Microsoft Video format

avi

Microsoft AVI format

audio

basic

8-bit ISDN audio file

wav

Microsoft WAV format

Figure 7   Webster Code Highlights

CCLIENT.H

 /////////////////////////////////////////////////////////////////////////////
// CClient.h : interface of the CClient class
//
// This is a part of the Webster HTTP Server application
// Copyright 1996 Microsoft Systems Journal.
//
#ifndef __CCLIENT_H__
#define __CCLIENT_H__

#include "logger.h"     // COMLOGREC

class CWebDoc;

class CClient : public CSocket {
   DECLARE_DYNAMIC(CClient);
private:
   CClient(const CClient& rSrc); // no implementation
   void operator=(const CClient& rSrc);   // no implementation

public:
   CClient(CWebDoc* m_pDoc);
   virtual ~CClient();

   // These are used for providing the HTTP service
   CWebDoc*       m_pDoc ;       // we use this quite a bit
   BOOL           m_bDone ;      // set when we're done
   CString        m_PeerIP ;     // requestor's IP address
   UINT           m_Port ;       // port we're connected to
   struct hostent*   m_pHE ;     // for resolving client name
   CString        m_PeerName ;   // resolved client name
   COMLOGREC      m_LogRec ;     // Common Log Format log record

   // These are used for servicing the request
   char           *m_buf ;       // current receive buffer
   DWORD          m_irx ;        // index into receive buffer
   CStringList    m_cList ;      // list of request strings
   BOOL           m_bHTTP10 ;    // HTTP 1.0 format?
   CString        m_cURI ;       // requested file name
   CString        m_cLocalFNA ;  // constructed (local) file name
   enum METHOD_TYPE              // the HTML request methods
   {
      METHOD_UNSUPPORTED = 0,
      METHOD_GET,
      METHOD_POST,
      METHOD_HEAD,
      METHOD_PUT,
      METHOD_DELETE,
      METHOD_LINK,
      METHOD_UNLINK
   } m_nMethod ;

   struct _tagMethodTable {
      enum METHOD_TYPE  id;
      char              *key;
   };
   CSocketFile*   m_pFile;    // socket file for reply

   // service handling operations
   BOOL ProcessPendingRead() ;
   void ParseReq() ;
   void ProcessReq() ;
   BOOL SendReplyHeader ( CFile& cFile ) ;
   void SendTag() ;

   // service response
   BOOL SendFile ( CString& SendFNA, CString& BaseFNA,
                   BOOL bTagMsg = FALSE ) ;
   BOOL SendCGI ( CString& SendFNA, CString& BaseFNA ) ;

   // send-to-client
   BOOL SendRawData ( LPVOID lpszMessage, int count ) ;
   BOOL SendData ( LPCSTR lpszMessage ) ;
   BOOL SendData ( CString& cMessage ) ;
   BOOL SendData ( CFile& cFile ) ;

   // misc utilities
   BOOL ResolveClientName ( BOOL bUseDNS ) ;
   void SendCannedMsg ( int idErr, ... ) ;
   inline struct hostent* GetHostByAddr ( LPCSTR lpszIP ) {
      // translate dotted string format into integer
      int uPeer[4] ;
      sscanf ( lpszIP, "%d.%d.%d.%d",
             &uPeer[0], &uPeer[1], &uPeer[2], &uPeer[3] ) ;

      // move it into a char array for ::gethostbyaddr()
      char cPeer[4] ;
      cPeer[0] = uPeer[0] ;
      cPeer[1] = uPeer[1] ;
      cPeer[2] = uPeer[2] ;
      cPeer[3] = uPeer[3] ;
      return ( ::gethostbyaddr ( cPeer, 4, PF_INET ) ) ;
   }

protected:
   virtual void OnReceive(int nErrorCode);
#ifdef   _DEBUG
   virtual void OnSend(int nErrorCode);
   virtual void OnClose(int nErrorCode);
#endif
};

#endif // __CCLIENT_H__

CCLIENT.CPP

 /////////////////////////////////////////////////////////////////////////////
// CClient.cpp : implementation of the CClient class
//
// This is a part of the Webster HTTP Server application
// Copyright 1996 Microsoft Systems Journal.
//
#include "stdafx.h"
#include "cclient.h"
#include "webdoc.h"
#include "resource.h"   // IDS_ id's
#include "webster.h" // CWebApp
#include "mainfrm.h" // CMainFrame

// CClient Construction

CClient::CClient(CWebDoc* m_pParentDoc)
{
   m_pDoc = m_pParentDoc ; // cache this for lots of use later
   m_bDone = FALSE ;
   m_irx = 0 ;
   m_buf = NULL ;
   m_bHTTP10 = FALSE ;     // assume HTTP 0.9
   m_nMethod = METHOD_UNSUPPORTED ;
   time_t tNow ;
   time ( &tNow ) ;
   CTime cNow ( tNow ) ;
   m_LogRec.datetime = cNow ; // this is our birth date
   m_LogRec.client   = "" ;
   m_LogRec.inetd    = "-" ;
   m_LogRec.username = "-" ;
   m_LogRec.request  = "" ;
   m_LogRec.status   = 200 ;
   m_LogRec.bytes    = 0 ;
   m_pHE = NULL ;
}

CClient::~CClient()
{
   if ( m_buf ) {
      free ( m_buf ) ;
      m_buf = NULL ;
   }
}

// CSocket Implementation

IMPLEMENT_DYNAMIC(CClient, CSocket)

// CClient Overridable callbacks

void CClient::OnReceive(int nErrorCode)
{
   CSocket::OnReceive(nErrorCode);

   // If the read processing was successful, then we've done our job.
   // If not, the service request has not completed yet.
   // Caution: The bDone boolean is used elsewhere,
   //          don't try to tighten up this check.
   if ( ProcessPendingRead() )
   {
      AfxGetMainWnd()->PostMessage ( WM_NEW_CLIENT,
                              (WPARAM)0,
                              (LPARAM)this ) ;
      // send request off for processing
      ProcessReq() ; // process the request
      m_bDone = TRUE ;
      if ( theApp.m_bLogEnable )
      {
         m_pDoc->WriteLog ( m_LogRec ) ;
      }
      AfxGetMainWnd()->PostMessage ( WM_KILL_SOCKET,
                              (WPARAM)0,
                              (LPARAM)this ) ;
   }
}

#ifdef   _DEBUG
// This is called to notify a blocked sender that the socket is unblocked
// now. Not required for retail operation.
void CClient::OnSend(int nErrorCode)
{
   CSocket::OnSend(nErrorCode);

   if ( theApp.m_bDebugOutput ) {
      if ( m_cList.GetCount() == 0 ) {
         m_pDoc->DbgMessage ( "OnSend:: No request yet.\n" ) ;
         return ; // no request yet, keep reading
      }
      m_pDoc->DbgMessage ( "OnSend::Ready to send data.\n" ) ;
   }
}

// This is called when the client wants to abort the data download.
// Also not required for retail operation.
void CClient::OnClose(int nErrorCode)
{
   CSocket::OnClose(nErrorCode);
   m_pDoc->Message ( "OnClose::\n" ) ;
}
#endif   // _DEBUG

// This routine collects the request from the client
BOOL CClient::ProcessPendingRead()
{
// Not all clients can send a block at a time (e.g., telnet). What we're
// doing here is constructing a buffer until we get either a cr/lf pair, or
// until dwBytes to read is zero.
//
// Note that since we've been called from the OnReceive notification
// handler, then if dwBytes is zero, it must mean the client has closed the
// connection.

   DWORD dwBytes ;

   IOCtl ( FIONREAD, &dwBytes ) ;      // anything to get?
   if ( dwBytes == 0 )
      return ( TRUE ) ; // we must be done!

   // allocate, or extend, our buffer - leave room for '\0' at end
   if ( m_irx == 0 ) // first time thru - malloc
      m_buf = (char *)malloc ( dwBytes+1 ) ;
   else           // otherwise - realloc
      m_buf = (char *)realloc ( m_buf, m_irx+dwBytes+1 ) ;
   // (so, like _when_ is C++ gonna support resizeable memory?)

   // get this chunk from the client
   if ( Receive ( &m_buf[m_irx], dwBytes, 0 ) == SOCKET_ERROR ) {
      int err = GetLastError() ;
      if ( err == WSAECONNRESET ) { // remote has terminated (gracefully)
         m_pDoc->Message ( "WSAECONNERESET\n" ) ;
         return ( TRUE ) ; // must be done!
      }
   }
   m_irx += dwBytes ;   // this much was added to our accumulating buffer

   // This is our socket equivalent of gets()
   // If we return FALSE, it means we need more input
   if ( m_irx < 2 )   // enough to parse?
      return ( FALSE ) ;
   if ( memcmp ( &m_buf[m_irx-2], "\r\n", 2 ) != 0 )  // end of line?
      return ( FALSE ) ;

   // split this request up into the list of lines
   m_buf[m_irx] = '\0' ;   // Make this is an SZ string for parsing.
   char *pBOL = m_buf ;
   for ( char *pEOL = strpbrk ( pBOL, "\r" ) ;
         pEOL ;
         pEOL = strpbrk ( pBOL, "\r" ) ) {
      *pEOL = '\0' ; // make this chunk an SZ string
      m_cList.AddTail ( CString(pBOL,strlen(pBOL)) ) ; // add to list
      *pEOL++ ;      // skip '\0'
      *pEOL++ ;      // skip '\n'
      pBOL = pEOL ;  // point to next text
   }
   m_irx = 0 ;    // reset for next chunk from client
   free ( m_buf ) ;
   m_buf = NULL ;

   // are we in HTTP 1.0 mode yet?
   if ( ! m_bHTTP10 ) {
      if ( m_cList.GetHead().Find ( "HTTP/1.0" ) != -1 )
         m_bHTTP10 = TRUE ;   // we are now...
      else
         return ( TRUE ) ; // we must be done
   }

   // We are definitely in HTTP 1.0 mode now, so look for the terminating
   // empty line. Since we've already stripped off the cr/lf, the length
   // will be zero.
   return ( m_cList.GetTail().GetLength() == 0 ) ;
}  // ProcessPendingRead()

// This is a lookup table for translating the parsed method text string
// into a predefined token value. This token value will be used later
// to dispatch the request to an appropriate handler.
static struct CClient::_tagMethodTable MethodTable[] = {
   { CClient::METHOD_GET   , "GET"    },
   { CClient::METHOD_POST  , "POST"   },
   { CClient::METHOD_HEAD  , "HEAD"   },
   { CClient::METHOD_PUT   , "PUT"    },
   { CClient::METHOD_DELETE, "DELETE" },
   { CClient::METHOD_LINK  , "LINK"   },
   { CClient::METHOD_UNLINK, "UNLINK" },
} ;
static const int MethodTableLen = sizeof(MethodTable)/
                          sizeof(struct CClient::_tagMethodTable) ;

void CClient::ParseReq()
{
   CStringList cList ;  // list of parsed command tokens

   // save the request line for our log record
   if ( m_cList.IsEmpty() ) { // always check IsEmpty() first
      m_pDoc->DbgVMessage ( "Command list is empty!\nSending 400 error\n" ) ;
      SendCannedMsg ( 400 ) ;
      return ;
   }
   CString cReq = m_cList.GetHead() ;
   m_LogRec.request = cReq ;

   // parse the primary, and most important, request line

   // parse the request line into a list of tokens
   LPSTR tempmsg = new char[cReq.GetLength()+1] ;  // allow for EOS
   strcpy ( tempmsg, cReq ) ;
   char *pBOL = tempmsg ;
   for ( char *pEOL = strpbrk ( pBOL, " " ) ;
        pEOL ;
        pEOL = strpbrk ( pBOL, " " ) ) {
      *pEOL = '\0' ;
      CString tempToken ( pBOL, strlen(pBOL) ) ;
      *pEOL++ ;   // skip '\0'
      pBOL = pEOL ;
      cList.AddTail ( tempToken ) ;
   }
   // save whatever's left as the last token
   CString tempToken ( pBOL, strlen(pBOL) ) ;
   cList.AddTail ( tempToken ) ;
   delete tempmsg ;

   POSITION pos = cList.GetHeadPosition() ;  // prepare to scan the request

   // 1) parse the method
   if ( pos == NULL ) {
      m_pDoc->DbgVMessage ( "Null request method\nSending 400 error\n" ) ;
      SendCannedMsg ( 400 ) ;
      return ;    
   }
   m_cURI = cList.GetNext ( pos ) ; // pointing to METHOD now
   for ( int i = 0 ; i < MethodTableLen ; i++ ) {
      if ( m_cURI.CompareNoCase ( MethodTable[i].key ) == 0 ) {
         m_nMethod = MethodTable[i].id ;
         break ;
      }
   }
   // 2) parse the URI
   if ( pos == NULL ) {
      m_pDoc->DbgVMessage ( "Null request URI\nSending 400 error\n" ) ;
      SendCannedMsg ( 400 ) ;
      return ;    
   }
   m_cURI = cList.GetNext ( pos ) ; // pointing to ENTITY now
   m_pDoc->VMessage ( "   Data request: %s\n", m_cURI ) ;

   // replace UNIX '/' with MS '\'
   for ( i = 0 ; i < m_cURI.GetLength() ; i++ ) {
      if ( m_cURI[i] == '/' )
         m_cURI.SetAt ( i, '\\' ) ;
   }

   // add base path
   if ( m_cURI[0] != '\\' )
      m_cLocalFNA = theApp.m_HTMLPath + CString("\\") + m_cURI ;
   else
      m_cLocalFNA = theApp.m_HTMLPath + m_cURI ;

   // This is a real ugly little hack for MikeAh to use forms/GET
   // I just snarf the rest of the command line from the query
   // separator on...
   if ( ( i = m_cLocalFNA.Find ( '?' ) ) != -1 )
      m_cLocalFNA.GetBufferSetLength ( i ) ;

   // 3) parse the rest of the request lines
   if ( ! m_bHTTP10 )
      return ; // if HTTP 0.9, we're done!

   // parse the client's capabilities here...
   if ( theApp.m_bDebugOutput ) {
      POSITION pos = m_cList.GetHeadPosition() ;
      for ( int i = 0 ; i < m_cList.GetCount() ; i++ ) {
         // For now, we'll just have a boo at them. Being such a simple
         // server, let's not get too concerned with details.
         m_pDoc->DbgVMessage ( "   %d>%s\n", i+1, m_cList.GetNext ( pos ) ) ;
      }
   }
}  // ParseReq()

// dispatch service handler
void CClient::ProcessReq()
{
   // parse the request
   ParseReq() ;

   // can only handle GETs for now
   if ( m_nMethod != METHOD_GET ) {
      m_pDoc->VMessage ( "   Unknown method requested: %s\n", m_cURI ) ;
      SendCannedMsg ( 405, m_cURI ) ;
      return ;
   }

   // try to determine the send method based on the file type
   char *ext = strrchr ( m_cLocalFNA, '.' ) ;
   if (  m_cURI == "\\" )  {  // blind request?
      m_pDoc->DbgVMessage ( "   Blind request\n" ) ;
      if ( ! SendFile ( m_cLocalFNA, m_cURI ) )
         return ; // send failure!

   } else if ( ext ) {        // has an extension?
      *ext++ ; // point to start of file extension
      m_pDoc->DbgVMessage ( "   File extension: <%s>\n", ext ) ;
      if ( ! SendFile ( m_cLocalFNA, m_cURI ) )
         return ; // send failure!

   } else { // must be a CGI script specification
      m_pDoc->DbgVMessage ( "   CGI request: %s %s\n", m_cLocalFNA, m_cURI ) ;
      if ( ! SendCGI ( m_cLocalFNA, m_cURI ) ) {
         // ...insert CGI-implementation dependant actions here...
         return ;
      }
   }
   SendTag() ; // Done!!!
}  // ProcessReq()

// CClient Service Response Operations

// this is the built-in list of MIME types we automatically recognize
static struct _tagMIME_Table {
   char  *ext ;
   char  *type ;
} MIME_Table[] = {
   { ".gif" , "image/gif"  },
   { ".jpg" , "image/jpg"  },
   { ".htm" , "text/html"  },
   { ".html", "text/html"  },
   { ".txt" , "text/plain" }
} ;
static const int MIME_len = sizeof(MIME_Table)/sizeof(struct _tagMIME_Table);

BOOL CClient::SendReplyHeader ( CFile& cFile )
{
   if ( ! m_bHTTP10 )   // if HTTP 0.9, response header not used
      return ( TRUE ) ;

   // Response header components:
   // 1 - HTTP 1.0 response header
   // 2 - Server time
   // 3 - Server version
   // 4 - MIME version
   // 5 - MIME type
   // 6 - Last-modified time
   // 7 - Content length
   // 8 - HTTP 1.0 End-of-header

   CString  tmp ;
   if ( ! SendData ( "HTTP/1.0 200 OK\r\n" ) )
      return ( FALSE ) ;   // skate from here...

   CTime rTime = CTime::GetCurrentTime() ;
   tmp.Format ( "Date: %s\r\n",
            rTime.FormatGmt("%a, %d %b %Y %H:%M:%S %Z") ) ;
   SendData ( tmp ) ;
   SendData ( "Server: Webster/1.0\r\n" ) ;
   SendData ( "MIME-version: 1.0\r\n" ) ;

   char ext[5] ;
   _splitpath ( cFile.GetFileName(), NULL, NULL, NULL, ext ) ;
   tmp = ext ;
   for ( int i = 0 ; i < MIME_len ; i++ ) {
      if ( tmp.CompareNoCase ( MIME_Table[i].ext ) == 0 ) {
         SendData ( "Content-type: " ) ;
         SendData ( MIME_Table[i].type ) ;
         SendData ( "\r\n" ) ;
         break ;
      }
   }
   CFileStatus rStatus ;
   if ( cFile.GetStatus ( rStatus ) ) {
      tmp.Format ( "Last-modified: %s\r\n",
               rStatus.m_mtime.FormatGmt("%a, %d %b %Y %H:%M:%S %Z") ) ;
      SendData ( tmp ) ;
   }
   tmp.Format ( "Content-length: %d\r\n", cFile.GetLength() ) ;
   SendData ( tmp ) ;
   SendData ( "\r\n" ) ;   // end-of-header
   return ( TRUE ) ;
}  // SendReplyHeader()

void CClient::SendTag()
{
   // send our personalized message
   CString tagmsg ;
   BOOL ret = TRUE ;
   switch ( theApp.m_nTagType ) {
      case CWebApp::TAG_AUTO:
         tagmsg.LoadString ( IDS_TAGSTRING ) ;
         ret = SendData ( tagmsg ) ;
         m_pDoc->Message ( " Sent auto tag\n" ) ;
         break ;
      case CWebApp::TAG_FILE:
         tagmsg = theApp.m_HTMLPath + CString("\\") + theApp.m_HTMLTag ;
         ret = SendFile ( tagmsg, theApp.m_HTMLTag, TRUE ) ;
         break ;
      case CWebApp::TAG_NONE:
         m_pDoc->Message ( " No tag\n" ) ;
         break ;
   }
   tagmsg.LoadString ( IDS_TAGCOMMENT ) ;
   if ( ret )
      SendData ( tagmsg ) ;
}  // SendTag()

// URI file handler
BOOL CClient::SendFile(CString& m_cLocalFNA, CString& BaseFNA, BOOL bTagMsg)
{
   CFile cFile ;
   BOOL FoundIt ;

   // if our request isn't empty, then try to open the file specified
   if ( m_cLocalFNA != theApp.m_HTMLPath + CString("\\") ) {
      m_pDoc->DbgVMessage ( "Attempting to open: %s\n", m_cLocalFNA ) ;
      FoundIt = cFile.Open ( m_cLocalFNA,
                             CFile::modeRead | CFile::typeBinary ) ;

   } else { // otherwise, it was a blind access. send the default file
      m_pDoc->DbgMessage ( "Blank request, trying Default - " ) ;
      m_cLocalFNA = theApp.m_HTMLPath
                  + CString("\\")
                  + theApp.m_HTMLDefault ;
      FoundIt = cFile.Open ( m_cLocalFNA,
                        CFile::modeRead | CFile::typeBinary ) ;
      // if our configuration settings are hosed, use emergency plan 'B'
      if ( ! FoundIt ) {
         m_pDoc->DbgVMessage ( "Couldn't find: %s\nTrying Bogus - ",
                           m_cLocalFNA ) ;
         m_cLocalFNA = theApp.m_HTMLPath + CString("\\")
                     + theApp.m_HTMLBogus ;
         FoundIt = cFile.Open ( m_cLocalFNA,
                                CFile::modeRead | CFile::typeBinary ) ;
      }
   }
   if ( ! FoundIt ) {
      m_pDoc->DbgVMessage ( "Couldn't find: %s\nSending 404 error\n",
                        m_cLocalFNA ) ;
      SendCannedMsg ( 404, BaseFNA ) ;
      return ( TRUE ) ;;
   }
   m_pDoc->DbgMessage ( "\n" ) ; // make debug msg readable

   // we found a file, so send it already...
   m_pDoc->VMessage ( "   Sending: %s\n", m_cLocalFNA ) ;
   BOOL ret = TRUE ;
   if ( ! bTagMsg )  // if tag message, skip the response header
      ret = SendReplyHeader ( cFile ) ;
   if ( ret )
      ret = SendData ( cFile ) ;
   cFile.Close() ;
   return ( ret ) ;
}  // SendFile()

// CGI handler

BOOL CClient::SendCGI ( CString& m_cLocalFNA, CString& BaseFNA )
{
   // This is a hook for future development. Just send 404 error for now.
   SendCannedMsg ( 404, BaseFNA ) ;
   return ( TRUE ) ;
}

// Data transmission operations

// this is for sending our own info and messages to the client
BOOL CClient::SendRawData ( LPVOID lpMessage, int count )
{
   // Use a CSocketFile for pumping the message out
   CSocketFile sockFile ( this, FALSE ) ;
   TRY {
      sockFile.Write ( lpMessage, count ) ;
   } CATCH(CFileException, e) {
      m_pDoc->VMessage ( "Failed to write raw to client socket!\n" ) ;
      m_pDoc->VMessage ( "   >>> %s\n", theApp.MapErrMsg(GetLastError()) ) ;
      return ( FALSE ) ;;
   } END_TRY
   sockFile.Flush() ;
   return ( TRUE ) ;
}

// this is for sending a CString message to the client
BOOL CClient::SendData ( CString& cMessage )
{
   return ( SendData ( (LPCTSTR)cMessage ) ) ;
}

// this is for sending a LPSTR message to the client
BOOL CClient::SendData ( LPCSTR lpszMessage )
{
   m_pDoc->DbgVMessage ( ">>>Sending client message: %s\n", lpszMessage ) ;
   return ( SendRawData ( (LPVOID)lpszMessage, strlen(lpszMessage) ) ) ;
}

// this is for sending file data to the client
#define  BUF_SIZE 4096  // same as default for CSocket as CArchive

BOOL CClient::SendData ( CFile& cFile )
{
   char  buf[BUF_SIZE] ;
   int      nBytes ;

   // Use a CSocketFile for pumping the data out
   CSocketFile sockFile ( this, FALSE ) ;
   while ( ( nBytes = cFile.Read ( buf, BUF_SIZE ) ) > 0 ) {
      TRY {
         sockFile.Write ( (LPVOID)buf, nBytes ) ;
         m_LogRec.bytes += nBytes ;
      } CATCH(CFileException, e) {
         m_pDoc->VMessage ( "Failed to write data to client socket!\n%s\n",
                              theApp.MapErrMsg(GetLastError()) ) ;
         return ( FALSE ) ;
      } END_TRY
   }
   sockFile.Flush() ;
   return ( TRUE ) ;
}

// CClient Utility Operations

BOOL CClient::ResolveClientName ( BOOL bUseDNS )
{
   if ( ! GetPeerName ( m_PeerIP, m_Port ) ) {
      m_pDoc->Message ( " Can't get client name\n" ) ;
      return ( FALSE ) ;
   }
   m_LogRec.client = m_PeerIP ;

   if ( bUseDNS ) {
      if ( m_PeerIP == "127.0.0.1" ) { // hey, it's me!!!
         m_LogRec.client = "Local loopback" ;
         m_pDoc->VMessage ( " Local loopback (%s)\n", m_PeerIP ) ;
      } else {
         if ( m_pHE = GetHostByAddr ( (LPCSTR)m_PeerIP ) ) {
            m_LogRec.client = m_pHE->h_name ;
            m_pDoc->VMessage ( " %s (%s)\n", m_pHE->h_name, m_PeerIP ) ;
         } else {
            int err = WSAGetLastError() ;
            m_pDoc->VMessage ( " Unable to get host name: %s. Err: %d\n",
                                 m_PeerIP, err ) ;
            return ( FALSE ) ;
         }
      }
   } else {
      m_pDoc->VMessage ( " %s\n", m_PeerIP ) ;
   }
   return ( TRUE ) ;
}

// table of canned messages that we can handle
static struct _tagMsgTable {
   int   id ;
   int   idStr ;
} MsgTable[] = {
   { 400, IDS_400_MESSAGE },
   { 404, IDS_404_MESSAGE },
   { 405, IDS_405_MESSAGE },
   { 503, IDS_503_MESSAGE }
} ;
static const int MsgTableSize = sizeof(MsgTable)/sizeof(struct _tagMsgTable);

void CClient::SendCannedMsg ( int idErr, ... )
{
   BOOL bGotIt = FALSE ;
   for ( int i = 0 ; i < MsgTableSize ; i++ ) {
      if ( MsgTable[i].id == idErr ) {
         bGotIt = TRUE ;
         break ;
      }
   }
   CString fmt ;
   char  buf[200] ;
   if ( ! bGotIt ) { // idErr is a bogus code!
      fmt.LoadString ( IDS_500_MESSAGE ) ;
      wsprintf ( buf, fmt, idErr ) ;
   } else {
      fmt.LoadString ( MsgTable[i].idStr ) ;
      va_list  ptr ;
      va_start ( ptr, idErr ) ;
      wvsprintf ( buf, fmt, ptr ) ; 
   }
   Send ( buf, strlen(buf), 0 ) ;

   // write log record
   m_LogRec.status = idErr ;
   if ( theApp.m_bLogEnable )
      m_pDoc->WriteLog ( m_LogRec ) ;

   // write status message
   m_pDoc->DbgVMessage ( "   Sent %03d status message to client\n", idErr ) ;
}

CLISTEN.H

 /////////////////////////////////////////////////////////////////////////////
// CListen.h : interface of the CListen class
//
// This is a part of the Webster HTTP Server application
// Copyright 1996 Microsoft Systems Journal.
//
#ifndef __CLISTEN_H__
#define __CLISTEN_H__

class CWebDoc; // fwd ref

class CListen : public CSocket {
   DECLARE_DYNAMIC(CListen);
private:
   CListen(const CListen& rSrc);         // no implementation
   void operator=(const CListen& rSrc);  // no implementation

public:
   CListen(CWebDoc* pDoc);
   virtual ~CListen();
   CWebDoc* m_pDoc;

protected:
   virtual void OnAccept(int nErrorCode);
};
#endif // __CLISTEN_H__

CLISTEN.CPP

 /////////////////////////////////////////////////////////////////////////////
// CListen.cpp : implementation of the CListener class
//
// This is a part of the Webster HTTP Server application
// Copyright 1996 Microsoft Systems Journal.
//
#include "stdafx.h"
#include "clisten.h"
#include "webdoc.h"

CListen::CListen(CWebDoc* pDoc)
{
   m_pDoc = pDoc ;   // save doc pointer for later use
}

void CListen::OnAccept(int nErrorCode)
{
   CSocket::OnAccept(nErrorCode);
   m_pDoc->OnAccept() ; // process this in the context of the document
}

CListen::~CListen()
{
}

IMPLEMENT_DYNAMIC(CListen, CSocket)

WEBDOC.H

 /////////////////////////////////////////////////////////////////////////////
// WebDoc.h : interface of the CWebDoc class
//
// This is a part of the Webster HTTP Server application
// Copyright 1996 Microsoft Systems Journal.
//
class CListen ;   // forward reference
class CClient ;   //...ditto...

#include "logger.h"     // ofstream

class CWebDoc : public CDocument {
public:
   CStringList m_strList ;    // Status text buffer
   CString     m_strInfo ;    // Current text construction buffer
   int         m_nLines ;     // # lines in the status text buffer
   int         m_nMaxLines ;  // size of status text buffer
private:
   CListen* m_pSocket ;       // doc's one and only listening socket
   CPtrList m_listConnects ;  // list of active connections
   ofstream m_fileLog ;       // log stream

public:
   virtual ~CWebDoc();
   // Server
   void OnAccept() ;
   void CheckIdleConnects() ;
   void KillSocket ( CClient* pSocket ) ;
   // Logging
   void OpenLogfile() ;
   void WriteLog ( COMLOGREC& LogRec ) ;
   // Status
   void Message ( LPCTSTR lpszMessage ) ;
   void Message ( CString cMessage ) ;
   void VMessage ( LPCTSTR lpszFormat, ... ) ;
   void DbgMessage ( LPCTSTR lpszMessage ) ;
   void DbgMessage ( CString cMessage ) ;
   void DbgVMessage ( LPCTSTR lpszFormat, ... ) ;
   BOOL ActiveClients() { return ( ! m_listConnects.IsEmpty() ) ; }

   //{{AFX_VIRTUAL(CWebDoc)
   public:
   virtual BOOL OnNewDocument();
   virtual void Serialize(CArchive& ar);
   virtual void OnCloseDocument();
   virtual void SetTitle(LPCTSTR lpszTitle);
   //}}AFX_VIRTUAL

protected:
   CWebDoc();
   DECLARE_DYNCREATE(CWebDoc)
   //{{AFX_MSG(CWebDoc)
   afx_msg void OnClearView();
   //}}AFX_MSG
   DECLARE_MESSAGE_MAP()
};

WEBDOC.CPP

 /////////////////////////////////////////////////////////////////////////////
// WebDoc.cpp : implementation of the CWebDoc class
//
// This is a part of the Webster HTTP Server application
// Copyright 1996 Microsoft Systems Journal.
//
#include "stdafx.h"
#include "Webster.h"
#include "WebDoc.h"
#include "WebView.h"
#include "clisten.h"
#include "cclient.h"

#if _MFC_VER < 0x0400
// for CSocket "dead socket" race problem in MFC 3.0
#include "afxpriv.h" // WM_SOCKET_xxx
#endif

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

IMPLEMENT_DYNCREATE(CWebDoc, CDocument)

BEGIN_MESSAGE_MAP(CWebDoc, CDocument)
   //{{AFX_MSG_MAP(CWebDoc)
   ON_COMMAND(IDM_CLEAR_VIEW, OnClearView)
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

CWebDoc::CWebDoc()
{
   m_nLines = 0 ;
   m_nMaxLines = 100 ;
   m_strInfo.Empty() ;  // make sure we start off clean
}

CWebDoc::~CWebDoc()
{
}

BOOL CWebDoc::OnNewDocument()
{
   if (!CDocument::OnNewDocument())
      return FALSE;

   CString msg ;  // we use this a lot for MessageBox() messages
   GetFocus() ;   // For MessageBox() - frame window not init'ed yet

   // open log file
   OpenLogfile() ;

   // Create our one and only listener socket.
   // OnCloseDocument() takes care of deleting m_pSocket.
   m_pSocket = new CListen ( this ) ;
   if ( ! m_pSocket ) {
      AfxMessageBox ( "Unable to allocate memory for listener socket!" ) ;
      return ( FALSE ) ;
   }

   if ( ! m_pSocket->Create ( theApp.m_wwwPort ) ) {
      DWORD dwErr = m_pSocket->GetLastError() ;
      switch ( dwErr ) {
         case WSAEADDRINUSE:  // example of expected error handler
            AfxMessageBox ( "The WWW port is already in use!" ) ;
            break ;
         default:             // example of generic error handler
            msg.Format ( "Listener socket Create failed: %s\n",
                       theApp.MapErrMsg(dwErr) ) ;
            AfxMessageBox ( msg ) ;
      }
      return ( FALSE ) ;
   }

   // start listening for requests and set running state
   BOOL ret = m_pSocket->Listen() ;
   if ( ret ) {
      theApp.m_State = CWebApp::ST_WAITING ;
      msg.Format ( "Port: %d", theApp.m_wwwPort ) ;
      SetTitle ( msg ) ;
   } else {
      DWORD dwErr = m_pSocket->GetLastError() ;
      msg.Format ( "Listener socket Listen failed: %s\n",
                theApp.MapErrMsg(dwErr) ) ;
      AfxMessageBox ( msg ) ;
   }
   return ( ret ) ;
}

void CWebDoc::Serialize(CArchive& ar)
{
}

// Server handlers
//
void CWebDoc::OnAccept()
{
#if _MFC_VER < 0x0400
   // In order to fix the 'dead socket' race problem on Win95, we need to
   // make sure that all sockets that have been closed are indeed dead
   // before requesting a new one. This prevents reallocating a socket that
   // hasn't fully run down yet.
   // This is a feature of MFC prior to 4.0 and is no longer necessary
   // in subsequent versions.
   MSG msg ;
   while ( ::PeekMessage ( &msg, NULL,
                     WM_SOCKET_NOTIFY, WM_SOCKET_DEAD,
                     PM_REMOVE ) )  {
      ::DispatchMessage ( &msg ) ;
   }
#endif
   time_t tNow ;  // add time tag for MikeAh
   time( &tNow ) ;
   CTime cNow ( tNow ) ;
   Message ( cNow.Format ( "%m/%d/%y %H:%M:%S" ) ) ;
   Message ( " - Connection request:" ) ;

   // create a client object
   CClient *pClient = new CClient ( this ) ;
   if ( pClient == NULL ) {
      Message ( ">> Unable to create client socket! <<\n" ) ;
      return ;
   }

   if ( ! m_pSocket->Accept ( *pClient ) ) {
      Message ( ">> Unable to accept client connecton! <<\n" ) ;
      delete pClient ;
      return ;
   }
   pClient->ResolveClientName ( theApp.m_bResolveClientname ) ;

   // have we hit our resource limit?
   if ( m_listConnects.GetCount() >= (int)theApp.m_nMaxConnects ) {
      // yes, send failure msg to client
      pClient->SendCannedMsg ( 503 ) ;
      delete pClient ;
      Message ( "  Connection rejected - MaxConnects\n" ) ;
      return ;
   }
   Message ( "  Connection accepted!!!\n" ) ;

   // add this client to our list
   m_listConnects.AddTail ( pClient ) ;

   // Service Agent has the 'tater now...
}  // OnAccept()

// This routine is called periodically from the MainFrame sanity timer
// handler. We're checking to see if any clients are loitering and, if so,
// clobber them.
void CWebDoc::CheckIdleConnects()
{
   // compute the age threshold
   time_t tNow ;
   time( &tNow ) ;
   CTime cNow ( tNow ) ;
   CTimeSpan cTimeOut ( 0, 0, 0, theApp.m_nTimeOut ) ;
   cNow -= cTimeOut ;   // anyone created before this time will get zapped

   DbgMessage ( "--- Checking for idle connections ---\n" ) ;
   for ( POSITION pos = m_listConnects.GetHeadPosition() ; pos != NULL ; ) {
      CClient* pClient = (CClient*)m_listConnects.GetNext ( pos ) ;
      // anyone lanquishing in the list?
      if ( pClient->m_bDone ) {
         KillSocket ( pClient ) ;
      }
      // anyone timed out?
      else if ( pClient->m_LogRec.datetime < cNow ) {
         char msg[80] ;
         wsprintf(msg,">>> Idle timeout on client: %s\n",pClient->m_PeerIP);
         Message ( msg ) ;
         KillSocket ( pClient ) ;
      }
   }
   // flush the log file buffer, while we're at it
   if ( theApp.m_bLogEnable && m_fileLog.is_open() )
      m_fileLog.flush() ;
}  // CheckIdleConnects()

// This routine is called from the MainFrame when a client has notified the
// aforementioned that it is done. Since the document owns the client
// objects, the document is responsible for cleaning up after it.
void CWebDoc::KillSocket ( CClient* pSocket )
{
   BOOL bFoundIt = FALSE ;
   // remove this client from the connection list
   for ( POSITION pos = m_listConnects.GetHeadPosition() ; pos != NULL ; ) {
      POSITION temp = pos ;
      CClient* pSock = (CClient*)m_listConnects.GetNext ( pos ) ;
      if ( pSock == pSocket ) {
         bFoundIt = TRUE ;
         m_listConnects.RemoveAt ( temp ) ;
//(dec)...debug...
// looking for cause of accvio when client cancels transfer
// AsyncSelect causes accvio after Send has failed
         if ( pSocket->AsyncSelect(0) == 0 )
            DWORD err = GetLastError() ;
         pSocket->Close() ;   //...debug...
//(dec)...end of debug...
         delete pSocket ;  // destructor calls Close()
         pSocket = NULL ;  // make sure its no longer accessible
         Message ( "  Connection closed.\n" ) ;
         break ;
      }
   }
   if ( ! bFoundIt ) {
      DbgMessage(">> Uh oh! - Might have a sync problem.\n" ) ;
      DbgMessage(">> Couldn't find delete-pending socket in client list.\n");
   }
}  // KillSocket()


// Logging handlers
void CWebDoc::OpenLogfile()
{
   // we may have just turned logging off, so check this before m_bLogEnable
   if ( m_fileLog.is_open() )
      m_fileLog.close() ;
   if ( ! theApp.m_bLogEnable )
      return ;

   // now try to open/create the file
   m_fileLog.open ( theApp.m_LogPath, ios::ate, filebuf::sh_read ) ;
}

void CWebDoc::WriteLog ( COMLOGREC& LogRec )
{
   if ( theApp.m_bLogEnable && m_fileLog.is_open() ) {
      m_fileLog << LogRec.client << " "
                << LogRec.inetd << " "
                << LogRec.username << " ["
                << LogRec.datetime.Format("%d/%b/%Y %H:%M:%S") << "] \""
                << LogRec.request << "\" "
                << LogRec.status << " "
                << LogRec.bytes << "\n" ;
   }
}

// Status message handlers
void CWebDoc::Message ( LPCTSTR lpszMessage )
{
   if ( ! theApp.m_bShowStatus )
      return ;
   Message ( CString(lpszMessage) ) ;
}

void CWebDoc::Message ( CString cStr )
{
   if ( ! theApp.m_bShowStatus )
      return ;
   if ( cStr.GetLength() == 0 )
      return ;
   m_strInfo += cStr ;
   // This chunk of code unpacks the message string into individual lines.
   // The View window code is much simpler this way.
   BOOL bUpdate = FALSE ;
   int newline ;
   while ( ( newline = m_strInfo.Find ( "\n" ) ) >= 0 ) {
      CString cTemp = m_strInfo.Left ( newline ) ;
      m_strInfo = m_strInfo.Right ( (m_strInfo.GetLength()-newline) - 1 ) ;

      // purge excess messages
      while ( m_strList.GetCount() >= m_nMaxLines )
         m_strList.RemoveHead() ;

      m_strList.AddTail ( cTemp ) ;
      bUpdate = TRUE ;
   }
   if ( bUpdate ) // update views if any lines were added
      UpdateAllViews ( NULL ) ;
}

void CWebDoc::DbgMessage ( LPCTSTR lpszMessage )
{
   if ( theApp.m_bDebugOutput )
      Message ( lpszMessage ) ;
}

void CWebDoc::DbgMessage ( CString cStr )
{
   if ( theApp.m_bDebugOutput )
      Message ( cStr ) ;
}

// We could use the var_arg form of these functions exclusively,
// but it's a little more efficient to only use these when we
// really need them.
void CWebDoc::VMessage ( LPCTSTR lpszFormat, ... )
{
   va_list  ptr ;
   char  buf[200] ;
   va_start ( ptr, lpszFormat ) ;
   wvsprintf ( buf, lpszFormat, ptr ) ; 
   Message ( buf ) ;
}

void CWebDoc::DbgVMessage ( LPCTSTR lpszFormat, ... )
{
   va_list  ptr ;
   char  buf[200] ;
   va_start ( ptr, lpszFormat ) ;
   wvsprintf ( buf, lpszFormat, ptr ) ; 
   DbgMessage ( buf ) ;
}

// Class-Private handlers
void CWebDoc::SetTitle(LPCTSTR lpszTitle) 
{
   CString cTitle ;
   cTitle.Format ( "Listening on port: %d", theApp.m_wwwPort ) ;
   CDocument::SetTitle(cTitle);
}

// CWebDoc commands
void CWebDoc::OnCloseDocument() 
{
   // clobber everyone still connected
   for ( POSITION pos = m_listConnects.GetHeadPosition() ; pos != NULL ; ) {
      CClient* pClient = (CClient*)m_listConnects.GetNext ( pos ) ;
      KillSocket ( pClient ) ;
   }
   delete m_pSocket ;   // release the listening socket

   // tidy up the log file
   if ( theApp.m_bLogEnable && m_fileLog.is_open() )
      m_fileLog.close() ;
   CDocument::OnCloseDocument();
}

void CWebDoc::OnClearView() 
{
   m_strList.RemoveAll() ;
   UpdateAllViews ( NULL, 0L, 0 ) ;
}

Figure 15   MFC Socket Classes

CObject Classes

CAsyncSocket

Asynchronous socket class thatencapsulates WINSOCK. Base class for all socket classes

CSocket

Synchronous easy-to-use socket class provides blocking to work with CArchive and CSocketFile.

CFile Class

CSocketFile

CFile-derived clas that works with CSocket and Carchive for streaming objects to/from sockets.