AMG: This page provides detailed information about the design, implementation, and philosophy of the Wibble web server.
"Wibble" is similar in sound to "Wub", and according to the Jargon File [L1 ], it is one possible pronunciation of "www".
The similarity in name and symmetry of function between Wibble and "WippleWobble - A Mini Web Browser (for Windows)" is entirely coincidental.
Zones are analogous to domains in Wub and TclHttpd. Zones are matched against the URI by prefix, with an important exception for directory names. I use the term "zone" instead of "domain" because I don't want any confusion with DNS domain names. Someday I may add support for virtual hosts via the Host: header, which means that DNS domain/host names will be included in zone names [L2 ].
Handlers can be stacked, so zones are defined by a combination of handlers which are executed in sequence. Each handler receives a state dictionary as its last argument. The state dictionary contains at least three sub-dictionaries: request, response, and options. The request dictionary is derived from the HTTP request. The response dictionary, which is often empty, is the preliminary response assembled by previous zone handlers. The contents of options indicate the match prefix and suffix, plus the (possible) filesystem path of the requested object, plus any other options specified when the zone handler was registered. The state dictionary can also contain custom sub-dictionaries that were put there by previous zone handlers. The handler returns using the [nexthandler] or [sendresponse] command. [nexthandler] takes any number of state dictionaries as arguments, which will be passed to subsequent handlers. [sendresponse] takes the final response dict to send to the client.
Zones also stack. For example, if no handlers for zone /foo return a response, then the handlers for / are tried. Just as the handlers within a zone must be specified in the order they are to be executed, the zones themselves must be specified in order of decreasing specificity. To inhibit this stacking behavior, be sure that a default handler is defined for the zone, e.g. notfound.
The $::wibble::zonehandlers variable defines the zones and their handlers. $::wibble::zonehandlers is a list of three-element triples: zone prefix, zone handler command prefix, and options dictionary. The [::wibble::handle] command is used to update this variable.
Statically, the zone handlers form a stack or list. But dynamically (during program execution), the zone handlers can branch from a list into a tree, which is traversed in a breadth-first manner to search for a response to send to the client. The tree branches whenever [nexthandler] is given more than one argument; each argument forms a new alternative handler stack operating on a modified state dictionary. When [nexthandler] is given zero arguments, the "node" is a leaf node, the tip of a dead branch; the state dictionary that was passed to the handler is removed from consideration. Simply returning from a zone handler is the same as calling [nexthandler $state] when $state has not been modified.
Support for configuration options varies between zone handlers. Zone handlers can also take positional configuration options by including them in the command argument to [::wibble::handle], which is actually a list constituting a command prefix.
header, query, and post are all dictionaries whose elements share a common structural convention. When it's possible for an element to exist but not have a value, or to both have a value and have attributes, the element is represented as a dictionary with the value stored with key "" (empty string) and with the attributes in the remaining keys.
[dict exists $query foo] checks if the foo key exists.
[dict exists $query foo ""] checks if the foo key exists and has a value.
[dict get $query foo ""] gets that value.
The vars zone will help you sort this all out. This HTML snippet:
<form method="post" enctype="multipart/form-data" action="/vars"> <input type="text" name="foo" /> <input type="text" name="bar" /> <input type="file" name="quux" /> <input type="submit" value="Submit" /> </form>
Results in this vars output:
post foo content-disposition {} | form-data |
post foo content-disposition name | foo |
post foo {} | value of foo |
post bar content-disposition {} | form-data |
post bar content-disposition name | bar |
post bar {} | value of bar |
post quux content-disposition {} | form-data |
post quux content-disposition name | quux |
post quux content-disposition filename | data.txt |
post quux content-type {} | application/octet-stream |
post quux {} | contents of data.txt |
rawpost | (len=585) |
This shows that foo=value%20of%20foo and bar=value%20of%20bar. All three form inputs have content-disposition=form-data, but their content-disposition name attributes differ. content-disposition filename is available for quux, and there's a content-type too.
http://localhost:8080/vars?foo=value+of+foo&bar=value+of+bar&key+with+no+value produces the following vars output:
query foo {} | value of foo |
query bar {} | value of bar |
query {key with no value} | |
rawquery | ?foo=value+of+foo&bar=value+of+bar&key+with+no+value |
Notice the presence of {} (a.k.a. "") at the end of the query keys with values and the absence of same at the end of query keys without values.
The response header dictionary uses the same structure as the request header dictionary.
The sendcommand command prefix is suffixed with the socket name, the request dictionary, and the response dictionary. It should return true if processing is to continue or false if the socket is to be closed. If the sendcommand element is not present in the response dictionary, the [defaultsend] command is used.