+++ /dev/null
----
-postid: 000
-title: Hunchentoot: review of acceptor code
-date: August 1, 2019
-author: Lucian Mogoșanu
-tags: tech, tmsr
----
-
-This post is part of a series on [Common Lisp WWWism][cl-www], more
-specifically of [ongoing work][hunchentoot-i] to
-[understand][hunchentoot-ii] the web server known as Hunchentoot. Even
-more specifically, this post exhaustively covers each of the methods
-and functions implemented by the acceptor class.
-
-Let's begin with my own version of (ac)[counting][ave1] the functions
-in acceptor.lisp. First, we read all the top-level S-expressions in
-the file:
-
-~~~~ {.commonlisp}
-(defparameter *top-level-sexprs*
- (with-open-file (in "acceptor.lisp")
- (loop for sexpr = (read in nil :err)
- until (eq sexpr :err)
- collect sexpr)))
-~~~~
-
-and after fiddling around with fixing read-time weird encounters, we
-keep expressions that are lists beginning with "defmethod" or "defun":
-
-~~~~ {.commonlisp}
-(defparameter *funcs*
- (loop for def in *top-level-sexprs*
- with acc = nil do
- (when (and (consp def) (member (car def) '(defmethod defun)))
- (push (list (car def) (cadr def)) acc))
- finally (return (nreverse acc))))
-~~~~
-
-Then we:
-
-~~~~ {.commonlisp}
-> (length *funcs*)
-27
-~~~~
-
-which is weird, because counting them by hand (for the purpose of
-e.g. determining the number of lines occupied by each function) yields
-no less than 30; which actually makes sense, because some of the
-definitions have a "#+lispworks" in front of them, which causes the CL
-reader to ignore them. So the first observation is that the
-Hunchentoot coad is full of [ifdefism][ifdef] of the lowest kind. And
-since I don't have a Lispworks and I'm not planning to support it, my
-branch of the Hunchentoot V tree will be devoid of any "#+lispworks",
-and ultimately of any "#+..." whatsoever.
-
-The second observation is that most methods -- notable exceptions
-being [process-connection](#pc) and [acceptor-status-message](#asm) --
-are actually quite small and easy to read; most of the [gnarl][gnarl]
-lies in heavy "systems code" that has to do corner-case handling for
-some particularity of e.g. the HTTP protocol, and in some cases even
-that could be refactored into something sane. And now without further
-ado, we dive straight into the function/method definitions. Note the
-organization: <anchor> <function name with link to source
-code>: <description>, which should help the reader to more
-easily navigate this maze of references.
-
-<a name="ddd" href="#ddd">[ddd]</a>
-[**default-document-directory**][ht-ddd]: Function for finding the
-"default document root", i.e. the "www/" directory in the Hunchentoot
-coad base. This directory contains a test page and the pre-generated
-Hunchentoot documentation (otherwise found in "doc"). I'm not
-convinced this is really needed, other than perhaps for a quick test
-of new Hunchentoot installations. Then again, any other filesystem
-path that the operator desires could be set as a document root for
-that purpose.
-
-<a name="po" href="#po">[po]</a> [**print-object**][ht-po]:
-Implementation for the printing method, called when e.g. "print" is
-called on an acceptor object.
-
-<a name="ii" href="#ii">[ii]</a> [**initialize-instance**][ht-ii]:
-method for initializing acceptor objects, called when "make-instance"
-is called. Note the ":after" method qualifier, specifying that this
-implementation of initialize-instance is called after that of the
-superclass.
-
-This defmethod: a. sets the [persistent-connections-p][ht-pcp] field
-to nil for single-threaded taskmasters; and b. sets the document root
-and error template directory to their so-called [physical
-pathname][physical-pathname]. In particular (b) allows using so-called
-"[logical pathnames][logical-pathname]", which I'll admit I never
-used, but which supposedly aim to implement a sort of pseudo-GNS for
-refering to files using filesystem-agnostic resource locators.
-
-<a name="s" href="#s">[s]</a> [**start**][ht-s]: a. sets "self"
-acceptor as the acceptor of its current taskmaster (why isn't this
-being done in initialize-instance?); b. calls [start-listening](#sl);
-c. calls the taskmaster's execute-acceptor.
-
-<a name="s2" href="#s2">[s2]</a> [**stop**][ht-s2]: a. sets
-[acceptor-shutdown-p][ht-asp] to true; b. calls
-[wake-acceptor-for-shutdown][ht-wafs]; c. when "soft" is set to true,
-waits on the [shutdown-queue][ht-sq] condition variable for the next
-outstanding request to be served; d. calls the taskmaster's shutdown
-method; e. closes and loses the reference to the acceptor's listening
-socket, thus allowing it to be garbage collected.
-
-Note on (c): while I'm not sure this "soft shutdown" mechanism is of
-much use at all[^1], we observe that it escapes the problem of
-indefinite postponment by waiting on only the *first* in-progress
-request to be served. That is, if the server wanted to play really
-nicely, it would wait on that condition variable until
-[requests-in-progress][ht-rip] went down to zero; however, in this
-case some really mean fucker who does not want to let us finish could
-have generated requests ad infinitum, or in any case, until the next
-power outage. Fortunately, whoever designed this wasn't that naive.
-
-<a name="wafs" href="#wafs">[wafs]</a>
-[**wake-acceptor-for-shutdown**][ht-wafs]: "Creates a dummy connection
-to the acceptor, waking [accept-connections][ht-ac] while it is
-waiting. This is supposed to force a check of
-[acceptor-shutdown-p][ht-asp]." And you have to admit, that's a pretty
-weird way of forcing the check too, but it works; anyway, a much
-simpler approach than using [CFFI and epoll][btcbase-1911362].
-
-<a name="ics" href="#ics">[ics]</a>
-[**initialize-connection-stream**][ht-ics]: By default it returns the
-stream object as it was passed to it (and naught else), but
-sub-acceptors can override/extend this behaviour. Off the top of my
-head I'm not sure why I'd need to implement a sub-method of this
-myself, for now it's just that the "SSL acceptor" (which is well on
-the way to extinction) uses it to do SSListic stuff.
-
-<a name="rcs" href="#rcs">[rcs]</a>
-[**reset-connection-stream**][ht-rcs]: "Resets the stream which is
-used to communicate between client and server after one request has
-been served so that it can be used to process the next request." We're
-not told what this "reset" means more exactly, so we're stuck trying
-to reverse it from the context. Note that similarly to the previous
-method, this can be extended by users to perform various actions on
-streams; only it's not called at the very beginning of a connection,
-but after each request.
-
-Our reset-connection-stream function is called from
-[process-connection][ht-pc] after processing the current request and
-flushing the stream (why is "flushing" a separate operation from
-"resetting" anyway? no idea). One would expect thusly that "resetting"
-would have something to do with "request processing", only there's no
-apparent relation between the two: the method implementation only
-turns the connection stream from a "chunked stream" (assuming
-chunking[^2] is enabled, which it is, by default) to a "non-chunked"
-(regular) socket stream. I'm not sure exactly why this is done at this
-point, since chunking is later re-enabled either by the server and/or
-client upon response/request delivery.
-
-<a name="dwarci" href="#dwarci">[dwarci]</a>
-[**do-with-acceptor-request-count-incremented**][ht-dwarci]:
-a. atomically increments [requests-in-progress][ht-rip]; b. calls a
-function received as a parameter; c. decrements requests-in-progress;
-d. if [acceptor-shutdown-p][ht-asp] is set, then it signals a wake-up
-through the [shutdown-queue][ht-sq].
-
-This is a reference-counting thing used to keep track of the number of
-requests in the progress of being served at any given time. It's used
-exclusively in conjunction with process-request.
-
-<a name="warci" href="#warci">[warci]</a>
-[**with-acceptor-request-count-incremented**][ht-warci]: Wrapper
-around the [previous function](#dwarci), which conveniently allows
-creating a context for executing code with said fields incremented.
-
-<a name="amr" href="#amr">[amr]</a>
-[**acceptor-make-request**][ht-amr]: Given all request parameters
-(headers, "content stream", HTTP method, URI and HTTP protocol
-version), plus the remote and local addresses taken from the socket,
-instantiates a new request object.
-
-<a name="ds" href="#ds">[ds]</a> [**detach-socket**][ht-ds]: This
-isn't called from, nor subclassed, anywhere. Judging by the fact that
-it sets [\*finish-processing-socket\*][ht-fps] (to true) and
-[\*close-hunchentoot-stream\*][ht-chs] (to nil), it's apparent that it
-signals to the (calling) [process-connection][ht-pc] that it should
-stop doing any further processing on the socket of the current
-connection and "abandon" it, i.e. hand over control over it to the
-user.
-
-If there is an honest use case for this, I don't know it yet.
-
-<a name="pc" href="#pc">[pc]</a> [**process-connection**][ht-pc]: This
-is a fucking huge beast, moreso that it calls most of the methods and
-functions defined above. Let's look at it.
-
-There's also an ":around"-qualified method defined at [line
-363][ht-pc-a], which does nothing but wrap a call-next-method call in
-[a context][ht-wccal] that ensures that all errors and warnings are
-logged by the server. Thus all other method implementations, including
-the one at [427][ht-pc] are run in this error handling context.
-
-As for the [main implementation][ht-pc], it: a. takes the current
-acceptor socket stream; b. [initializes it](#ics); then c. runs a
-request processing loop (which we'll get to immediately); and d. if
-\*close-hunchentoot-stream\* is set, then it flushes and closes the
-socket stream under processing.
-
-As for the processing [loop][ht-pc-loop], it: 1. checks whether it
-needs to [shut down][ht-asp], in which case it ends; 2. calls
-[get-request-data][ht-grd][^3], returning headers, URL, method and
-protocol version; 3. checks that there is a valid method; 4. does some
-gnarly handling for "transfer encodings", i.e. chunking, then
-5. [with-acceptor-request-count-incremented](#warci) it calls
-process-request on a new request generated using
-[acceptor-make-request](#amr); 6. it flushes the output stream;
-7. [resets](#rcs) it; and then finally, 8. if
-\*finish-processing-socket\* is set, it returns.
-
-If you've been following closely, you'll see that the thing only looks
-horrible and that it otherwise makes sense for the most part, plus all
-the complexity involved in chunking, tracking request counts and the
-"socket abandoning" [described above](#ds). All in all, a good
-candidate for refactoring, altough to be honest, if you wake me up
-tonight and ask me to do that, I won't know where to start.
-
-<a name="asp2" href="#asp2">[asp2]</a> [**acceptor-ssl-p**][ht-asp2]:
-This one's well on the way [out][https].
-
-<a name="ala" href="#ala">[ala]</a> [**acceptor-log-access**][ht-ala]:
-logs page accesses. Called upon sending a [response][ht-sr-la] to the
-client.
-
-<a name="alm" href="#alm">[alm]</a>
-[**acceptor-log-message**][ht-alm]: Similar to the previous function,
-only it logs to [message-log-destination][ht-mld]. If you're familiar
-with Apache, then these are very similar to the AccessLog and ErrorLog
-facilities. Called from the taskmaster and from the following wrapper
-function.
-
-<a name="lms" href="#lms">[lms]</a> [**log-message\***][ht-lms]:
-Convenient wrapper for the previous method, that logs for the
-currently bound \*acceptor\* special variable. This is the message
-logger used all around the Hunchentoot code.
-
-<a name="sl" href="#sl">[sl]</a> [**start-listening**][ht-sl]: When
-the given acceptor isn't listening, create a new listening socket;
-call usocket's [socket-listen][usocket-sl], which does all the binding
-etc.
-
-An ":after" method is also implemented, which sets the acceptor's
-[port][ht-ap] if it was set to zero, i.e. it was allocated by the
-operating system and at that point it's setting its actual value.
-
-<a name="ac" href="#ac">[ac]</a> [**accept-connections**][ht-ac]: In a
-loop: a. if [acceptor-shutdown-p](#asp) is set, then return; otherwise
-b. [wait-for-input][usocket-wfi], then c. [socket-accept][usocket-sa]
-on the listening socket; d. [set-timeouts][ht-st] for new socket;
-e. schedule connection handling for the new socket via taskmaster's
-handle-incoming-connection.
-
-accept-connections is called by the taskmaster on execute-acceptor.
-
-<a name="adp" href="#adp">[adp]</a>
-[**acceptor-dispatch-request**][ht-adp]: As [previously
-discussed][adp-htiii]: a. if the [document-root][ht-dr] exists, then a
-static file is served; otherwise, b. a HTTP not-found is returned.
-
-In particular, (a) calls [handle-static-file][ht-hsf]; let's look at
-this one in more detail: it 1. checks that the file exists (using
-[FAD][cl-fad]) and replies with a HTTP not-found if it doesn't find
-it; 2. sets the content-type using
-[mime-type][ht-mt][^4]; 3. [checks][ht-hims] whether the file has been
-modified since some particular time specified in the
-request; 4. attempts to handle the [range][ht-mhrh] header, if it
-exists; 5. sends the headers; 6. in a loop, sends chunks of data while
-there are bytes available to send; and 7. flushes the output stream
-before exiting.
-
-In particular, (b) calls [abort-request-handler][ht-arh], which
-transfers control to [handler-done][ht-hd], i.e. abruptly ends request
-handling and sends the reply down the output stream.
-
-<a name="hr" href="#hr">[hr]</a> [**handle-request**][ht-hr]: called
-from within the "[request processing][ht-pr]" context; it wraps
-[acceptor-dispatch-request](#adp) in a condition catcher that recovers
-from any errors that might occur in the process of handling requests.
-
-<a name="asm" href="#asm">[asm]</a>
-[**acceptor-status-message**][ht-asm]: Used by Hunchentoot to
-"compose" "status replies", e.g. 40x pages, served from
-e.g. [process-request][ht-pr]. This one's another beast, so I'm
-breaking it into as-small-as-possible pieces to ingurgitate. There's
-more than one definition for this method: the main one
-([736][ht-asm]), a "default" one ([721][ht-asm2]) and an
-":around"-qualified one ([724][ht-asm3]) -- let's take them one by
-one.
-
-The ":around" [method][ht-asm3] runs the first thing when
-acceptor-status-message is called; it calls the next method, wrapped
-in an error handling context that logs the error and falls back to
-[make-cooked-message](#mcm).
-
-The "default" [method][ht-asm2] is the one that gets called when all
-other calls fail -- notice the "(acceptor t)" specialization, t being
-the [mother of all types][hs-t]. Similarly to the error handling case
-above, this one calls [make-cooked-message](#mcm).
-
-The main [method][ht-asm]: a. defines some local procedures (which
-we'll examine below); b. ignores status codes smaller than 300[^5];
-and c. it calls one of the local procedures,
-error-contents-from-template, which tests whether a file "xxx.html"
-(where xxx is the status code) exists[^6] in the
-[error-template-directory][ht-etd], and if the case, it serves it
-under a "request context variable" substitution. In order to serve the
-file, its contents are read using the file-contents local procedure
-and then the so-called variable substitution is performed using the
-substitute-request-context-variables local procedure.
-
-file-contents reads the entire contents of the file in a buffer using
-[read-sequence][hs-rs].
-
-substitute-request-context-variables looks for variables between curly
-braces preceded by a dollar, such as, say, ${script-name} and then
-substitutes them according to the contents of "properties". Not sure
-it's obvious by now, but I believe this ad-hoc "context variable
-substitution" to be a terrible idea. I genesized [cl-who][cl-who] for
-precisely this type of thing and I'm not going to have it replaced by
-some half-assed pseudo-solution concocted by Dorel in one of his
-sleepless nights[^7].
-
-Anyway, from the acceptor-status-message series, we're left with:
-
-<a name="mcm" href="#mcm">[mcm]</a> [**make-cooked-message**][ht-mcm]:
-Given a HTTP status code and optional Lisp error and backtrace
-objects, it formats an Apache-style error page containing a short
-description of the status code. The bulk of this formatting occurs in
-the cooked-message local procedure, while the function body matches
-the HTTP code to the description message and calls cooked-message.
-
-<a name="sak" href="#sak">[sak]</a> [**string-as-keyword**][ht-sak]:
-Used by acceptor-status-message to convert a string (denoting the name
-of something) into a symbol from the [keyword package][hs-keyword].
-
-<a name="ars" href="#ars">[ars]</a>
-[**acceptor-remove-session**][ht-ars]: Used downstream for session
-cleanup, in [remove-session][ht-rs] and, I suspect, in session
-management code written by the user. At this level, the implementation
-doesn't do anything so it must exist mainly to be subclassed by users.
-
-<a name="asn" href="#asn">[asn]</a>
-[**acceptor-server-name**][ht-asn]: "Returns a string which can be
-used for 'Server' headers." Clear enough from where I'm standing.
-
-This is it for now. I only have three other fundamental Hunchentoot
-pieces to dissect in this manner, plus, as can be observed here, some
-of the auxiliary code.
-
-Meta: given that proper in-blog comments are for now [still
-missing][btcbase-1925065], readers are invited to leave them in
-[#spyked][contact] or (assuming they know where they're heading)
-#trilema. Existing comments are preserved here in [^8].
-
-[^1]: Why would I ever want to, of all things, have the server wait
- for arbitrary *clients* to get served while, of all things,
- shutting down? The way I see it, when the operator sends the
- "stop" command to the server, the latter should get to it without
- any delay, regardless of what happens to clients' connections or
- requests.
-
- What does the reader think about this?
-
-[^2]: "Chunking" is a... let's say feature of [HTTP/1.1][http-11]
- which allows dividing a request and/or a response into so-called
- "chunks". Yes, you read that correctly.
-
- So the first set of fuckers building the Internet came with this
- notion of "Maximum Transmissible Unit" (MTU), where a "unit" is a
- layer 3, i.e. IP packet; and so at the third (Internet) layer,
- packets are given lengths that must fit into this MTU, otherwise
- routers will fragment them into smaller pieces or drop them or
- whatever the fuck it is they do. Yes, a large subset of the IP
- spec deals with precisely this fragmentation thing.
-
- Now, as if this wasn't enough, [TCP][tcp] also has a (transport
- layer) segment size, which must fit into a so-called "Maximum
- Segment Size" (MSS), which must be smaller than the MTU, because
- we also need to fit lower-layer headers and all that. Otherwise
- TCP isn't concerned too much with this, but misconfiguration *can*
- cause problems with congestion windows and whatnot, and we sure as
- hell don't want this shit to blow up.
-
- Finally, as if the fuckers who designed the L3 shit and the ones
- who specced the L4 shit didn't add enough, here come the L7 idiots
- who, not being satisfied with a "file transfer protocol" decide to
- "support" file transfers over HTTP; and since files may be as
- large as, say, 1TB, then yes, splitting them into small chunks is
- very much preferable to sending the whole thing right away.
-
- Which brings us to the following question: why would they want to
- add support for unknown-sized data to HTTP, of all things? What's
- wrong with defining a FToWP (File Transfer over Web Protocol) and
- using *that* for linking to divx porn or whatever. Yeah, it's I
- who's hatin', not them who decide to implement transfer for
- virtually everything within a protocol originally designed for
- delivering *hypertext*... right? Right.
-
-[^3]: In short, [get-request-data][ht-grd] parses the first HTTP
- request line, then calls [chunga][chunga]'s read-http-headers,
- does some [special handling][ietf] on said headers and finally, it
- returns all the values.
-
-[^4]: [mime-type][ht-mt] implements the MIME crap by holding a hash
- table of associations between "file types", i.e. filename
- extensions, and MIME [content types][rfc2045-ct].
-
- This approach is notoriously idiotic, as the extension of a file's
- name, that is, that thing that comes after the period -- assuming
- a period even exists and assuming one handles all the other
- idiocies such as "a period at the beginning of a filename denotes
- a hidden file, and not an extension marker" or "what's the
- difference between 'somefile' and 'somefile.'?"; and yes, Common
- Lisp does a pretty good job at handling all these, because
- e.g. `""` is different from `nil` -- has nothing to do with the
- content of said file. The fact that Microshits imagine this and
- then decide to force "nudepics.jpg.exe" down the throats of idiots
- has zilch to do with what we're trying to achieve here and this
- MIME thing is from this point of view identical, since a web
- server can lie all it wants about the nature of the content it's
- serving through the mere repopulation of this hash table.
-
- From where I'm looking, this then calls for a study into how
- browsers act upon the Content-Type header, which would I suppose
- inform some sane policy to allow clients to distinguish between, I
- suppose, binary, plain text and HTML data. Really, it's not my job
- to tell the client whether the "binary" he's getting is an image
- or an excel spreadsheet, might as well let 'im figure this out by
- 'imself already.
-
-[^5]: I don't know why it does that, or why acceptor-status-message
- would ever get executed for 1xx or 2xx codes. This will remain an
- exercise for later.
-
-[^6]: This time the test is performed using [probe-file][hs-pf]
- instead of the [FAD][cl-fad] code. Not much consistency there...
-
-[^7]: For the sake of posterity, and of preserving whatever heathen
- history I can, I'll leave here [the culprit][huebner-git], one
- self-proclaimed "hacker" Hans Hübner, who's been patching this
- Hunchentoot for over a decade now. Well, no more.
-
-[^8]: No comments yet.
-
-[ht-ddd]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L32
-[ht-ap]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L43
-[ht-pcp]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L94
-[ht-asp]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L130
-[ht-rip]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L134
-[ht-sq]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L138
-[ht-mld]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L154
-[ht-etd]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L161
-[ht-dr]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L169
-[ht-po]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L203
-[ht-ii]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L208
-[ht-s]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L310
-[ht-s2]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L318
-[ht-wafs]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L338
-[ht-ics]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L348
-[ht-rcs]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L352
-[ht-pc-a]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L363
-[ht-dwarci]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L370
-[ht-warci]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L380
-[ht-amr]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L388
-[ht-ds]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L423
-[ht-pc]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L427
-[ht-pc-loop]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L437
-[ht-asp2]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L486
-[ht-ala]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L498
-[ht-alm]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L527
-[ht-sl]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L552
-[ht-lms]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L541
-[ht-ac]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L570
-[ht-adp]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L628
-[ht-hr]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L642
-[ht-mcm]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L683
-[ht-asm2]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L721
-[ht-asm3]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L724
-[ht-sak]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L731
-[ht-asm]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L736
-[ht-ars]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L781
-[ht-asn]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L786
-[ht-fps]: http://coad.thetarpit.org/hunchentoot/c-specials.lisp.html#L143
-[ht-chs]: http://coad.thetarpit.org/hunchentoot/c-specials.lisp.html#L147
-[ht-grd]: http://coad.thetarpit.org/hunchentoot/c-headers.lisp.html#L241
-[ht-sr-la]: http://coad.thetarpit.org/hunchentoot/c-headers.lisp.html#L178
-[ht-st]: http://coad.thetarpit.org/hunchentoot/c-set-timeouts.lisp.html#L31
-[ht-mhrh]: http://coad.thetarpit.org/hunchentoot/c-misc.lisp.html#L123
-[ht-hsf]: http://coad.thetarpit.org/hunchentoot/c-misc.lisp.html#L151
-[ht-mt]: http://coad.thetarpit.org/hunchentoot/c-mime-types.lisp.html#L359
-[ht-hims]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L480
-[ht-arh]: http://coad.thetarpit.org/hunchentoot/c-misc.lisp.html#L117
-[ht-hd]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L240
-[ht-pr]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L219
-[ht-rs]: http://coad.thetarpit.org/hunchentoot/c-session.lisp.html#L266
-[ht-wccal]: http://coad.thetarpit.org/hunchentoot/c-util.lisp.html#L362
-[cl-www]: /posts/y05/090-tmsr-work-ii.html#selection-108.0-108.17
-[hunchentoot-i]: /posts/y05/093-hunchentoot-i.html
-[hunchentoot-ii]: /posts/y05/096-hunchentoot-ii.html
-[ave1]: http://ave1.org/2019/lisp-documenting-my-encounter-with-step-1/#comment-233
-[ifdef]: http://btcbase.org/log-search?q=ifdef
-[gnarl]: http://btcbase.org/log-search?q=gnarl
-[physical-pathname]: http://archive.is/LZIFg
-[logical-pathname]: http://archive.is/w9qP9
-[gns]: http://btcbase.org/log-search?q=gns
-[btcbase-1911362]: http://btcbase.org/log/2019-05-06#1911362
-[http-11]: http://archive.is/QOKNZ
-[tcp]: /posts/y05/096-hunchentoot-ii.html#fn1
-[chunga]: http://archive.is/hlVI5
-[ietf]: https://tools.ietf.org/html/rfc2616#section-14.20
-[https]: /posts/y03/05b-https-war-declaration.html
-[usocket-sl]: https://quickref.common-lisp.net/usocket.html#go-to-the-USOCKET_003ccolon_003e_003ccolon_003eSOCKET_002dLISTEN-function
-[usocket-wfi]: https://quickref.common-lisp.net/usocket.html#go-to-the-USOCKET_003ccolon_003e_003ccolon_003eWAIT_002dFOR_002dINPUT-function
-[usocket-sa]: https://quickref.common-lisp.net/usocket.html#index-Generic-Function_002c-socket_002daccept
-[adp-htiii]: /posts/y06/097-hunchentoot-iii.html#selection-121.147-133.16
-[cl-fad]: http://edicl.github.io/cl-fad/
-[rfc2045-ct]: https://tools.ietf.org/html/rfc2045#section-5.1
-[hs-t]: http://www.lispworks.com/documentation/HyperSpec/Body/t_t.htm#t
-[hs-pf]: http://clhs.lisp.se/Body/f_probe_.htm
-[hs-rs]: http://clhs.lisp.se/Body/f_rd_seq.htm
-[cl-who]: /posts/y05/092-cl-who.html
-[huebner-git]: http://archive.is/iRuVe
-[hs-keyword]: http://www.lispworks.com/documentation/HyperSpec/Body/11_abc.htm
-[btcbase-1925065]: http://btcbase.org/log/2019-07-26#1925065
-[contact]: http://webchat.freenode.net/?channels=#spyked&nick=from_thetarpit
--- /dev/null
+---
+postid: 098
+title: Hunchentoot: acceptor code review
+date: August 1, 2019
+author: Lucian Mogoșanu
+tags: tech, tmsr
+---
+
+This post is part of a series on [Common Lisp WWWism][cl-www], more
+specifically of [ongoing work][hunchentoot-i] to
+[understand][hunchentoot-ii] the web server known as Hunchentoot. Even
+more specifically, this post exhaustively covers each of the methods
+and functions implemented by the acceptor class.
+
+Let's begin with my own version of (ac)[counting][ave1] the functions
+in acceptor.lisp. First, we read all the top-level S-expressions in
+the file:
+
+~~~~ {.commonlisp}
+(defparameter *top-level-sexprs*
+ (with-open-file (in "acceptor.lisp")
+ (loop for sexpr = (read in nil :err)
+ until (eq sexpr :err)
+ collect sexpr)))
+~~~~
+
+and after fiddling around with fixing read-time weird encounters, we
+keep expressions that are lists beginning with "defmethod" or "defun":
+
+~~~~ {.commonlisp}
+(defparameter *funcs*
+ (loop for def in *top-level-sexprs*
+ with acc = nil do
+ (when (and (consp def) (member (car def) '(defmethod defun)))
+ (push (list (car def) (cadr def)) acc))
+ finally (return (nreverse acc))))
+~~~~
+
+Then we:
+
+~~~~ {.commonlisp}
+> (length *funcs*)
+27
+~~~~
+
+which is weird, because counting them by hand (for the purpose of
+e.g. determining the number of lines occupied by each function) yields
+no less than 30; which actually makes sense, because some of the
+definitions have a "#+lispworks" in front of them, which causes the CL
+reader to ignore them. So the first observation is that the
+Hunchentoot coad is full of [ifdefism][ifdef] of the lowest kind. And
+since I don't have a Lispworks and I'm not planning to support it, my
+branch of the Hunchentoot V tree will be devoid of any "#+lispworks",
+and ultimately of any "#+..." whatsoever.
+
+The second observation is that most methods -- notable exceptions
+being [process-connection](#pc) and [acceptor-status-message](#asm) --
+are actually quite small and easy to read; most of the [gnarl][gnarl]
+lies in heavy "systems code" that has to do corner-case handling for
+some particularity of e.g. the HTTP protocol, and in some cases even
+that could be refactored into something sane. And now without further
+ado, we dive straight into the function/method definitions. Note the
+organization: <anchor> <function name with link to source
+code>: <description>, which should help the reader to more
+easily navigate this maze of references.
+
+<a name="ddd" href="#ddd">[ddd]</a>
+[**default-document-directory**][ht-ddd]: Function for finding the
+"default document root", i.e. the "www/" directory in the Hunchentoot
+coad base. This directory contains a test page and the pre-generated
+Hunchentoot documentation (otherwise found in "doc"). I'm not
+convinced this is really needed, other than perhaps for a quick test
+of new Hunchentoot installations. Then again, any other filesystem
+path that the operator desires could be set as a document root for
+that purpose.
+
+<a name="po" href="#po">[po]</a> [**print-object**][ht-po]:
+Implementation for the printing method, called when e.g. "print" is
+called on an acceptor object.
+
+<a name="ii" href="#ii">[ii]</a> [**initialize-instance**][ht-ii]:
+method for initializing acceptor objects, called when "make-instance"
+is called. Note the ":after" method qualifier, specifying that this
+implementation of initialize-instance is called after that of the
+superclass.
+
+This defmethod: a. sets the [persistent-connections-p][ht-pcp] field
+to nil for single-threaded taskmasters; and b. sets the document root
+and error template directory to their so-called [physical
+pathname][physical-pathname]. In particular (b) allows using so-called
+"[logical pathnames][logical-pathname]", which I'll admit I never
+used, but which supposedly aim to implement a sort of pseudo-GNS for
+refering to files using filesystem-agnostic resource locators.
+
+<a name="s" href="#s">[s]</a> [**start**][ht-s]: a. sets "self"
+acceptor as the acceptor of its current taskmaster (why isn't this
+being done in initialize-instance?); b. calls [start-listening](#sl);
+c. calls the taskmaster's execute-acceptor.
+
+<a name="s2" href="#s2">[s2]</a> [**stop**][ht-s2]: a. sets
+[acceptor-shutdown-p][ht-asp] to true; b. calls
+[wake-acceptor-for-shutdown][ht-wafs]; c. when "soft" is set to true,
+waits on the [shutdown-queue][ht-sq] condition variable for the next
+outstanding request to be served; d. calls the taskmaster's shutdown
+method; e. closes and loses the reference to the acceptor's listening
+socket, thus allowing it to be garbage collected.
+
+Note on (c): while I'm not sure this "soft shutdown" mechanism is of
+much use at all[^1], we observe that it escapes the problem of
+indefinite postponment by waiting on only the *first* in-progress
+request to be served. That is, if the server wanted to play really
+nicely, it would wait on that condition variable until
+[requests-in-progress][ht-rip] went down to zero; however, in this
+case some really mean fucker who does not want to let us finish could
+have generated requests ad infinitum, or in any case, until the next
+power outage. Fortunately, whoever designed this wasn't that naive.
+
+<a name="wafs" href="#wafs">[wafs]</a>
+[**wake-acceptor-for-shutdown**][ht-wafs]: "Creates a dummy connection
+to the acceptor, waking [accept-connections][ht-ac] while it is
+waiting. This is supposed to force a check of
+[acceptor-shutdown-p][ht-asp]." And you have to admit, that's a pretty
+weird way of forcing the check too, but it works; anyway, a much
+simpler approach than using [CFFI and epoll][btcbase-1911362].
+
+<a name="ics" href="#ics">[ics]</a>
+[**initialize-connection-stream**][ht-ics]: By default it returns the
+stream object as it was passed to it (and naught else), but
+sub-acceptors can override/extend this behaviour. Off the top of my
+head I'm not sure why I'd need to implement a sub-method of this
+myself, for now it's just that the "SSL acceptor" (which is well on
+the way to extinction) uses it to do SSListic stuff.
+
+<a name="rcs" href="#rcs">[rcs]</a>
+[**reset-connection-stream**][ht-rcs]: "Resets the stream which is
+used to communicate between client and server after one request has
+been served so that it can be used to process the next request." We're
+not told what this "reset" means more exactly, so we're stuck trying
+to reverse it from the context. Note that similarly to the previous
+method, this can be extended by users to perform various actions on
+streams; only it's not called at the very beginning of a connection,
+but after each request.
+
+Our reset-connection-stream function is called from
+[process-connection][ht-pc] after processing the current request and
+flushing the stream (why is "flushing" a separate operation from
+"resetting" anyway? no idea). One would expect thusly that "resetting"
+would have something to do with "request processing", only there's no
+apparent relation between the two: the method implementation only
+turns the connection stream from a "chunked stream" (assuming
+chunking[^2] is enabled, which it is, by default) to a "non-chunked"
+(regular) socket stream. I'm not sure exactly why this is done at this
+point, since chunking is later re-enabled either by the server and/or
+client upon response/request delivery.
+
+<a name="dwarci" href="#dwarci">[dwarci]</a>
+[**do-with-acceptor-request-count-incremented**][ht-dwarci]:
+a. atomically increments [requests-in-progress][ht-rip]; b. calls a
+function received as a parameter; c. decrements requests-in-progress;
+d. if [acceptor-shutdown-p][ht-asp] is set, then it signals a wake-up
+through the [shutdown-queue][ht-sq].
+
+This is a reference-counting thing used to keep track of the number of
+requests in the progress of being served at any given time. It's used
+exclusively in conjunction with process-request.
+
+<a name="warci" href="#warci">[warci]</a>
+[**with-acceptor-request-count-incremented**][ht-warci]: Wrapper
+around the [previous function](#dwarci), which conveniently allows
+creating a context for executing code with said fields incremented.
+
+<a name="amr" href="#amr">[amr]</a>
+[**acceptor-make-request**][ht-amr]: Given all request parameters
+(headers, "content stream", HTTP method, URI and HTTP protocol
+version), plus the remote and local addresses taken from the socket,
+instantiates a new request object.
+
+<a name="ds" href="#ds">[ds]</a> [**detach-socket**][ht-ds]: This
+isn't called from, nor subclassed, anywhere. Judging by the fact that
+it sets [\*finish-processing-socket\*][ht-fps] (to true) and
+[\*close-hunchentoot-stream\*][ht-chs] (to nil), it's apparent that it
+signals to the (calling) [process-connection][ht-pc] that it should
+stop doing any further processing on the socket of the current
+connection and "abandon" it, i.e. hand over control over it to the
+user.
+
+If there is an honest use case for this, I don't know it yet.
+
+<a name="pc" href="#pc">[pc]</a> [**process-connection**][ht-pc]: This
+is a fucking huge beast, moreso that it calls most of the methods and
+functions defined above. Let's look at it.
+
+There's also an ":around"-qualified method defined at [line
+363][ht-pc-a], which does nothing but wrap a call-next-method call in
+[a context][ht-wccal] that ensures that all errors and warnings are
+logged by the server. Thus all other method implementations, including
+the one at [427][ht-pc] are run in this error handling context.
+
+As for the [main implementation][ht-pc], it: a. takes the current
+acceptor socket stream; b. [initializes it](#ics); then c. runs a
+request processing loop (which we'll get to immediately); and d. if
+\*close-hunchentoot-stream\* is set, then it flushes and closes the
+socket stream under processing.
+
+As for the processing [loop][ht-pc-loop], it: 1. checks whether it
+needs to [shut down][ht-asp], in which case it ends; 2. calls
+[get-request-data][ht-grd][^3], returning headers, URL, method and
+protocol version; 3. checks that there is a valid method; 4. does some
+gnarly handling for "transfer encodings", i.e. chunking, then
+5. [with-acceptor-request-count-incremented](#warci) it calls
+process-request on a new request generated using
+[acceptor-make-request](#amr); 6. it flushes the output stream;
+7. [resets](#rcs) it; and then finally, 8. if
+\*finish-processing-socket\* is set, it returns.
+
+If you've been following closely, you'll see that the thing only looks
+horrible and that it otherwise makes sense for the most part, plus all
+the complexity involved in chunking, tracking request counts and the
+"socket abandoning" [described above](#ds). All in all, a good
+candidate for refactoring, altough to be honest, if you wake me up
+tonight and ask me to do that, I won't know where to start.
+
+<a name="asp2" href="#asp2">[asp2]</a> [**acceptor-ssl-p**][ht-asp2]:
+This one's well on the way [out][https].
+
+<a name="ala" href="#ala">[ala]</a> [**acceptor-log-access**][ht-ala]:
+logs page accesses. Called upon sending a [response][ht-sr-la] to the
+client.
+
+<a name="alm" href="#alm">[alm]</a>
+[**acceptor-log-message**][ht-alm]: Similar to the previous function,
+only it logs to [message-log-destination][ht-mld]. If you're familiar
+with Apache, then these are very similar to the AccessLog and ErrorLog
+facilities. Called from the taskmaster and from the following wrapper
+function.
+
+<a name="lms" href="#lms">[lms]</a> [**log-message\***][ht-lms]:
+Convenient wrapper for the previous method, that logs for the
+currently bound \*acceptor\* special variable. This is the message
+logger used all around the Hunchentoot code.
+
+<a name="sl" href="#sl">[sl]</a> [**start-listening**][ht-sl]: When
+the given acceptor isn't listening, create a new listening socket;
+call usocket's [socket-listen][usocket-sl], which does all the binding
+etc.
+
+An ":after" method is also implemented, which sets the acceptor's
+[port][ht-ap] if it was set to zero, i.e. it was allocated by the
+operating system and at that point it's setting its actual value.
+
+<a name="ac" href="#ac">[ac]</a> [**accept-connections**][ht-ac]: In a
+loop: a. if [acceptor-shutdown-p](#asp) is set, then return; otherwise
+b. [wait-for-input][usocket-wfi], then c. [socket-accept][usocket-sa]
+on the listening socket; d. [set-timeouts][ht-st] for new socket;
+e. schedule connection handling for the new socket via taskmaster's
+handle-incoming-connection.
+
+accept-connections is called by the taskmaster on execute-acceptor.
+
+<a name="adp" href="#adp">[adp]</a>
+[**acceptor-dispatch-request**][ht-adp]: As [previously
+discussed][adp-htiii]: a. if the [document-root][ht-dr] exists, then a
+static file is served; otherwise, b. a HTTP not-found is returned.
+
+In particular, (a) calls [handle-static-file][ht-hsf]; let's look at
+this one in more detail: it 1. checks that the file exists (using
+[FAD][cl-fad]) and replies with a HTTP not-found if it doesn't find
+it; 2. sets the content-type using
+[mime-type][ht-mt][^4]; 3. [checks][ht-hims] whether the file has been
+modified since some particular time specified in the
+request; 4. attempts to handle the [range][ht-mhrh] header, if it
+exists; 5. sends the headers; 6. in a loop, sends chunks of data while
+there are bytes available to send; and 7. flushes the output stream
+before exiting.
+
+In particular, (b) calls [abort-request-handler][ht-arh], which
+transfers control to [handler-done][ht-hd], i.e. abruptly ends request
+handling and sends the reply down the output stream.
+
+<a name="hr" href="#hr">[hr]</a> [**handle-request**][ht-hr]: called
+from within the "[request processing][ht-pr]" context; it wraps
+[acceptor-dispatch-request](#adp) in a condition catcher that recovers
+from any errors that might occur in the process of handling requests.
+
+<a name="asm" href="#asm">[asm]</a>
+[**acceptor-status-message**][ht-asm]: Used by Hunchentoot to
+"compose" "status replies", e.g. 40x pages, served from
+e.g. [process-request][ht-pr]. This one's another beast, so I'm
+breaking it into as-small-as-possible pieces to ingurgitate. There's
+more than one definition for this method: the main one
+([736][ht-asm]), a "default" one ([721][ht-asm2]) and an
+":around"-qualified one ([724][ht-asm3]) -- let's take them one by
+one.
+
+The ":around" [method][ht-asm3] runs the first thing when
+acceptor-status-message is called; it calls the next method, wrapped
+in an error handling context that logs the error and falls back to
+[make-cooked-message](#mcm).
+
+The "default" [method][ht-asm2] is the one that gets called when all
+other calls fail -- notice the "(acceptor t)" specialization, t being
+the [mother of all types][hs-t]. Similarly to the error handling case
+above, this one calls [make-cooked-message](#mcm).
+
+The main [method][ht-asm]: a. defines some local procedures (which
+we'll examine below); b. ignores status codes smaller than 300[^5];
+and c. it calls one of the local procedures,
+error-contents-from-template, which tests whether a file "xxx.html"
+(where xxx is the status code) exists[^6] in the
+[error-template-directory][ht-etd], and if the case, it serves it
+under a "request context variable" substitution. In order to serve the
+file, its contents are read using the file-contents local procedure
+and then the so-called variable substitution is performed using the
+substitute-request-context-variables local procedure.
+
+file-contents reads the entire contents of the file in a buffer using
+[read-sequence][hs-rs].
+
+substitute-request-context-variables looks for variables between curly
+braces preceded by a dollar, such as, say, ${script-name} and then
+substitutes them according to the contents of "properties". Not sure
+it's obvious by now, but I believe this ad-hoc "context variable
+substitution" to be a terrible idea. I genesized [cl-who][cl-who] for
+precisely this type of thing and I'm not going to have it replaced by
+some half-assed pseudo-solution concocted by Dorel in one of his
+sleepless nights[^7].
+
+Anyway, from the acceptor-status-message series, we're left with:
+
+<a name="mcm" href="#mcm">[mcm]</a> [**make-cooked-message**][ht-mcm]:
+Given a HTTP status code and optional Lisp error and backtrace
+objects, it formats an Apache-style error page containing a short
+description of the status code. The bulk of this formatting occurs in
+the cooked-message local procedure, while the function body matches
+the HTTP code to the description message and calls cooked-message.
+
+<a name="sak" href="#sak">[sak]</a> [**string-as-keyword**][ht-sak]:
+Used by acceptor-status-message to convert a string (denoting the name
+of something) into a symbol from the [keyword package][hs-keyword].
+
+<a name="ars" href="#ars">[ars]</a>
+[**acceptor-remove-session**][ht-ars]: Used downstream for session
+cleanup, in [remove-session][ht-rs] and, I suspect, in session
+management code written by the user. At this level, the implementation
+doesn't do anything so it must exist mainly to be subclassed by users.
+
+<a name="asn" href="#asn">[asn]</a>
+[**acceptor-server-name**][ht-asn]: "Returns a string which can be
+used for 'Server' headers." Clear enough from where I'm standing.
+
+This is it for now. I only have three other fundamental Hunchentoot
+pieces to dissect in this manner, plus, as can be observed here, some
+of the auxiliary code.
+
+Meta: given that proper in-blog comments are for now [still
+missing][btcbase-1925065], readers are invited to leave them in
+[#spyked][contact] or (assuming they know where they're heading)
+#trilema. Existing comments are preserved here in [^8].
+
+[^1]: Why would I ever want to, of all things, have the server wait
+ for arbitrary *clients* to get served while, of all things,
+ shutting down? The way I see it, when the operator sends the
+ "stop" command to the server, the latter should get to it without
+ any delay, regardless of what happens to clients' connections or
+ requests.
+
+ What does the reader think about this?
+
+[^2]: "Chunking" is a... let's say feature of [HTTP/1.1][http-11]
+ which allows dividing a request and/or a response into so-called
+ "chunks". Yes, you read that correctly.
+
+ So the first set of fuckers building the Internet came with this
+ notion of "Maximum Transmissible Unit" (MTU), where a "unit" is a
+ layer 3, i.e. IP packet; and so at the third (Internet) layer,
+ packets are given lengths that must fit into this MTU, otherwise
+ routers will fragment them into smaller pieces or drop them or
+ whatever the fuck it is they do. Yes, a large subset of the IP
+ spec deals with precisely this fragmentation thing.
+
+ Now, as if this wasn't enough, [TCP][tcp] also has a (transport
+ layer) segment size, which must fit into a so-called "Maximum
+ Segment Size" (MSS), which must be smaller than the MTU, because
+ we also need to fit lower-layer headers and all that. Otherwise
+ TCP isn't concerned too much with this, but misconfiguration *can*
+ cause problems with congestion windows and whatnot, and we sure as
+ hell don't want this shit to blow up.
+
+ Finally, as if the fuckers who designed the L3 shit and the ones
+ who specced the L4 shit didn't add enough, here come the L7 idiots
+ who, not being satisfied with a "file transfer protocol" decide to
+ "support" file transfers over HTTP; and since files may be as
+ large as, say, 1TB, then yes, splitting them into small chunks is
+ very much preferable to sending the whole thing right away.
+
+ Which brings us to the following question: why would they want to
+ add support for unknown-sized data to HTTP, of all things? What's
+ wrong with defining a FToWP (File Transfer over Web Protocol) and
+ using *that* for linking to divx porn or whatever. Yeah, it's I
+ who's hatin', not they who decide to implement transfer for
+ virtually everything within a protocol originally designed for
+ delivering *hypertext*... right? Right.
+
+[^3]: In short, [get-request-data][ht-grd] parses the first HTTP
+ request line, then calls [chunga][chunga]'s read-http-headers,
+ does some [special handling][ietf] on said headers and finally, it
+ returns all the values.
+
+[^4]: [mime-type][ht-mt] implements the MIME crap by holding a hash
+ table of associations between "file types", i.e. filename
+ extensions, and MIME [content types][rfc2045-ct].
+
+ This approach is notoriously idiotic, as the extension of a file's
+ name, that is, that thing that comes after the period -- assuming
+ a period even exists and assuming one handles all the other
+ idiocies such as "a period at the beginning of a filename denotes
+ a hidden file, and not an extension marker" or "what's the
+ difference between 'somefile' and 'somefile.'?"; and yes, Common
+ Lisp does a pretty good job at handling all these, because
+ e.g. `""` is different from `nil` -- has nothing to do with the
+ content of said file. The fact that Microshits imagine this and
+ then decide to force "nudepics.jpg.exe" down the throats of idiots
+ has zilch to do with what we're trying to achieve here and this
+ MIME thing is from this point of view identical, since a web
+ server can lie all it wants about the nature of the content it's
+ serving through the mere repopulation of this hash table.
+
+ From where I'm looking, this then calls for a study into how
+ browsers act upon the Content-Type header, which would I suppose
+ inform some sane policy to allow clients to distinguish between, I
+ suppose, binary, plain text and HTML data. Really, it's not my job
+ to tell the client whether the "binary" he's getting is an image
+ or an excel spreadsheet, might as well let 'im figure this out by
+ 'imself already.
+
+[^5]: I don't know why it does that, or why acceptor-status-message
+ would ever get executed for 1xx or 2xx codes. This will remain an
+ exercise for later.
+
+[^6]: This time the test is performed using [probe-file][hs-pf]
+ instead of the [FAD][cl-fad] code. Not much consistency there...
+
+[^7]: For the sake of posterity, and of preserving whatever heathen
+ history I can, I'll leave here [the culprit][huebner-git], one
+ self-proclaimed "hacker" Hans Hübner, who's been patching this
+ Hunchentoot for over a decade now. Well, no more.
+
+[^8]: No comments yet.
+
+[ht-ddd]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L32
+[ht-ap]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L43
+[ht-pcp]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L94
+[ht-asp]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L130
+[ht-rip]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L134
+[ht-sq]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L138
+[ht-mld]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L154
+[ht-etd]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L161
+[ht-dr]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L169
+[ht-po]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L203
+[ht-ii]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L208
+[ht-s]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L310
+[ht-s2]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L318
+[ht-wafs]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L338
+[ht-ics]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L348
+[ht-rcs]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L352
+[ht-pc-a]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L363
+[ht-dwarci]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L370
+[ht-warci]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L380
+[ht-amr]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L388
+[ht-ds]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L423
+[ht-pc]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L427
+[ht-pc-loop]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L437
+[ht-asp2]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L486
+[ht-ala]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L498
+[ht-alm]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L527
+[ht-sl]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L552
+[ht-lms]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L541
+[ht-ac]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L570
+[ht-adp]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L628
+[ht-hr]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L642
+[ht-mcm]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L683
+[ht-asm2]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L721
+[ht-asm3]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L724
+[ht-sak]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L731
+[ht-asm]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L736
+[ht-ars]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L781
+[ht-asn]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L786
+[ht-fps]: http://coad.thetarpit.org/hunchentoot/c-specials.lisp.html#L143
+[ht-chs]: http://coad.thetarpit.org/hunchentoot/c-specials.lisp.html#L147
+[ht-grd]: http://coad.thetarpit.org/hunchentoot/c-headers.lisp.html#L241
+[ht-sr-la]: http://coad.thetarpit.org/hunchentoot/c-headers.lisp.html#L178
+[ht-st]: http://coad.thetarpit.org/hunchentoot/c-set-timeouts.lisp.html#L31
+[ht-mhrh]: http://coad.thetarpit.org/hunchentoot/c-misc.lisp.html#L123
+[ht-hsf]: http://coad.thetarpit.org/hunchentoot/c-misc.lisp.html#L151
+[ht-mt]: http://coad.thetarpit.org/hunchentoot/c-mime-types.lisp.html#L359
+[ht-hims]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L480
+[ht-arh]: http://coad.thetarpit.org/hunchentoot/c-misc.lisp.html#L117
+[ht-hd]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L240
+[ht-pr]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L219
+[ht-rs]: http://coad.thetarpit.org/hunchentoot/c-session.lisp.html#L266
+[ht-wccal]: http://coad.thetarpit.org/hunchentoot/c-util.lisp.html#L362
+[cl-www]: /posts/y05/090-tmsr-work-ii.html#selection-108.0-108.17
+[hunchentoot-i]: /posts/y05/093-hunchentoot-i.html
+[hunchentoot-ii]: /posts/y05/096-hunchentoot-ii.html
+[ave1]: http://ave1.org/2019/lisp-documenting-my-encounter-with-step-1/#comment-233
+[ifdef]: http://btcbase.org/log-search?q=ifdef
+[gnarl]: http://btcbase.org/log-search?q=gnarl
+[physical-pathname]: http://archive.is/LZIFg
+[logical-pathname]: http://archive.is/w9qP9
+[gns]: http://btcbase.org/log-search?q=gns
+[btcbase-1911362]: http://btcbase.org/log/2019-05-06#1911362
+[http-11]: http://archive.is/QOKNZ
+[tcp]: /posts/y05/096-hunchentoot-ii.html#fn1
+[chunga]: http://archive.is/hlVI5
+[ietf]: https://tools.ietf.org/html/rfc2616#section-14.20
+[https]: /posts/y03/05b-https-war-declaration.html
+[usocket-sl]: https://quickref.common-lisp.net/usocket.html#go-to-the-USOCKET_003ccolon_003e_003ccolon_003eSOCKET_002dLISTEN-function
+[usocket-wfi]: https://quickref.common-lisp.net/usocket.html#go-to-the-USOCKET_003ccolon_003e_003ccolon_003eWAIT_002dFOR_002dINPUT-function
+[usocket-sa]: https://quickref.common-lisp.net/usocket.html#index-Generic-Function_002c-socket_002daccept
+[adp-htiii]: /posts/y06/097-hunchentoot-iii.html#selection-121.147-133.16
+[cl-fad]: http://edicl.github.io/cl-fad/
+[rfc2045-ct]: https://tools.ietf.org/html/rfc2045#section-5.1
+[hs-t]: http://www.lispworks.com/documentation/HyperSpec/Body/t_t.htm#t
+[hs-pf]: http://clhs.lisp.se/Body/f_probe_.htm
+[hs-rs]: http://clhs.lisp.se/Body/f_rd_seq.htm
+[cl-who]: /posts/y05/092-cl-who.html
+[huebner-git]: http://archive.is/iRuVe
+[hs-keyword]: http://www.lispworks.com/documentation/HyperSpec/Body/11_abc.htm
+[btcbase-1925065]: http://btcbase.org/log/2019-07-26#1925065
+[contact]: http://webchat.freenode.net/?channels=#spyked&nick=from_thetarpit