menu
  Home  ==>  papers  ==>  web  ==>  simple_web_server   

Simple Web Server - Felix John COLIBRI.


1 - Introduction

We will present here a simple web server and the corresponding simple web browser. The brower will request an Web page, the Server will send it back, and the Browser will display the text of this page (as an ASCII text). We will use the basic Windows Socket library, and a small class hierarchy.

Several servers and browsers are available, even in source form (see the reference list below). So the present Server and Browser were mainly built in order to validate our CLASS organisation, before using this CLASS library for for custom applications




2 - The Socket Library

2.1 - Requirements

As presented in the socket programming paper, any Socket library using the asynchronous mode contains
  • a client socket which uses Socket, Connect, Send, Recv and CloseSocket calls, with a Windows message handler receiving fd_connect, fd_write, fd_read and fd_close notifications
  • a server socket with Socket, Bind, Listen and CloseSocket and a Windows message handler receiving fd_accept, fd_write, fd_read and fd_close notifications. The fd_accept notification is used to create separate sockets for each incoming client, and each of those socket performs the actual communication with the Client.
Our goal was to separate as much as possible the Client part from the Server part. Here is the result.

2.2 - The overall picture

Our library contains the following CLASSes:
  • a c_base_socket containing all attributes and methods uses by all three kinds of sockets (client, server, server_client)
  • a c_base_socket_with_buffer is used to manage the received or sent data which may arrive or be assembled in several packets
  • a c_client_socket, used by client applications (a Browser, for instance)
  • a c_server_socket which basically waits for Client connections and creates the c_server_client_sockets. The c_server_client_sockets are managed by a c_server_client_socket_list
  • a c_server_client_socket which handles the communication with a single Client
The UML class diagram is the following:

socket library UML class diagram



2.3 - c_base_socket

Everything that is not specific to server handling or client handling has been placed in this CLASS, which is defined by:

 c_base_socketclass(c_basic_object)
                  m_wsa_datatwsaData;

                  m_base_socket_handletSocket;
                  m_window_handlehWnd;

                  m_is_openBoolean;

                  m_on_after_socket_errort_po_base_socket_event;
                  m_on_after_do_close_sockett_po_base_socket_event;

                  Constructor create_base_socket(p_nameString);

                  procedure raise_exception(p_socket_errort_socket_error;
                      p_wsa_error_codeIntegerp_error_textString);

                  procedure wsa_startup;
                  procedure allocate_window;
                  procedure WndProc(var MessageTMessage);

                  procedure create_win_socket;

                  function f_do_send_string(p_stringString): IntegerVirtual;
                  function f_do_send_buffer(p_pt_bufferPointer;
                      p_send_countInteger): IntegerVirtual;
                  function f_do_read_data(p_ptPointerp_get_maxInteger): Integer;

                  procedure do_close_win_socketVirtual;
                end// c_base_socket

And:

  • to handle the Windows notification we need a handle. We chose to create a new (invisible) Window, using AllocateHwnd. Since both c_client_socket and c_server_socket will use the window, we placed the window management in the base socket. The window handle is placed there as well
  • the calls used to read and write data have also been placed at this base level
  • m_on_after_do_close_socket is an event pointer, used to notify that CloseSocket has been called


2.4 - c_base_socket_with_buffer

When data arrives in packet, or when we want to assemble the data before sending it, we need some buffering. This is handled by:

 c_base_socket_with_buffer=
     class(c_base_socket)
       m_c_reception_bufferc_byte_buffer;
       m_c_emission_bufferc_byte_buffer;

       Constructor create_base_socket_with_buffer(p_nameString);

       procedure send_buffered_dataVirtual;
       procedure receive_buffered_dataVirtual;

       Destructor DestroyOverride;
     end// c_base_socket_with_buffer



2.4.1 - c_client_socket

Basically this CLASS is used to connect to a Server, send over some request, and wait for the answer. Since data is not transfered in a single chunk, we must buffer the data, and this is why the class is derived from the c_base_socket_with_buffer:

 c_client_socket=
     class(c_base_socket_with_buffer)
       m_lookup_handleTHandle;
       m_pt_get_host_dataPointer;
       m_on_after_socket_host_lookupt_po_client_socket_event;
       m_on_after_socket_connectedt_po_client_socket_event;

       m_on_after_socket_wrote_datat_po_client_socket_event;
       m_on_after_socket_received_datat_po_client_socket_event;
       m_on_after_remote_server_client_socket_closedt_po_client_socket_event;

       Constructor create_client_socket(p_nameString);

       procedure wsa_selectVirtual;

         procedure handle_wm_lookup_host(var pv_wm_lookup_host:
             t_wm_lookup_host); Message wm_lookup_host;
       procedure do_lookup_win_socket_server(p_server_nameString);

       procedure do_connect_win_socket(p_server_ipStringp_portInteger);

       procedure handle_wm_async_select(var pv_wm_async_select:
           t_wm_async_select); Message wm_asynch_select;

       procedure do_close_win_socketOverride;
     end// c_client_socket

And:

  • when we only know the symbolic name of the Server (www.borland.com) we have to search the IP address (80.15.235.71). We perform this search in an asynchronous way, using a message notification telling us about the search result. This is managed by the "lookup" attributes and methods
  • the selection of client messages (fd_connect, fd_write, fd_read, fd_close) is specified with wsa_select. handle_wm_async_select is the message handler
  • to connect to a Server, we call do_connect_win_socket. When the connection is established, we receive an fd_connect notification
  • we then send data using f_do_send_buffer. And when the Server sends data back, the fd_read notification stores this data in the reception buffer


2.5 - c_server_socket, c_server_client_socket

The c_server_socket is defined by:

 c_server_socket=
     Class(c_base_socket)
       m_c_parent_refc_basic_object;

       m_on_after_acceptt_po_accept_socket_event;

       m_c_server_client_socket_listc_server_client_socket_list;
       m_c_class_of_server_client_socketc_class_of_server_client_socket;

       Constructor create_server_socket(p_nameString;
           p_c_class_of_server_client_socketc_class_of_server_client_socket);

       procedure wsa_selectVirtual;

       procedure do_bind_win_socket(p_portInteger);
       procedure do_listen_to_client(p_listen_queue_sizeInteger);

       procedure handle_wm_async_select(var pv_wm_async_select:
           t_wm_async_select); Message wm_asynch_select;

       procedure do_close_win_socketOverride;

       Destructor DestroyOverride;
     end// c_server_socket

Note that:

  • wsa_select and handle_wm_async_select work in the same way as for the c_client_socket (but we want the fd_accept notification, and have no use for fd_connect)
  • do_bind_win_socket and do_listen_to_client start the client watch
  • when a Client connects, the fd_accept notification will be used to create a c_server_client_socket and add it to the m_c_server_client_socket_list.
  • note that the server itself never sends or receives any data. But all the c_server_client_sockets will use the c_server_socket message handler. So the handling of fd_write, fd_read and fd_close which are sent to one of the c_server_client_socket, have to be monitored in the c_server_socket.handle_wm_async_select message handler.


The c_server_client_socket is defined by:

 c_server_client_socket=
     class(c_base_socket_with_buffer)
       // -- back link to the c_server_socket
       m_c_server_socket_refc_server_socket;

       Constructor create_server_client_socket(p_nameString;
           p_c_server_socket_refc_server_socket); Virtual;

       // -- Empty handlers. Will be Overriden by descendents
       procedure handle_can_write_dataVirtual;
       procedure handle_received_dataVirtual;
       procedure handle_remote_client_closedVirtual;
     end// c_server_client_socket

and this object is contained in a tStringList encapsulation using the socket handle as a key:

 c_server_client_socket_list=
     Class(c_base_socket)
       m_c_server_client_socket_listtStringList;

       constructor create_server_client_socket_list(p_nameString);

       function f_c_server_client_socket(p_indexInteger): c_server_client_socket;
       procedure add_server_client_socket(p_socket_handle_stringString;
           p_c_server_client_socketc_server_client_socket);
       procedure delete_server_client_socket(p_socket_handle_stringString);
       function f_c_find_server_client_socket_index(
           p_socket_handle_stringString): Integer;
       function f_c_find_server_client_socket(
           p_socket_handle_stringString): c_server_client_socket;

       Destructor DestroyOverride;
     end// c_server_client_socket_list



Now the interesting part:

  • a Client connects:
    • c_server_socket receives an fd_accept notification
    • the fd_accept handler
      • calls Winsock.Accept to get the handle of the new Socket record
      • creates a new c_server_client_socket object
      • this object is appended to the c_server_client_list
    procedure handle_fd_accept_notification(p_socket_handletSocket);
      var l_address_socket_intSockAddrIn;
          l_address_sizeInteger;
          l_server_client_socket_handletSocket;
          l_c_server_client_socketc_server_client_socket;
      begin
        l_address_size:= sizeof(l_address_socket_in);
        l_server_client_socket_handle:= Accept(p_socket_handle, @l_address_socket_in, @l_address_size);

        // -- create this client socket, using a Class reference and a Virtual constructor
        l_c_server_client_socket:=
            m_c_class_of_server_client_socket.create_server_client_socket('serv_cli'Self);

        with l_c_server_client_socket do
        begin
          m_base_socket_handle:= l_server_client_socket_handle;
        end// with l_c_server_client_socket

        // -- add the socket to the list
        m_c_server_client_socket_list.add_server_client_socket(
            IntToStr(l_server_client_socket_handle), l_c_server_client_socket);

        // -- notify the user
        if Assigned(m_on_after_accept)
          then m_on_after_accept(Selfl_c_server_client_socket);
      end// handle_fd_accept_notification

  • since the WinSock sending buffer of the newly c_server_client_socket is empty, the server Windows message handler receives an fd_write notification. In our case, we do not send anything back to the client, since we have not yet analyzed what was requested

  • the server Windows message handler then receives an fd_read notification:
    • the parameter is the server_client handle which is used to locate the c_server_client socket
    • once this c_server_client_socket is found in the list, the data is read into its reception buffer.
    • any notification is sent to the user


Just a couple of notes:
  • the c_server_socket does not need any buffer: it receives the connection requests from the Clients, and generates the fd_accept notification. All data transfer will be performed between the c_server_client_socket and the remote c_client_socket
  • the c_server_client_sockets do require buffering, since the Client's request is size is not known: the c_server_client_socket is unable to tell beforehand how many bytes are supposed to arrive. So the c_server_client_socket grabs whatever arrives, stores them in a buffer, and analyze the content after each reception. Most of the TCP/IP protocol programming reside in the analysis of this buffer, with detection of double return/line feed, empty periods on single lines etc
  • the Delphi tServerSocket handles all received bytes in this class (the server client socket delegates reception and emission events to the server socket class). When we implement any protocol, most of the protocol specific handling will be placed in the server client socket. And we must keep the received bytes and analyzed parameters on a per server client basis. So this organization forces us to duplicate the server client socket objets and their container list in each protocol application.
    Therefore we chose to keep the reception and emission in a specific c_server_client_socket class, which can easily be derived. This will now be presented.


2.6 - New protocol implementation

When we implement any protocol (HTTP, POP2, or custom protocols), we try to keep the protocol independent part in the general socket classes, and put the protocol specific parts in derived components.

In our case

  • for the Client, we simply create a c_client_socket descendent to handle the Client part of the chosen protocol
  • for the Server:
    • a descendent (or a container) of the c_server_socket contains mainly the protocol parameters (pathes etc)
    • a descendent of the c_server_client_socket contains the analysis of the Client's requests, stores in its attributes the protocol parameters and values, builds the answer and sends it back
      The general c_server_socket is able to create a c_custom_server_client_socket CLASS because we use a VIRTUAL constructor, and a class reference:

       c_class_of_server_client_socketClass of c_server_client_socket;

       c_server_client_socket=
           Class(c_base_socket_with_buffer)
             // -- VIRTUAL: create different descendents
             Constructor create_server_client_socket(p_nameString;
                 p_c_server_socket_refc_server_socket); Virtual;
             // ...
           end// c_server_client_socket

       c_server_socket=
           Class(c_base_socket)
              m_c_class_of_server_client_socketc_class_of_server_client_socket;

              Constructor create_server_socket(p_nameString;
                  p_c_class_of_server_client_socketc_class_of_server_client_socket);
              // ...
            end// c_server_socket

      procedure handle_fd_accept_notification(p_socket_handletSocket);
        var l_c_server_client_socketc_server_client_socket;
        begin
          // ...
          // -- create this client socket, using a Class reference and a Virtual constructor
          l_c_server_client_socket:=
              m_c_class_of_server_client_socket.create_server_client_socket('serv_cli'Self);
          // ...
        end// handle_fd_accept_notification

      And then we create the descendent thus:

      Type c_http_server_client_socket=
               Class(c_server_client_socket)
                 Constructor create_server_client_socket(p_nameString;
                     p_c_server_socket_refc_server_socket); Override;
               end// c_http_server_client_socket

      var my_http_server_socketc_server_socket;

      my_http_server_socket:= c_server_socket.create_server_socket('http',
          c_http_server_client_socket);




The UML class diagram shows the derived class at the bottom, below the purple line.



We will illustrate this specialization with the HTPP protocol, implementing a simple server and browser.




3 - The HTTP Server and the HTTP Client

3.1 - The Browser

Our Browser
  • selects (in a tFileListBox) a page name
  • builds the HTTP request header including the requested page
  • sends the request header to the Server
  • waits the HTTP answer, and stores it in the c_client_socket buffer
  • displays the answer in a tMemo (ASCII display: no outlay font selection, image display etc)
Since the task is so easy, we did not derive a specific c_http_client_socket class, and handled everything in the Browser From.



Here is a snapshot of the browser:

web browser



3.2 - The Server

The Server receives the page requests and sends the pages to each Client.

We created a c_web_server class which contains the c_server_socket and the page path:

 c_web_serverclass(c_basic_object)
                 m_c_server_socketc_server_socket;
                 m_site_pathString;

                 Constructor create_web_server(p_namep_site_pathString);

                 procedure start_web_server(p_portInteger);
                 procedure close_web_server;

                 Destructor DestroyOverride;
               end// c_web_server



And each Client is handled by the c_http_server_client_socket CLASS:

 c_http_server_client_socket=
     Class(c_server_client_socket)
       m_http_answer_errort_http_answer_error;
       m_page_nameString;

       Constructor create_server_client_socket(p_nameString;
           p_c_server_socket_refc_server_socket); Override;

       procedure handle_received_dataOverride;

         function f_mime_type(p_page_nameString): String;
         function f_answer_content_type(p_MIME_typeString): String;
         function f_answer_content_length(p_body_lengthInteger): String;
         function f_server_full_file_name(p_file_namestring): String;
         procedure send_page_not_found(p_page_nameString);
       procedure read_file_and_send_page(p_page_nameString);
     end// c_http_server_client_socket

And the header is analyzed in this method:

procedure c_http_server_client_socket.handle_received_data;
  var l_read_indexInteger;
  begin
    // -- fetch the bytes
    receive_buffered_data;

    with m_c_reception_buffer do
    begin
      // -- presently only analyze GET and the name
      l_read_index:= f_string_position(0, 'get');

      if l_read_index< 0
        then m_http_answer_error:= e_no_GET
        else begin
            // -- skip GET
            m_read_index:= l_read_index+ 3;
            // -- skip blanks
            skip_blanks(m_read_index);
            m_page_name:= f_extract_non_blank(m_read_index);
            if (m_page_name'/'or (m_page_name'')
              then m_page_name:= k_default_HTML_page;

            read_file_and_send_page(m_page_name);
          end;
    end// with m_c_reception_buffer
  end// handle_received_data



Here is a snapshot of the server:

web server




4 - Improvements

Most socket library use an "horizontal" layering:
  • all basic socket routines (Socket, Bind, Listen, Accept, Connect, CloseSocket) in a single class. The primitives are used for both server sockets and client sockets
  • upper classes are dedicated to server or client handling
We chose stress the separation between client and server:
  • a basic socket class handles whatever is common to both client and server: Send, Recv
  • a c_client socket contains only client tasks
  • a c_server_socket contains
    • the server socket with addition / removal of server client sockets
    • each server client socket handles the incoming clients
This vertical organization eases the derivation of classes for specific TCP IP protocol applications.



Please note that:

  • this "vertical" socket library is not supposed to be a better socket library implementation. But with the the horizontal layer we found more difficult to separate the server part from the client part.
  • in addition, we did not try to minimize the communication between the layers. In a well built library, the user project is supposed to just call Run and OnFinished and everything in between is performed behind closed doors. On the contrary, we allowed every event to percolate up to the user. So we can watch when the connection was established, when data was received and so on. This would not normally be displayed on a user Form.
  • we did not try to implement all socket possibilities: UDP, blocking mode etc.
  • nevertheless, we are using this library for all our socket project (POP mail reader, NEWS group reader, FTP client for uploading our .HTML pages, or download the IIS logs from our hosting service, reverse DNS and WHOIS to analyze those logs, web spidering, CVS downloads, peer to peer server and clients, etc)



5 - Download the Sources

Here are the source code files:

Those .ZIP files contain:
  • the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
  • any .TXT for parameters
  • all units (.PAS) for units
Those .ZIP
  • are self-contained: you will not need any other product (unless expressly mentioned).
  • can be used from any folder (the pathes are RELATIVE)
  • will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP:
  • create or select any folder of your choice
  • unzip the downloaded file
  • using Delphi, compile and execute
To remove the .ZIP simply delete the folder.



As usual:

  • please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will be helpful for other readers
  • we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
  • or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
    Name :
    E-mail :
    Comments * :
     

  • and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in your blog or newsgroup posts when relevant. That's the way we operate: the more traffic and Google references we get, the more articles we will write.



6 - Other Servers and Browsers

Here are some other web server you might be interested in (check our links page ) :
  • the Delphi library SvrHTTP server unit
  • the Indy suite IdHTTPServer component
  • the ICS component suite tHTTPServer class
  • the TurboPower aPRO library (now on SourceForge)
Among other sources:
  • TinyWeb from RIT Labs which is a full fledged CGI server
  • Server 7 from Matt TAYLOR, which is an ISAPI server
  • Ben Ziegler added a server to allow checking his HTML generation tool (same testing purpose as Delphi's SvrHTTP)
  • kbMem is a commercial version of the previous


On the Browser front:
  • Delphi includes a tWebBrowser which encapsulates Microsoft's SHDOCVW.DLL Shell Doc Object and Control Library (nice and easy rendering, but no source)
  • pBear has a rendering engine Shareware (source$)
  • TurboPower APRO (SourceForge) has a complete rendering engine (free source)



7 - The author

Felix John COLIBRI works at the Pascal Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi Xe_n migrations, refactoring), Delphi Consulting and Delph training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP  /  UML, Design Patterns, Unit Testing training sessions.
Created: nov-04. Last updated: jul-15 - 98 articles, 131 .ZIP sources, 1012 figures
Copyright © Felix J. Colibri   http://www.felix-colibri.com 2004 - 2015. All rigths reserved
Back:    Home  Papers  Training  Delphi developments  Links  Download
the Pascal Institute

Felix J COLIBRI

+ Home
  + articles_with_sources
    + database
    + web_internet_sockets
      – tcp_ip_sniffer
      – socket_programming
      – socket_architecture
      – simple_web_server
      – simple_cgi_web_server
      – cgi_database_browser
      – whois
      – web_downloader
      – web_spider
      – rss_reader
      – news_message_tree
      – indy_news_reader
      – delphi_web_designer
      – intraweb_architecture
      – ajax_tutorial
      – bayesian_spam_filter
      + asp_net
    + oop_components
    + uml_design_patterns
    + debug_and_test
    + graphic
    + controls
    + colibri_utilities
    + colibri_helpers
    + delphi
    + firemonkey
    + compilers
  + delphi_training
  + delphi_developments
  + sweet_home
  – download_zip_sources
  + links
Contacts
Site Map
– search :

RSS feed  
Blog