From: Lucian Mogosanu Date: Thu, 1 Aug 2019 07:52:57 +0000 (+0300) Subject: Add full hunchentoot-iv draft X-Git-Tag: v0.11~20 X-Git-Url: https://git.mogosanu.ro/?a=commitdiff_plain;h=6679c6a6a6f513ea6e94c250d2d5cefa8ac63c61;p=thetarpit.git Add full hunchentoot-iv draft --- diff --git a/drafts/000-hunchentoot-iv.markdown b/drafts/000-hunchentoot-iv.markdown new file mode 100644 index 0000000..a97c656 --- /dev/null +++ b/drafts/000-hunchentoot-iv.markdown @@ -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: <anchor> <function name with link to source +code>: <description>, which should help the reader to more +easily navigate this maze of references. + +[ddd] +[**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. + +[po] [**print-object**][ht-po]: +Implementation for the printing method, called when e.g. "print" is +called on an acceptor object. + +[ii] [**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. + +[s] [**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. + +[s2] [**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. + +[wafs] +[**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]. + +[ics] +[**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. + +[rcs] +[**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. + +[dwarci] +[**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. + +[warci] +[**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. + +[amr] +[**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. + +[ds] [**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. + +[pc] [**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. + +[asp2] [**acceptor-ssl-p**][ht-asp2]: +This one's well on the way [out][https]. + +[ala] [**acceptor-log-access**][ht-ala]: +logs page accesses. Called upon sending a [response][ht-sr-la] to the +client. + +[alm] +[**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. + +[lms] [**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. + +[sl] [**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. + +[ac] [**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. + +[adp] +[**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. + +[hr] [**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. + +[asm] +[**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: + +[mcm] [**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. + +[sak] [**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]. + +[ars] +[**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. + +[asn] +[**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