Wibble help

AMG: This page is for questions and answers about how to use the Wibble web server. Report bugs at Wibble bugs.

Fetching backrefs...

Beware 09/04/2015 - What's the best way to do file includes? I can't source another template because it evals it.

AMG: I have never considered that, I'm sorry. You're right, [templatefile] reads the template file then passes its contents to [template], so there's no opportunity to tell [templatefile] it should read and insert the contents of another file. This is a hole in functionality. I'm open to suggestion though.


In-depth description of image file upload example

JM 12Feb2012 - the file index.html.tmpl on the "image file upload, cookies, sessions" example on Wibble examples is somehow connected to the "image.script" file mentioned in step 3, how that works?

AMG: image.script serves the images; that's all it does. I'll repeat it here, annotated with copious comments:

# If there is a sessionid cookie in the header,
if {[dict exists $header cookie sessionid ""]} {
  # Link the local session variable to an element in the global session array.
  # The linked element is the one whose key is the same as the value stored in
  # the sessionid cookie.  It's quite possible that the element doesn't exist,
  # but create the link anyway.
  upvar #0 sessions([dict get $header cookie sessionid ""]) session
}

# If:
# 1. [info exists session]: there was a sessionid cookie, and there exists an
#    associated element in the global sessions array,
# 2. [dict exists $session imagetype]: the imagetype dict element exists in
#    the value stored in the session variable,
# 3. [dict exists $session imagedata]: and the imagedata dict element exists,
if {[info exists session] && [dict exists $session imagetype]
 && [dict exists $session imagedata]} {
  # Serve the image to the client.  Use the imagetype dict element as the HTTP
  # content-type, and use the imagedata dict element as the content.
  dict set response header content-type [dict get $session imagetype]
  dict set response content [dict get $session imagedata]
} else {
  # But if any of that stuff wasn't true (e.g., no session data), 404 error.
  dict set response status 404
}

JM 13Mar2012 - Thanks for the explanation, what I do not understand is what is it triggering this code (i.e. I was expecting image.script to be part of the ACTION form so that whenever the submit button is clicked, such action is then triggered). I may be missing something basic, I appreciate your help.

AMG: It gets sourced by [scriptfile] in order to serve http://localhost:8080/image , which you will notice is referenced by <img src="image" />. The form's action is left at the HTML default, which is the same as the page that served the form to begin with. That page is provided by index.html.tmpl, which handles the image upload. That's very different from image.script, which handles the image download, i.e. display.


image file upload, cookies, sessions for Wibble 0.4

JM 2Jun2012 - Andy, you clearly stated that the "image file upload, cookies, sessions" example on Wibble examples may not work with recent versions of wibble. Just for the fun of it, I was trying to make it work with wibble 0.4 but I couldn't figure out the dictionary path to the cookie headers...could you give me a hint? (looks like I am not being able to "set-cookie" when I wrote something like this:

 % dict set state response header set-cookie sessionid=$sessionid\;Max-Age=$timeout\;Version=1

AMG: The set-cookie header has multiple parts, like we discussed the other day. For response headers, the place to look is [enheader], which encodes HTTP headers, meaning converting them from Wibble format (nested dictionaries, lists, and values) to HTTP format (mishmash of text, control, and escape characters). I'll repeat the relevant code here:

set-cookie {
    # Value is a list of cookie definitions.
    dict for {key2 val2} $val {
        append str "$nl$key: [enhex $key2]=[enhex [dict get $val2 ""]]"
        dict for {key3 val3} $val2 {
            switch $key3 {
            domain - path {
                append str \;$key3=[string map {; %3b} $val3]
            } port {
                append str \;port
                if {[llength $val3]} {
                    append str =\"[join $val3 ,]\"
                }
            } discard - httponly - secure {
                append str \;$key3
            } expires {
                switch [lindex $val3 0] {
                    abstime {append str \;expires=[entime $val3 1]}
                    reltime {append str \;max-age=[lindex $val3 1]}
                }
            }}
        }
        set nl \n
    }
}

According to this code, the set-cookie header is a nested dict which maps from cookie names ($key2) to cookie data ($val2). $val2 is itself a nested dict, where the "" element gives the cookie value and the other elements give the cookie attributes. The supported attributes are: domain, path, port, discard, httponly, secure, and expires. domain and path have simple string values. port has a list value, and each list element should be a number (Wibble doesn't validate this). The values for discard, httponly, and secure are ignored, so just use "". expires has a time value, meaning that it's a two-element list consisting of abstime or reltime followed by the the absolute or relative time.

At the moment, Wibble doesn't give the cookie version. I can add this if it's needed for browser compatibility.

Here's an example. Put this in a file called "cookie.script" in your docroot, then browse to "cookie":

dict set state response header set-cookie cookiename "" "cookie value"
dict set state response header set-cookie cookiename domain .whatever
dict set state response header set-cookie cookiename path /whatever
dict set state response header set-cookie cookiename port {80 8080}
dict set state response header set-cookie cookiename discard ""
dict set state response header set-cookie cookiename httponly ""
dict set state response header set-cookie cookiename secure ""
dict set state response header set-cookie cookiename expires {reltime 60}
dict set state response header set-cookie cookie2name "" "cookie2 value"
dict set state response header set-cookie cookie3name "" "cookie3 value"
vars $state

In addition to actually setting some cookies, this will display the header structure.

Now, let's apply these concepts to the sample line of code you posted. You wrote:

dict set state response header set-cookie sessionid=$sessionid\;Max-Age=$timeout\;Version=1

Here's the modern Wibble approach:

dict set state response header set-cookie sessionid "" $sessionid
dict set state response header set-cookie sessionid expires [list reltime $timeout]

One really nice thing about this approach is that Wibble knows what you're trying to do. This enables Wibble to give a nicely formatted debug display, e.g. the vars zone handler and the panic printout. More importantly, it makes it possible for Wibble to correctly encode each part of the cookie. For instance, Wibble passes the cookie value through [enhex] so you don't have to worry about semicolons in $sessionid. Another nice thing is that this nested dict format mirrors what's used by Wibble when receiving headers.

Hope this helps!

JM: yes, with your help, I was finally able to make it work...
should I post it here?

just a few changes really...

AMG: Yes, please! That would be great. :^)


JM 3Jun2012 - here is the example "image file upload, cookies, sessions" from Wibble examples with just a few modifications to make it work under wibble 0.4

% dict set state response header content-type "" text/html
% if {[dict exists $state response header cookie sessionid ""]} {
%   set sessionid [dict get $state response 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 state response header set-cookie sessionid "" $sessionid
%   dict set session expiration [clock add $now $timeout seconds]
%   after [expr {$timeout * 1000}] [list apply {{sessionid} {
%     unset -nocomplain ::sessions($sessionid)
%   }} $sessionid]
% }
% set imagetitle "My Image"
% if {[info exists post]} {
%   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]
%   }
% }
<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="image2" />
% }
  </td></tr></table>
</form>
</html>

here is also the companion script (please name it image2.script)

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

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

MHo 2021-12-26: I've mounted a ZIP-file to serve files from, which works so far. But the console shows the following errors (example) on some requests like hitting the browser back button:

tried to post events channel "rc43" is not interested in
    while executing
"chan postevent $fd read"
    (procedure "::zip:::eventPost" line 4)
    invoked from within
"::zip:::eventPost rc43"
    ("after" script)
tried to post events channel "rc43" is not interested in
    while executing
"chan postevent $fd read"
    (procedure "::zip:::eventPost" line 4)
    invoked from within
"::zip:::eventPost rc43"
    ("after" script)
tried to post events channel "rc43" is not interested in
    while executing
"chan postevent $fd read"
    (procedure "::zip:::eventPost" line 4)
    invoked from within
"::zip:::eventPost rc43"
    ("after" script)

Meanwhile I think it's probably a vfs::zip issue...