Version 17 of Wibble examples

Updated 2011-03-04 15:17:32 by AMG

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.

dzach: When faced with numeric character references [L3 ], e.g. "&#933 ;&#928 ;&#917 ;&#931 ;-google.png", wibble erroneously splits it at every ; character, so for the example given here we get: content-disposition {{} form-data name imagedata filename {"&#933} &#928 {} &#917 {} &#931 {} -google.png\" {}} in the post data, where filename contains "&#933 instead of "&#933 ;&#928 ;&#917 ;&#931 ;-google.png" (spaces inserted before ';' for display purposes). If the encoding of the page is set correctly, e.g. utf-8 for the example above, the problem doesn't appear, although it still exists.

AMG: Are you sure numeric character references are valid in the context of an HTTP header? Don't confuse HTML with HTTP. Is this HTTP header generated by your web browser (which one?) or are you doing it from a script or by hand? I will need a test case, if you want me to fix this.


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: [L4 ]. It's the second-to-most recent version. This change log entry [L5 ] 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.