From 334754e1d6356c38d5ff6f6e2ec7ead91bfa3668 Mon Sep 17 00:00:00 2001 From: Lucian Mogosanu Date: Thu, 1 Aug 2019 18:14:34 +0300 Subject: [PATCH] posts: 098 --- drafts/000-hunchentoot-iv.markdown | 530 --------------------------------- posts/y06/098-hunchentoot-iv.markdown | 530 +++++++++++++++++++++++++++++++++ 2 files changed, 530 insertions(+), 530 deletions(-) delete mode 100644 drafts/000-hunchentoot-iv.markdown create mode 100644 posts/y06/098-hunchentoot-iv.markdown diff --git a/drafts/000-hunchentoot-iv.markdown b/drafts/000-hunchentoot-iv.markdown deleted file mode 100644 index a97c656..0000000 --- a/drafts/000-hunchentoot-iv.markdown +++ /dev/null @@ -1,530 +0,0 @@ ---- -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 diff --git a/posts/y06/098-hunchentoot-iv.markdown b/posts/y06/098-hunchentoot-iv.markdown new file mode 100644 index 0000000..ea145ff --- /dev/null +++ b/posts/y06/098-hunchentoot-iv.markdown @@ -0,0 +1,530 @@ +--- +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. + +[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 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 -- 1.7.10.4