Wibble detailed description

AMG: This page provides detailed information about the design, implementation, and philosophy of the Wibble web server.

Fetching backrefs...

Name

"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.


Zone handlers

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.


State dictionary

Entries received from client

  • request socket: The name of the Tcl channel that is connected to the client.
  • request peerhost: Network address of the client.
  • request peerport: TCP port number of the client.
  • request method: HTTP method (GET, PUT, POST, HEAD, etc.).
  • request uri: HTTP URI, including query string.
  • request path: Path of client-requested object, excluding query string.
  • request protocol: HTTP/1.0 or HTTP/1.1, whatever the client sent.
  • request header: Dictionary of HTTP header fields from the client.
  • request rawheader: List of HTTP header lines.
  • request accept: Dictionary mapping from Accept-X: headers to acceptable values, sorted by descending qvalue.
  • request query: Dictionary of query string elements.
  • request rawquery: Query string in text form.
  • request post: Dictionary of POST elements.
  • request rawpost: POST in text form.

Locally derived entries

  • request port: Local TCP server port number.
  • request rawtime: Time of request in seconds since the epoch.
  • request time: Time of request, formatted with [clock format].
  • options prefix: Zone prefix name.
  • options suffix: Client-requested object path, sans prefix and query string.
  • options fspath: Object path with root option prepended. Only defined if root option is defined.

Common configuration options

  • options root: Filesystem directory corresponding to zone root directory.
  • options indexfile: Name of "index.html" file to append to directory requests.

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.

Element structure

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.


Response dictionary

  • status: The numeric HTTP status to send. 200 is OK, 404 is Not Found, etc.
  • header: Dictionary of HTTP header fields to send to the client.
  • content: Message body to send to the client.
  • contentfile: Name of a file containing the message body to send to the client.
  • contentchan: Name of a Tcl channel from which the message body will be read.
  • contentsize: When using contentchan, size in bytes of the response data.
  • sendcommand: Command prefix to handle sending the response to the client.

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.


Predefined zone handlers

  • vars: Echo state dictionary.
  • dirslash: Redirect directory requests lacking a trailing slash.
  • indexfile: Add indexfile to directory requests.
  • dirlist: Serve directory listings.
  • scriptfile: Execute *.script files.
  • templatefile: Expand *.tmpl files.
  • contenttype: Guess content-type header.
  • staticfile: Serve static files (not directories).
  • redirect: Send 301 Moved Permanently.
  • forbidden: Send 403 Forbidden.
  • notfound: Send 404 Not Found.