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. |