Version 24 of Wibble examples

Updated 2012-05-27 00:53:23 by Jorge

AMG: This page contains example pages and applications for the Wibble web server. There may be some overlap with the Wibble zone handlers page. If all you have is a zone handler, put it in the zone handlers page. If you are explaining it in the context of a complete application, put it here. If you're showing off a useful modification of Wibble, maybe put that here too. And if you have a sample page or template, that's also welcome here.

Fetching backrefs...

Hello world

AMG: Put this in a file called index.html.tmpl and see what happens.

% dict set response header content-type text/html
<html><head><title>Hello from Wibble! - $uri</title></head><body>
% set rand [expr {rand()}]
% if {$rand > 0.5} {
random=[format %.3f $rand] &gt; 0.5<br/>
% } else {
random=[format %.3f $rand] &lt;= 0.5<br/>
% }
time/date=[clock format [clock seconds]]<br/>
milliseconds=[clock milliseconds]<br/>
clicks=[clock clicks]<br/>
% if {[info exists query] && ![dict exists $query noiframe]} {
<iframe src="?noiframe" width="100%"/>
% }
</body></html>

AMG: Thanks JBLZ for the bug fix. I had to make a similar one to [dirslash]; I don't think I've posted it yet.


Image file upload, cookies, sessions

AMG: Step 1. Make a directory, then create a file called wibble.tcl in that directory. Paste the Wibble implementation into the file. For this example, I used this version: [L1 ]. (Later versions may be incompatible, and I may forget to update this example!)

Step 2. Create a file in the same directory called index.html.tmpl with the following contents:

% dict set response header content-type text/html
%
% if {[dict exists $header cookie sessionid ""]} {
%   set sessionid [dict get $header cookie sessionid ""]
%   upvar #0 sessions($sessionid) session
% }
%
% set now [clock seconds]
%
% if {![info exists session]} {
%   set sessionid [format %llX [string reverse\
%       [string range [expr rand()] 2 end]]]
%   upvar #0 sessions($sessionid) session
%   set timeout 60
%   dict set response header set-cookie\
%       sessionid=$sessionid\;Max-Age=$timeout\;Version=1
%   dict set session expiration [clock add $now $timeout seconds]
%   after [expr {$timeout * 1000}] [list apply {{sessionid} {
%     unset -nocomplain ::sessions($sessionid)
%   }} $sessionid]
% }
%
% if {[dict exists $post imagetitle ""]
%  && [dict exists $post imagedata ""]
%  && [dict exists $post imagedata content-disposition filename]
%  && [dict exists $post imagedata content-type ""]
%  && [string length [dict get $post imagedata ""]]} {
%   set imagetitle [dict get $post imagetitle ""]
%   set imagefilename [dict get $post imagedata content-disposition filename]
%   set imagetype [dict get $post imagedata content-type ""]
%   dict set session imagetitle $imagetitle
%   dict set session imagedata [dict get $post imagedata ""]
%   dict set session imagefilename $imagefilename
%   dict set session imagetype $imagetype
% } elseif {[dict exists $session imagetitle]} {
%   set imagetitle [dict get $session imagetitle]
% } else {
%   set imagetitle "My Image"
% }
%
<html><head><title>Image file upload test</title></head><body>
<form method="post" enctype="multipart/form-data">
  <table border="1"><tr><th>
    Session ID
  </th><td>
    [enhtml $sessionid]
  </th></tr><tr><th>
    Expiration
  </th><td>
% set expiration [dict get $session expiration]
    [enhtml [clock format $expiration]], [expr {$expiration - $now}]s left
  </th></tr><tr><th>
    Sessions
  </th><td>
    <table>
% foreach id [lsort [array names ::sessions]] {
%   set expiration [dict get $::sessions($id) expiration]
    <tr><th>
      [enhtml $id]
    </th><td>
      [enhtml [clock format $expiration]], [expr {$expiration - $now}]s left
    </td></tr>
% }
    </table>
  </th></tr><tr><th>
    Image title
  </th><td>
    <input type="text" name="imagetitle" value="[enattr $imagetitle]" />
  </td></tr><tr><th>
    Image file
  </th><td>
    <input type="file" name="imagedata" />
    <input type="submit" value="Upload Image" />
% if {[dict exists $session imagedata]} {
  </td></tr><tr><th>
    File name
  </th><td>
    [enhtml [dict get $session imagefilename]]
  </td></tr><tr><th>
    Type
  </th><td>
    [enhtml [dict get $session imagetype]]
  </td></tr><tr><th>
    Image
  </th><td>
    <img src="image" />
% }
  </td></tr></table>
</form>
</html>

Step 3. Create another file in the same directory called image.script with the following contents:

if {[dict exists $header cookie sessionid ""]} {
  upvar #0 sessions([dict get $header cookie sessionid ""]) session
}

if {[info exists session] && [dict exists $session imagetype]
 && [dict exists $session imagedata]} {
  dict set response header content-type [dict get $session imagetype]
  dict set response content [dict get $session imagedata]
} else {
  dict set response status 404
}

Step 4. Run wibble.tcl using tclsh, wish, tclkit, or whatever you prefer. You're now up and running.

Step 5. Point your Web browser to http://localhost:8080/ , and upload images. Access the site from multiple browsers on multiple computers. If you have Firecookie [L2 ] or similar, try messing with your cookies. Edit the files you created. Look at the index.html.script file that Wibble generated from index.html.tmpl. Have fun.


AJAX

AMG: AJAX long polling can be done in Wibble. Here, I'll show you. Make a file called delay.html and put it in your docroot:

<html><head><script type="text/javascript">
  function delay() {
    if (window.XMLHttpRequest) {
      xmlhttp = new XMLHttpRequest();
    } else {
      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.onreadystatechange = function() {
      if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
        document.getElementById("result").innerHTML = xmlhttp.responseText;
      }
    }
    document.getElementById("result").innerHTML = "Waiting...";
    time = document.getElementById("time").value;
    xmlhttp.open("GET", "delay?time=" + time, true);
    xmlhttp.send();
  }
</script></head><body>
  <p>How long shall I wait?</p>
  <p><input type="text" id="time" /> milliseconds</p>
  <p><button type="button" onclick="delay()">Do It!</button></p>
  <div id="result" />
</body></html>

And another called delay.script:

after [dict get $query time ""] [resume timer]
set start [clock milliseconds]
suspend timer
set finish [clock milliseconds]
dict set response header content-type text/plain
dict set response content "I waited for [expr {$finish - $start}] milliseconds."

Go to http://localhost:8080/delay.html , type a number in the box, click the button, and wait. ;^)

dzach 2011-3-1: Looks like something is missing. I'm getting an error invalid command name "resume" while executing "resume timer".

AMG: Oops, you're right. I made a bunch of changes to Wibble since writing this example, which is now out of date, and I forgot to update it. Try again using this version: [L3 ]. It's the second-to-most recent version. This change log entry [L4 ] shows what all has been done since then.

Part of the reason I haven't updated the examples is that I'm not sure I have ICC quite right. When I get some time (who knows when that will be...), I'll finish the large example AJAX application I've been working on, quite possibly redoing ICC in the process. You see, I can't know that I've chosen the right set of functionality until I've tried using it. Anyway, at that time I'll update the other examples too.

dzach: Thanks for the reply, it works now.


Example form

AMG: By JM's request, here's a template file that serves a form that simply echoes its own data back. It supports both GET and POST, and for POST it supports application/x-www-form-urlencoded, multipart/form-data, and text/plain. It allows file upload, but this only works fully for multipart/form-data POST (otherwise, the browser only sends the filename). This example also demonstrates how to deal with multiple form elements having the same name. You can change the method and enctype right in the form, though the new values won't take effect until the page reloads after hitting Submit. To run this example, just put in a file called "echo.html.tmpl" located inside your docroot. Then point your browser to http://localhost:8080/echo.html or wherever you put it.

%;# Declare the content type.
% dict set state response header content-type "" text/html
%
%;# Just return the value.
% proc x {val} {return $val}
%
%;# Set some defaults.
% set method POST
% set enctype multipart/form-data
% set numrows 5
% set rowdata {}
%
%;# Import data from query string and post data.
% foreach source {query post} {
%;  # Check if there is any query or post data.
%   if {[info exists $source]} {
%;    # Get and validate the method.
%     if {[dict exists [set $source] method ""]
%      && [dict get [set $source] method ""] in {GET POST}} {
%       set method [dict get [set $source] method ""]
%     }
%
%;    # Get and validate the enctype.
%     if {[dict exists [set $source] enctype ""]
%      && [dict get [set $source] enctype ""] in {
%         application/x-www-form-urlencoded multipart/form-data text/plain}} {
%       set enctype [dict get [set $source] enctype ""]
%     }
%
%;    # Get and validate the number of rows.
%     if {[dict exists [set $source] numrows ""]
%      && [regexp {^\d+$} [dict get [set $source] numrows ""]]
%      && [dict get [set $source] numrows ""] < 1000} {
%       set numrows [dict get [set $source] numrows ""]
%     }
%
%;    # Get rowdata.  This demonstrates how to access multiple keys all with
%;    # the same name.  Yes, it's less efficient than when everything has a
%;    # unique name, but at least it works.
%     foreach {key val} [set $source] {
%       if {$key eq "rowdata" && [dict exists $val ""]} {
%         lappend rowdata [dict get $val ""]
%       }
%     }
%
%;    # Get uploaded file dictionary.
%     if {[dict exists [set $source] file]} {
%       set file [dict get [set $source] file]
%     }
%   }
% }
%
%;# Construct the form attributes.
% set formattrs "method=\"[enattr $method]\""
% if {$method eq "POST"} {
%   append formattrs " enctype=\"[enattr $enctype]\""
% }
<html><body>
  <form $formattrs accept-charset="iso-8859-1">
%;# Method selector.
    Method:
% foreach option {GET POST} {
    <input type="radio" name="method" value="[enattr $option]"\
      [if {$method eq $option} {x {checked="checked"}}]>[enhtml $option]</input>
% }
    <br />

%;# Enctype selector.
    POST enctype:\
    <select name="enctype">
% foreach option {
%   application/x-www-form-urlencoded multipart/form-data text/plain
% } {
      <option [if {$enctype eq $option} {x {selected="selected"}}]\
        value="[enattr $option]">[enhtml $option]</option>
% }
    </select>\
    <br />

%;# Row count selector.
    Number of rows:\
    <input type="text" name="numrows" value="[enattr $numrows]" />\
    <br />

%;# Row data.
% for {set i 0} {$i < $numrows} {incr i} {
    Row #[enhtml [expr {$i + 1}]]: <input type="text" name="rowdata"
%   if {$i < [llength $rowdata]} {
      value="[enattr [lindex $rowdata $i]]"
%   }
    /><br />
% }
    
%;# File upload selector.
    File upload:\
    <input type="file" name="file" />\
    <br />

%;# Submit button.
    <input type="submit" value="Submit" />
  </form>

%;# Uploaded file information.
% if {[info exists file]} {
  <table>
    <tr><th colspan="3">Uploaded file</th></tr>
%   dict for {key val} $file {
%     if {$key eq ""} {
    <tr><td colspan="3"><pre>[enpre $val]</pre></td></tr>
%     } else {
%       dict for {key2 val2} $val {
    <tr><td>[enhtml $key]</td><td>[enhtml $key2]</td>
    <td><tt>[enhtml $val2]</tt></td></tr>
%       }
%     }
%   }
  </table>
% }
</body></html>

JM 26 May 2012 - the example works fine under IE, however, I had to change the first line to make it work under chrome and safari...

 %;# Declare the content type.
 %   dict set       response header content-type    text/html
 %;# dict set state response header content-type "" text/html