On Fri, Apr 05, 2019 at 02:19:47PM -0700, Arthur A. Gleckler wrote: > A "dispatcher" defines how each request is handled. To add a new dispatcher > to a server, one calls: > > (web-server/add-dispatchers! web-server . web-dispatchers) The original version of Spiffy had something like this as well. It is very user-friendly and convenient, but it makes it quite difficult to remove dispatchers. It also makes it impossible to run multiple servers in different threads in the same process, which handle different applications. The current version of Spiffy uses a vhost map (which turns out to be a needless distraction) which is a ((host . handler) ...) alist in a SRFI-39 / R7RS parameter. The handler receives the "previous" handler (a sort of continuation), which initially is just a thing that renders a minimal 404 page. Then, when you add a new handler, it can inspect the path and query parameters and so on to determine if this is handled by it, and if not, it calls the continuation (which may be a chain of existing handlers already). What I like about this approach is that the request *dispatching* is completely separated from the request/response *handling*. There are in fact multiple libraries which implement dispatching. But in practice, most people either do it by hand or use the uri-dispatch egg[1]. After thinking about this, I think the vhost can also be part of the handler instead. It can just look at the "host" header (or the host in the URL, if it's absolute and we're in HTTP 1.0) and decide if it wants to handle this particular host. > Dispatchers are created using the macro `make-web-dispatcher', which > defines: > > * which incoming requests this dispatcher handles > > * how to parse variable elements of the path, e.g. <month> and <year> in > "/calendar/<year>/<month>" > > * how to parse query parameters I like the fact you can handle query parameters like this. It's probably smart to add header dispatching too. That way, you can for example have different handlers for different content types, or for different host headers. > * `Request' is the name of a variable which will be bound to the > `http-request' object in `body'. Here's the definition of `http-request', > slightly simplified: > > (define-record-type http-request ...) This record looks a lot like intarweb's request object. I think that means we're on the right track :) > * Paths are matched by patterns that contain strings, which must match > exactly, and variables, expressed as `(? variable)', which match anything > between slashes. In Intarweb, we parse URI paths as lists, so matching like this becomes quite trivial; it is quite common to use Andrew Wright's "match" macro to match path components, which is of course very similar to your path matching syntax. > Every handler should return three values: > > code: response code, a number, e.g. 200 for HTTP OK > > headers: an alist of headers beyond the basic headers Content-Length and > Content-Type In Spiffy, we use a "response" record type. It is quite similar to the http-request record, but contains different fields like the response status code, headers and the port to write to. The response object is initially populated by the web server, and the user can manipulate it to add headers etc. The annoying bit is you have to write the response object manually which will serialize the response line and headers. Then you can write the response body (if any) and close the port to indicate when you're done. Because this is a bit obnoxious, for convenience, there's a "send-response" helper procedure that accepts headers, a response body, status code and so on which will write the response headers and then the body and close the port. > output: a thunk that will write the output, or false if there is no > output. Content-Length will be computed from this automatically. That's a nice way to do it. Does it support streamed responses of unknown length? In Spiffy, you have to manually set a few headers and write the response object manually and then you can write the content body. Depending on some magic, the response will automatically be chunked. I really dislike how this works (I always forget how it works, even though I came up with it), so perhaps we can come up with a different way. I think streamed responses are important, because it makes web socket support and "long polling" possible, as is the ability to send large files (which do have a known length of course). Perhaps you can return either a string which will be returned directly, or a "writer" procedure which receives a port which, when written to, sends the payload wrapped in a "chunk" of a chunked encoding response. I'm not sure this is the perfect solution, because you ideally want to automatically support HEAD and Range requests too. And if possible, the caching headers should be set automatically as well. > I'm excited by the idea of standardizing web request handlers. It would be > great to make it easy for Scheme hackers to share web code across Scheme > implementations and web servers. Yeah! Cheers, Peter [1] https://wiki.call-cc.org/eggref/4/uri-match