Simple HTTP Server

The following is an example of GUF-based networking code. This small (125-line) example responds to HTTP requests with a static web page.

This demo program has been compiled and run. It was tested against IE 5.5, Konqueror, Mozilla, Netscape 4.7x, and Lynx, and found to work in all cases. It was tested in situations involving multiple simultaneous connections and also found to work then.

Note that this code will not compile with any current version of GUF. I intend to write a new version of this code which works with the new versions of GUF when I get a chance.


File GNetTest.h:
/* GUF - the Grand Unified Framework
 * Copyright (C) 2000 Kenton Varda
 * <http://www.gauge3d.org/guf>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 */

// ===================================================================

#ifndef guf_GNetTest_h__INCLUDED
#define guf_GNetTest_h__INCLUDED

// includes ==========================================================
#include <guf/process/GEventReceiver.h>
#include <guf/network/GListener.h>
#include <guf/network/GNetStream.h>
#include <map>

// begin namespace ===================================================
namespace guf {

// class definition ==================================================
class GNetTest: GUF_IMPLEMENTS process::GEventReceiver
{
public:
   GNetTest();
   ~GNetTest();

   void Run(GString address);  //run the server on the given port

   //Implementation of GEventReceiver::ReceiveEvent(), called when an
   // event trigger owned by this object is fired.
   void ReceiveEvent(process::GEventTriggerPtr trigger, GObjectPtr event, GTime when, GExceptionPtr error) GUF_NO_THROW;

   //main function (with GufBuild, this function does not need to be global)
   static int Main(int argc, char* argv[]);

private:
   GBufferPtr mData;  //buffer storing our static web page
   process::GEventTriggerPtr mAcceptTrigger; //this trigger is fired when someone connects to the server
   network::GListenerPtr mListener; //this thingy listens for connections

   //this struct contains all the info we need about a particular connection.
   struct Connection
   {
      network::GNetStreamPtr mStream;           //the actual tcp data stream
      process::GEventTriggerPtr mReadTrigger;   //this is fired when a read operation completes
      process::GEventTriggerPtr mCloseTrigger;  //fired when we want to close the connection
      int mState;  //counts newline characters -- when we get two in a row, it's time to respond.
   };
   typedef GSmartPointer<Connection > ConnectionPtr;

   //Since ReceiveEvent() is only given a pointer to the trigger that was fired, we use
   //this map to find out which connection the trigger belongs to.
   map<process::GEventTriggerPtr, ConnectionPtr > mConnectionMap;
};
typedef GSmartPointer<GNetTest> GNetTestPtr;

} //namespace guf

#endif //included

File GNetTest.cpp:
/* GUF - the Grand Unified Framework
 * Copyright (C) 2000 Kenton Varda
 * <http://www.gauge3d.org/guf>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 */

// ===================================================================

// includes ==========================================================
#include "GNetTest.h"
#include <guf/process/GEventQueue.h>
#include <guf/network/GInternetProtocol.h>
#include <string.h>
#include <iostream>

// begin namespace ===================================================
namespace guf {

//In the future, GUF will have a replacement for cout.  For now, though, we
//have to define an operator for printing GString's.
static ostream& operator<<(ostream& os, GString str)
{
   char buffer[str.GetLength() + 1];
   str.CopyToBuffer(buffer, 0, str.GetLength(), true);
   os << buffer;
}

using namespace network;
using namespace process;

namespace  //anonymous (file-scope) namespace
{
   //this here is our static HTTP response, complete with HTTP/1.1 header and HTML file.
   //The server basically just thows this whole thing at anyone who connects to it.
   //This is probably violating some HTTP rules or something, but every browser I have
   //tested it with (IE 5.5, Konqueror, Mozilla, Netscape 4.7x, and Lynx) worked.
   const char OUTPUT_DATA[] =
   "HTTP/1.1 200 OK\n"
   "Server: GNetTest\n"
   "Accept-Ranges: bytes\n"
   "Content-Length: 391\n"
   "Content-Type: text/html\n"
   "\n"
   "<html>"
   " <head>"
   "  <title>GUF NetTest Server</title>"
   " <body>"
   "  <p>Hello, you've reached the GUF network architecture test server.  This is not a"
   "     real web server.  It only responds with this one page, no matter whan you ask"
   "     for.  But it does so using fully asynchronous I/O, which means it can handle"
   "     thousands of connections using only a single thread.  Ain't that cool?"
   " </body>"
   "</html>";
} //namespace

// class member functions ============================================
GNetTest::GNetTest()
   : mAcceptTrigger(GEventTrigger::New(GUF_THIS_PTR)) //Make a new trigger and set its owner to this.
{
   //create the data buffer and copy our canned response into it.
   mData = GBuffer::New(sizeof(OUTPUT_DATA));
   memcpy(mData->GetData(), OUTPUT_DATA, mData->GetSize());
}

GNetTest::~GNetTest()
{
   //No memory leaks here.  GUF cleans up for you.
}

void GNetTest::Run(GString address) //called from Main()
{
   GProtocolPtr inet = GInternetProtocol::New();  //get the internet protocol manager.
   mListener = inet->NewListener(inet->MakeAddress(address)); //create a listener on the given address.
   mListener->TriggerOnAccept(mAcceptTrigger);  //fire mAcceptTrigger when someone connects.

   GEventQueue::JoinEventLoop(); //wait for events and handle them
}

void GNetTest::ReceiveEvent(GEventTriggerPtr trigger, GObjectPtr event, GTime when, GExceptionPtr error) GUF_NO_THROW
{
   cout << "GNetTest: Got an event." << endl;

   if(error != null)
   {
      //we could check which trigger fired this event to find out what failed,
      //but that is probably explain in the description anyway, so why bother?
      cout << "Got an error event:" << endl;
      cout << error->GetDescription() << endl;
   }
   else if(trigger == mAcceptTrigger) //was mAcceptTrigger the one fired?
   {
      //The listener sends the new connection stream as the event object, so
      //we cast the event to a GNetStreamPtr.  This should never fail, but we
      //use an if() just in case.
      if(GNetStreamPtr stream = event.DynamicCast<GNetStreamPtr >())
      {
         cout << "GNetTest: Accepted a connection." << endl;

         //note: struct Connection is declared in GNetTest.h.
         ConnectionPtr connection = GUF_NEW_PTR Connection;
         connection->mStream = stream;
         connection->mReadTrigger = GEventTrigger::New(GUF_THIS_PTR);  //create trigger owned by this
         connection->mCloseTrigger = GEventTrigger::New(GUF_THIS_PTR); //create trigger owned by this
         connection->mState = 0; //no newlines counted

         //register triggers...
         //We want to simply close the connection if it breaks.
         connection->mStream->TriggerOnBrokenOutput(connection->mCloseTrigger);
         connection->mStream->TriggerOnBrokenInput(connection->mCloseTrigger);

         //Queue a read operation on the connection.  connection->mReadTrigger will be fired when
         //the read completes, unless it completes immediately, in which case the read data is
         //returned.
         GBufferPtr buffer = connection->mStream->Read(connection->mReadTrigger);
         if(buffer)
         {
            //read completed immediately.  However, to make things simpler, we will fire
            //the trigger ourselves and pretend that the read didn't complete immediately.
            connection->mReadTrigger->Fire(buffer);
         }

         //add the connection to the map.
         mConnectionMap[connection->mReadTrigger] = connection;
         mConnectionMap[connection->mCloseTrigger] = connection;
      }
   }
   else
   {
      map<GEventTriggerPtr, ConnectionPtr >::iterator i;

      //search the connection map for the trigger
      i = mConnectionMap.find(trigger);

      if(i != mConnectionMap.end())
      {
         //found it...
         ConnectionPtr connection = i->second;

         //check which event fired...
         if(trigger == connection->mReadTrigger)
         {
            //A read operation completed.  Normally, the buffer containing the read
            //data is sent as the event object, so we cast the event to a buffer.  This
            //cast should never fail, but we use an if() to be safe.
            if(GBufferPtr buffer = event.DynamicCast<GBufferPtr >())
            {
               cout << "GNetTest: Read from a connection:" << endl;
               cout.write(buffer->GetData(), buffer->GetSize()); //print out the HTTP header

               //HTTP clients tell the server when they are done sending the HTTP request by
               //sending two consecutive newlines.  We scan the buffer here for this occurrance
               //by counting the number of consecutive newlines in connection->mState.
               char* data = reinterpret_cast<char* >(buffer->GetData());
               for(int j = 0; j < buffer->GetSize(); j++)
               {
                  if(data[j] == '\n')
                  {
                     //read a newline
                     ++connection->mState;

                     if(connection->mState >= 2)
                     {
                        //got second newline.  Time to send the web page!
                        cout << "Responding to connection" << endl;

                        //Queue a write operation.  This is just like queuing a read.
                        //The function returns true if the operation completed immediately.
                        //We want the connection to close as soon as we're done writing, so
                        //we'll have Write trigger the close trigger when it is done.
                        if(connection->mStream->Write(mData, connection->mCloseTrigger))
                        {
                           //write completed immediately, so fire the trigger manually.
                           //(again, we are doing this just because it is easier to write
                           //and easier to understand.  Normally, you would handle the write
                           //directly here without using events at all.)
                           connection->mCloseTrigger->Fire(null);
                        }
                        return;  //we're all done with this connection now
                     }
                  }
                  else if(data[j] != '\r')   //ignore '\r' incase client is using MS-DOS-style text
                     connection->mState = 0;  //got something other than a newline, so reset count
               }

               //If we get here, we haven't read the whole header yet, so we start another read
               //like before.
               buffer = connection->mStream->Read(connection->mReadTrigger);
               if(buffer)
               {
                  //read completed immediately, so fire the trigger manually, like before.
                  connection->mReadTrigger->Fire(buffer);
               }
            }
         }
         else if(trigger == connection->mCloseTrigger)  //was the close trigger fired?
         {
            //connection->mCloseTrigger was fired, indicating that we want to close the
            //connection.

            //erase all entries from map.
            mConnectionMap.erase(connection->mReadTrigger);
            mConnectionMap.erase(connection->mCloseTrigger);

            //GUF smart pointers will clean up after us.  They will delete the
            //network stream, which will close the connection.
         }
      }
   }
}

//This is just a main function.  This program takes a single argument:
//the address on which to listen for connections.  This address has to
//be local, of course, since obviously you can't listen for connections
//on someone else's computer.  So, can specify your own IP and port
//number.  However, normally you just want to listen on all local IP's.
//To do that, you omit the IP address and just give the port number.
//For example, if you are a 1337 #4X0R, you could specify ":31337" to
//listen on your favorite port.  The rest of us can use something else,
//too.  (Note: Most HTTP servers run on port 80.  However, port 80 is
//normally reserved and can only be accessed by the root or Administrator
//user.  I strongly recommend that you do NOT run this program as root,
//however.)
int GNetTest::Main(int argc, char* argv[])
{
   try
   {
      GNetTestPtr tester = GUF_NEW_PTR GNetTest;
      if(argc > 1)
         tester->Run(argv[1]);  //use argv[1] as the address
      else
         tester->Run(":34543");  //listen on the default address
   }
   catch(GException& e)
   {
      //doh!
      cout << "Uncaught exception:" << endl;
      cout << e.GetDescription() << endl;
   }

   return 0;
}

} //namespace guf