gawkinet: Simple Server

 
 2.10 A Simple Web Server
 ========================
 
 In the preceding node, we built the core logic for event-driven GUIs.
 In this node, we finally extend the core to a real application.  No one
 would actually write a commercial web server in 'gawk', but it is
 instructive to see that it is feasible in principle.
 
    The application is ELIZA, the famous program by Joseph Weizenbaum
 that mimics the behavior of a professional psychotherapist when talking
 to you.  Weizenbaum would certainly object to this description, but this
 is part of the legend around ELIZA. Take the site-independent core logic
 and append the following code:
 
      function SetUpServer() {
        SetUpEliza()
        TopHeader = \
          "<HTML><title>An HTTP-based System with GAWK</title>\
          <HEAD><META HTTP-EQUIV=\"Content-Type\"\
          CONTENT=\"text/html; charset=iso-8859-1\"></HEAD>\
          <BODY BGCOLOR=\"#ffffff\" TEXT=\"#000000\"\
          LINK=\"#0000ff\" VLINK=\"#0000ff\"\
          ALINK=\"#0000ff\"> <A NAME=\"top\">"
        TopDoc    = "\
         <h2>Please choose one of the following actions:</h2>\
         <UL>\
         <LI>\
         <A HREF=" MyPrefix "/AboutServer>About this server</A>\
         </LI><LI>\
         <A HREF=" MyPrefix "/AboutELIZA>About Eliza</A></LI>\
         <LI>\
         <A HREF=" MyPrefix \
            "/StartELIZA>Start talking to Eliza</A></LI></UL>"
        TopFooter = "</BODY></HTML>"
      }
 
    'SetUpServer()' is similar to the previous example, except for
 calling another function, 'SetUpEliza()'.  This approach can be used to
 implement other kinds of servers.  The only changes needed to do so are
 hidden in the functions 'SetUpServer()' and 'HandleGET()'.  Perhaps it
 might be necessary to implement other HTTP methods.  The 'igawk' program
 that comes with 'gawk' may be useful for this process.
 
    When extending this example to a complete application, the first
 thing to do is to implement the function 'SetUpServer()' to initialize
 the HTML pages and some variables.  These initializations determine the
 way your HTML pages look (colors, titles, menu items, etc.).
 
    The function 'HandleGET()' is a nested case selection that decides
 which page the user wants to see next.  Each nesting level refers to a
 menu level of the GUI. Each case implements a certain action of the
 menu.  On the deepest level of case selection, the handler essentially
 knows what the user wants and stores the answer into the variable that
 holds the HTML page contents:
 
      function HandleGET() {
        # A real HTTP server would treat some parts of the URI as a file name.
        # We take parts of the URI as menu choices and go on accordingly.
        if(MENU[2] == "AboutServer") {
          Document    = "This is not a CGI script.\
            This is an httpd, an HTML file, and a CGI script all \
            in one GAWK script. It needs no separate www-server, \
            no installation, and no root privileges.\
            <p>To run it, do this:</p><ul>\
            <li> start this script with \"gawk -f httpserver.awk\",</li>\
            <li> and on the same host let your www browser open location\
                 \"http://localhost:8080\"</li>\
            </ul>\<p>\ Details of HTTP come from:</p><ul>\
                  <li>Hethmon:  Illustrated Guide to HTTP</p>\
                  <li>RFC 2068</li></ul><p>JK 14.9.1997</p>"
        } else if (MENU[2] == "AboutELIZA") {
          Document    = "This is an implementation of the famous ELIZA\
              program by Joseph Weizenbaum. It is written in GAWK and\
              uses an HTML GUI."
        } else if (MENU[2] == "StartELIZA") {
          gsub(/\+/, " ", GETARG["YouSay"])
          # Here we also have to substitute coded special characters
          Document    = "<form method=GET>" \
            "<h3>" ElizaSays(GETARG["YouSay"]) "</h3>\
            <p><input type=text name=YouSay value=\"\" size=60>\
            <br><input type=submit value=\"Tell her about it\"></p></form>"
        }
      }
 
    Now we are down to the heart of ELIZA, so you can see how it works.
 Initially the user does not say anything; then ELIZA resets its money
 counter and asks the user to tell what comes to mind open heartedly.
 The subsequent answers are converted to uppercase characters and stored
 for later comparison.  ELIZA presents the bill when being confronted
 with a sentence that contains the phrase "shut up."  Otherwise, it looks
 for keywords in the sentence, conjugates the rest of the sentence,
 remembers the keyword for later use, and finally selects an answer from
 the set of possible answers:
 
      function ElizaSays(YouSay) {
        if (YouSay == "") {
          cost = 0
          answer = "HI, IM ELIZA, TELL ME YOUR PROBLEM"
        } else {
          q = toupper(YouSay)
          gsub("'", "", q)
          if(q == qold) {
            answer = "PLEASE DONT REPEAT YOURSELF !"
          } else {
            if (index(q, "SHUT UP") > 0) {
              answer = "WELL, PLEASE PAY YOUR BILL. ITS EXACTLY ... $"\
                       int(100*rand()+30+cost/100)
            } else {
              qold = q
              w = "-"                 # no keyword recognized yet
              for (i in k) {          # search for keywords
                if (index(q, i) > 0) {
                  w = i
                  break
                }
              }
              if (w == "-") {         # no keyword, take old subject
                w    = wold
                subj = subjold
              } else {                # find subject
                subj = substr(q, index(q, w) + length(w)+1)
                wold = w
                subjold = subj        #  remember keyword and subject
              }
              for (i in conj)
                 gsub(i, conj[i], q)   # conjugation
              # from all answers to this keyword, select one randomly
              answer = r[indices[int(split(k[w], indices) * rand()) + 1]]
              # insert subject into answer
              gsub("_", subj, answer)
            }
          }
        }
        cost += length(answer) # for later payment : 1 cent per character
        return answer
      }
 
    In the long but simple function 'SetUpEliza()', you can see tables
 for conjugation, keywords, and answers.(1)  The associative array 'k'
 contains indices into the array of answers 'r'.  To choose an answer,
 ELIZA just picks an index randomly:
 
      function SetUpEliza() {
        srand()
        wold = "-"
        subjold = " "
 
        # table for conjugation
        conj[" ARE "     ] = " AM "
        conj["WERE "     ] = "WAS "
        conj[" YOU "     ] = " I "
        conj["YOUR "     ] = "MY "
        conj[" IVE "     ] =\
        conj[" I HAVE "  ] = " YOU HAVE "
        conj[" YOUVE "   ] =\
        conj[" YOU HAVE "] = " I HAVE "
        conj[" IM "      ] =\
        conj[" I AM "    ] = " YOU ARE "
        conj[" YOURE "   ] =\
        conj[" YOU ARE " ] = " I AM "
 
        # table of all answers
        r[1]   = "DONT YOU BELIEVE THAT I CAN  _"
        r[2]   = "PERHAPS YOU WOULD LIKE TO BE ABLE TO _ ?"
        ...
 
        # table for looking up answers that
        # fit to a certain keyword
        k["CAN YOU"]      = "1 2 3"
        k["CAN I"]        = "4 5"
        k["YOU ARE"]      =\
        k["YOURE"]        = "6 7 8 9"
        ...
      }
 
    Some interesting remarks and details (including the original source
 code of ELIZA) are found on Mark Humphrys' home page.  Yahoo!  also has
 a page with a collection of ELIZA-like programs.  Many of them are
 written in Java, some of them disclosing the Java source code, and a few
 even explain how to modify the Java source code.
 
    ---------- Footnotes ----------
 
    (1) The version shown here is abbreviated.  The full version comes
 with the 'gawk' distribution.