For questions, bug reports, feature requests, improvements, or patches
please use
the tbnl-devel
mailing list. If you want to be notified about future releases
subscribe to
the tbnl-announce
mailing list. These mailing lists were made available thanks to
the services of common-lisp.net.
You can search the devel mailing
list here
(thanks to TiarnŠn ” CorrŠin).

I was asked to give
a tutorial
at the International Lisp
Conference 2007 in Cambridge, and - as it was
about Hunchentoot - spent
two nights in March to come up with a nice example of what one could do
with Hunchentoot. The result was basically a working, albeit
shaky version of what CL-WEBDAV is now. (I chose a WebDAV server
because I had already written one in, cough, Perl a couple of years
before, so I more or less knew what I had to do.)

I ended up not using CL-WEBDAV for the tutorial, though, as I came to
the conclusion that there was too much stuff in it that would distract
from the actual subject. But as it was more or less complete, I
invested some more time for testing, polishing, cleanup, and
documentation, and then released it.

The initial release has been tested mildly
with LispWorks
on Windows and with SBCL on Linux
against cadaver, WebDrive, and
the native
Windows XP client. I currently (April 2007) don't use CL-WEBDAV
myself, so I can't say much about its usefulness, but here's a list of
potential pitfalls that come to mind:

CL-WebDAV is
only class 1
compliant at the moment, i.e. there's no support for locks.
(And as a consequence it won't work with the native client of
OS X IIRC - I don't have a Mac to test with.) Class 2
compliance shouldn't be too hard to add, though. You can bribe me
if you desperately need it... :)

CL-WEBDAV uses a CLOS-based approach to deal with the resources on the
server.
Every handler
instantiates (based on the request sent by the client) one or more
objects of a class which is a subclass
of RESOURCE and then calls
generic functions on these object to actually manipulate the resources
represented by these objects. So, in order to implement your own
WebDAV server, you are supposed to
subclass RESOURCE and implement a
couple of methods. CL-WEBDAV comes with
two exampleclasses which
demonstrate how this can be done.

This is the base class you'll have to subclass if you want to create
your own custom WebDAV server. Each object of this class represents
one resource on the server and most of the time these objects are
created by the server using only the :SCRIPT-NAME
initarg. If you need more initialization to happen, write an :AFTER
method for INITIALIZE-INSTANCE.

The base class RESOURCE has only
one slot which can be manipulated with this accessor. If a resource
is created by the server, then it will fill this slot with
the script name
that was used to access the resource. In this case you should
only read the slot's value.

If you create your own RESOURCE
objects (for example
in RESOURCE-CHILDREN),
you should set the value of this slot to a string that could be used
as a script name to retrieve the resource. There are several places
where CL-WEBDAV is calling this function internally, so it is
important that you use a meaningful value.

Whenever a DAV handler is executed, this variable should be bound to
the resource class which is to be used. If you're
using CREATE-DAV-DISPATCHER,
this will already be taken care of for you, so you can ignore this
variable.

This subsection lists all generic functions which must be
specialized for your own resource classes. If you think that this is
too much work, you should
subclass FILE-RESOURCE where
this work is already done for you.

This function must return a string which,
according to the WebDAV RFC, "provides a name for the resource
that is suitable for presentation to a user." You must
specialize this generic function for your own classes.

This function is called for PUT requests and
must read length octets of data from the (flexi) stream stream
and store them in a place appropriate for the resource
resource. The return value is irrelevant.

This function must completely remove the
resource resource. It doesn't have to deal with dead properties,
and it can assume that resource doesn't have children in case
it's a collection. The return value is irrelevant.

This function must "move" the (contents of
the) resource source in such a way that it can in the future be
accessed as destination. It doesn't have to deal with dead
properties, and it can assume that source doesn't have children
in case it's a collection. The return value is irrelevant.

This function must "copy" the (contents of
the) resource source in such a way that the copy can in the
future be accessed as destination. It doesn't have to deal with
dead properties, and it can assume that source doesn't have
children in case it's a collection. The return value is irrelevant.

This must be a function which accepts a
Hunchentootrequest objectrequest and returns a generalized
boolean denoting whether request is a resource the DAV
server wants to handle.
It will be called by the dispatcher created with CREATE-DAV-DISPATCHER.
Usually, you'll want to look at the
script name of
the request or something like that - see the class FILE-RESOURCE for
an example.

Note that you specialize this function on the resource class
(or its name) and not on the resource like the other functions in this subsection.

This subsection lists all generic functions which can be
specialized for your own resource classes. You don't necessarily have
to because they have default methods, but in some cases it might make
sense.

This function must return
a universal
time denoting the time the resource resource
was created. There's a default method which
returns RESOURCE-WRITE-DATE, but
most likely you'll want to specialize this for you own classes and return a more meaningful value.

This function must return a string denoting
the MIME
type of the resource resource. It will only
be called if resource is not a
collection. There's a default method which always returns
"application/octet-stream", but most likely you'll want to specialize
this for your own classes.

This function should return either NIL or a
language tag as defined in section 14.13
of RFC 2068.
If the value returned by this function is not NIL, it
will also be used as the Content-Language header returned
for GET requests. There's a default method which always
returns NIL.

This function should return either NIL or a DAV
"source" XML node (structured as an XMLS
node) that, according to
the WebDAV
RFC, "identifies the resource that contains the unprocessed
source of the link's source." There's a default method which
always returns NIL.

This function should return an ETag for the
resource resource or NIL. If the
value returned by this function is not NIL, it will also
be used as the ETag header returned for GET requests.
There's a default method which synthesizes a value based on the script
name and the write date of the resource, and in most cases you
probably don't need to specialize this function.

This function should return either NIL or a DAV
"resourcetype" XML node (structured as
an XMLS node) that, according to
the WebDAV
RFC, "specifies the nature of the resource." There's a
default method which returns something fitting for collections and NIL
otherwise, and in most cases you probably don't need to specialize
this function.

This function must return a string which is
the part of a resource's HTTPS or HTTPS URI that comprises the
scheme, the host, and the port and ends with a slash - something
like "http://localhost:4242/" or "https://www.lisp.org/".

The default method synthesizes this from the information
Hunchentoot provides and
usually you only have to write your own method if you're sitting
behind a proxy.

When dealing with properties or for some
methods like RESOURCE-TYPE
you'll need to handle XML
data. You can probably get away without it, but in case you need them,
this section collects the relevant CL-WEBDAV functions to manipulate
XML.

We're representing XML as XMLS nodes which are very similar
to
CXML's XMLS
nodes but try to get namespaces right because they don't purport
to be compatible
with XMLS.

Accepts an array octets of octets representing a
DAV XML node and converts it into the corresponding XMLS node.
According to
the WebDAV
RFC, non-DAV elements are skipped unless they appear in positions
(like in a "prop" element) where arbitrary elements are
allowed. If root-name is given, it should be the
local name (a string) of a DAV node. In this case, the XML is
validated. This function is expected to be called from within a
Hunchentoot request and
calls ABORT-REQUEST-HANDLER
with a return code of
+HTTP-BAD-REQUEST+ if a parsing error occurs or if the XML is
invalid.

CL-WEBDAV has default methods to store, retrieve, and
manipulate dead
properties of resources as requested by the client. However,
these default methods simply keep all properties in one global hash
table without persisting them. For a production server, you
definitely want to specialize the generic functions listed in this section for your resource class.

This function must return all dead properties
of the resource resource as a list of XML elements structured as
XMLS nodes. There's a default method but you
should definitely specialize this for production servers.

This function must remove the currently stored dead property
designated by property (an XMLS
node) of the resource resource. There's a
default method but you should definitely specialize this for
production servers. The return value is irrelevant.

This function must replace the currently stored dead property
designated by property (an XMLS
node) of the resource resource
with property, i.e. property
doubles as the property itself and as the property designator.
There's a default method but you should definitely specialize this for
production servers. The return value is irrelevant.

This function must move all dead properties of the
resource source to the
resource destination. There's a default method
but you should definitely specialize this for production servers. The
return value is irrelevant.

This function must copy all dead properties of
the resource source to the resource destination. There's a
default method but you should definitely specialize this for
production servers. The return value is irrelevant.

Creates and returns
a dispatcher
for the class resource-class which must be a subclass of RESOURCE. If
ms-workaround-p is true (which is the default),
OPTIONS requests are always handled irrespective of the results of
ACCEPT-REQUEST-P - this
is needed to work around problems with some Microsoft DAV clients.

A dispatcher which'll dispatch to OPTIONS-HANDLER in case of
an OPTIONS request and decline otherwise. This is only useful if
you want to cater to Microsoft DAV clients which always
unconditionally send OPTIONS requests to the "/" root
resource. Sigh...

A sorted list of DAV compliance classes reported in the
DAV header when answering OPTIONS requests. It doesn't
make much sense to have more then class 1 in here as long as there's
no lock support, i.e. the initial value is the list (1).

The class FILE-RESOURCE maps
URIs in a straightforward way to (a subtree of) your local file
system. This is probably the most common way to serve DAV resources,
and if you want to do it like this, you can
subclass FILE-RESOURCE and
adapt it without the need to
implement loads of methods. Of course, you
can go wild and store resources in a database, or generate them
dynamically, or whatever, but then this class is not for you and you
have to invent your own subclass
of RESOURCE.

Again, this is not meant to be ready for a production system. You
should at least make sure
that properties are persisted.

This generic function is called for subclasses of
FILE-RESOURCE to determine
the base pathname that's currently being used, i.e. the part
of the filesystem where the files served by the DAV server are stored.
The function must return
the namestring
of
the truename
of
an absolute
pathname denoting a directory, specifically it must return a string
starting and ending with slashes. (Note: This should work on Windows
as well.) You can specialize this function (either on the class or on
the name of the class) if you want.

The value of this variable is the return value of the default method
for FILE-RESOURCE-BASE-PATH-NAMESTRING.
It should be
the namestring
of
the truename
of
an absolute
pathname denoting a directory, specifically it must return a string
starting and ending with slashes. (Note: This should work on Windows
as well.)

This generic function is called for subclasses of
FILE-RESOURCE to determine
the base URI that's currently being used, i.e. the prefix
the script name
of a resource's URI must have in order to be valid. (In other words:
this URI represents the top-level collection of the DAV server.) The
function must return a string which starts with a slash if it's not
empty and does not end with a slash and is not
URL-encoded. You can specialize this function (either on the class or
on the name of the class) if you want.

The value of this variable is the return value of the default method
for FILE-RESOURCE-BASE-URI. It should be a string which starts with a
slash if it's not empty and does not end with a slash and is not
URL-encoded.

Authorized file resources are specialized file
resources, included in CL-WEBDAV solely for demonstration
purposes. DAV clients are required to authenticate themselves
using basic
HTTP authentication (user names must only contain ASCII
characters, decimal digits, underlines, and hyphens, and are
case-insensitive, the password must be the same as the user name), and
users are assigned their own private subtree of the file system.