Add full hunchentoot-iv draft
authorLucian Mogosanu <lucian@mogosanu.ro>
Thu, 1 Aug 2019 07:52:57 +0000 (10:52 +0300)
committerLucian Mogosanu <lucian@mogosanu.ro>
Thu, 1 Aug 2019 07:52:57 +0000 (10:52 +0300)
drafts/000-hunchentoot-iv.markdown [new file with mode: 0644]

diff --git a/drafts/000-hunchentoot-iv.markdown b/drafts/000-hunchentoot-iv.markdown
new file mode 100644 (file)
index 0000000..a97c656
--- /dev/null
@@ -0,0 +1,530 @@
+---
+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: &lt;anchor&gt; &lt;function name with link to source
+code&gt;: &lt;description&gt;, 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