From oleg Tue Mar 25 13:17:13 CST 1997 Newsgroups: comp.lang.scheme,comp.programming,comp.infosystems.www.authoring.cgi,comp.infosystems.www.misc Subject: the Web IS a _computer_, HTML its language (and a pushy Scheme CGI setting it off) Summary: a tail-f CGI that starts a Server-Net-Browser hyper-computer Followup-To: Distribution: From: oleg-at-pobox.com Keywords: Finite Automaton, FSM, HTTP, Scheme, CGI, Computation, WWW, Push Cc: Status: RO I apologize for the corny title; yet I mean it _literally_. This article is to describe a computer (a multi-threaded Finite State machine) with the Net as its "hardware", an http server and a browser as its TWO engines, http messages carrying html content as the computer's data, and url as a program counter. This whole conglomerate can really perform a sequence of steps on its own, querying and changing a global state: it does _compute_. BTW, the CGI script that sets the whole process off can also serve a _real_ purpose, say, monitoring operating system's logs. A while back there has been a discussion in comp.programming if HTML is a Programming [language]. The interaction between a browser and a http server -- which is typically a browser sending a request, the server replying, and they both shut up -- can be called computation by stretching a point. Just as one can press a "PI" button on a calculator and say that it has "computed" 3.141592. Many people however won't be so generous to call trivial fetching calculation. Still, there are calculators that can (after being properly programmed and set off) perform _automatically_ a _sequence_ of steps that query and modify a global environment and arrive at some (visual) result. So can the whole HTTP network, as we are about to show. Because it's HTML code that carries this global state around and tells a browser to switch to another state, HTML can be rightfully called a programming language. It is not for nothing the fad word 'push' is mentioned in the title of this article: server push is what the CGI script actually does to do the real work. And yes, this CGI script is written entirely in Scheme. Hopefully this gives one answer to a question (recently posted on comp.lang.scheme) if Scheme can have any real use. To start the web computation, "Open URL" http://your.host/cgi-bin/tail-f One may specify parameters along the way (especially if the URL is going to be embedded in a link): http://your.host/cgi-bin/tail-f?period=600&file=/w/web/ns-home/httpd-80/logs/access As the script's name implies, the script watches a given 'file', sending its last 'nlines' lines to the client. The script doesn't quit after the first peek into the file, but rather keeps watching the file, checking up on it every 'period' seconds. The watching stops when a user agent (a browser, that is) closes the connection: say, the user has pressed the 'stop' button. Of course watching terminates if the file disappears. All in all, this is similar to a familiar UNIX "tail -f" command, it merely works across the net. Opening the tail-f URL starts off the server-web-browser finite automaton. When it stops, you'll see the result of the computation: a frame set with a HTML form on the top, and the last lines of the 'file' (if the file name was specified with the URL). The form (the top frame) presents the parameters of the script -- file name, watch period, nlines -- and their specified or implied values. One can enter/modify them right on the form, and then click on the 'watch' button. The bottom frame shows the tail of the file (or the reason why it can't be done: say, the file does not exist or can't be read). Beside 'file', 'nlines', 'period' parameters, the script uses one more, "internal" parameter 'send', which carries the state of the automaton and passes it between its two engines, the server and the browser. All in all, the automaton has four states: Initial state (#0): 'send' parameter is absent The script only outputs the framework (frames) Its first frame (or the body of ) will call this script again with send=form The second frame will call this script with send=watch (thus the computation is forked in two parallel "threads")! Making a body: send=form makes a form with the current tail-f parameters and sends it to the client form's ACTION is the present script with send=watch Initiating watching the file: send=watch Locating the file, and and letting the user know that we starting the watch Really watching the file: send=watching Once initiated, the automaton goes through all these states without any user intervention. It's the last, final state that does the useful work: checks upon the file, and sends its last few lines to the user, using a "server push". BTW, in scanning/watching a file, we deliberately don't employ direct positioning of the file. For one thing, fseek() etc. functions are not standard in Scheme. For another, treating a file as a purely sequential stream enables us to handle 'file's which are not regular files per se: named (FIFO) pipes, raw devices, serial ports, etc. For example, you can create and activate a FIFO pipe: bash$ mknod /tmp/tf1 p bash$ while true; do date > /tmp/tf1; sleep 10; done and then open URL http://your.host/cgi-bin/tail-f?file=/tmp/tf1&nlines=2 or simply, http://your.host/cgi-bin/tail-f and enter "/tmp/tf1" on the form. After that, whenever you glance at screen, you'll see two most recent timestamps in the bottom frame, updated and "scrolled" automatically. This script's functioning has been tested with Netscape 3.0 on a Mac and UNIX (HP-UX 10.00, Solaris 2.3), Netscape 2.01 on Win95 and (with limited functionality) IE 3.0 on Win95. The CGI script itself is interpreted by Gambit, a Scheme interpreter/compiler. The script can be also compiled: that is, translated into C by GambitC compiler (gambc24g) and then compiled into a standalone executable. Alas, I don't have access to a publicly accessible HP box, or any other computer with Gambit or Scheme interpreter/compiler already installed. The text of the script is available from: http://pobox.com/~oleg/ftp/Scheme/tail-f.scm See http://pobox.com/~oleg/ftp/Scheme/myenv.scm for definitions of ++, --! etc. macros and 'cout', 'cerr' procedures I use in my Scheme code. Please see http://pobox.com/~oleg/ftp/Scheme/ for descriptions. The script implements all CGI functionality, tail-f functionality, and the "server push". It also defines CGI:url-unquote to parse a QUERY_STRING into an assoc list, and declares and implements a circular buffer object. The latter two parts can be used independently. The following snippet from the tail-f.scm code shows how CGI programming in Scheme actually looks like. BTW, this fragment tells how the script makes the browser call itself again, in another state. The snippet is a little bit long: I apologize. (let* ((query-string (OS:getenv "QUERY_STRING")) (query-parms (append (CGI:url-unquote (or query-string "")) ; default values for the optional parameters '(("period" "5") ("nlines" "10")))) (self-url (string-append "http:" (or (OS:getenv "SCRIPT_NAME") (CGI:bail-out "SCRIPT_NAME env parameter expected")))) ) ; Prepare to watch the file... (define (TF:prepare-watch) (if (or (not file-to-watch) (string=? file-to-watch "")) (CGI:bail-out "no file name specified")) (if (not (OS:file-exists? file-to-watch)) (CGI:bail-out "The file " file-to-watch " does not exist<BR>" "or is not permitted to read")) (let ((self-url-full (string-append self-url "?" (or query-string "") (if query-string "&" "")))) (cout "Content-type: text/html" nl nl "<HTML><HEAD><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0; URL=" self-url-full "send=watching\">" "</HEAD><BODY><P>Starting watch of " file-to-watch "<P>After the file is completely scanned, this display would " "<I>automatically</I> change " "and you will see the last " nlines " lines of the file<P><BR>" nl "If the change does not seem to be coming for a long time, " "your browser may not support " "a &lt;META&gt; tag. In this case, please " "<A HREF=\"" self-url-full "send=watching\">follow this link</A>" "</BODY></HTML>" flush-output))) To run the script, you need Gambit (2.4g), or any other Scheme interpreter; I can also provide a compiled (after translation from Scheme to C) executable for HP-UX platform. A word of warning: the script really tells you the tail of any file it can read. Thus every file which is world-readable on your system becomes, well, WORLD-readable. Your sysadm may not like this idea. It is rather easy however to add some form of file access control: for example, by modifying the script to allow checking up on files only >from /tmp directory. Better yet, one can use functionality already provided by a HTTP server: put the tail-f script into a directory access to which is controlled by .htaccess. I will really appreciate any comments, questions and suggestions.