+++ /dev/null
----
-postid: 000
-title: Hunchentoot: requests and replies [b]
-date: August 30, 2019
-author: Lucian Mogoșanu
-tags: tech, tmsr
----
-
-This post is part of a series on [Common Lisp WWWism][cl-www], more
-specifically a continuation of "[Hunchentoot: requests and
-replies][hunchentoot-via]". In this second part of "requests and
-replies" we will dissect "replies" and what remains of "requests", in
-precisely the reverse order.
-
-We ended [last time][hunchentoot-via] with a look at code that parses
-GET and POST requests. This time we will look (in the following order)
-at: a set of "interface methods" aimed to be employed by the user when
-implementing request handlers; the [process-request](#pr) method; and,
-last but not least, at the methods exposed by the "reply" component in
-this orchestra.
-
-First, we notice that most request accessors come with wrapper methods
-whose names end in a "star", which, by some arbitrary convention,
-means that they implicitly bind their current "request" parameter to
-the special variable \*request\*[^1]. Let's take a look at a few of
-these:
-
-<a name="sns" href="#sns">[sns]</a> [**script-name\***][ht-sns]
-
-<a name="qss" href="#qss">[qss]</a> [**query-string\***][ht-qss]
-
-<a name="gps" href="#gps">[gps]</a> [**get-parameters\***][ht-gps]
-
-<a name="his" href="#his">[his]</a> [**headers-in\***][ht-his]
-
-<a name="cis" href="#cis">[cis]</a> [**cookies-in\***][ht-cis]
-
-<a name="ras" href="#ras">[ras]</a> [**remote-addr\***][ht-ras]
-
-<a name="rps" href="#rps">[rps]</a> [**remote-port\***][ht-rps]
-
-<a name="las" href="#las">[las]</a> [**local-addr\***][ht-las]
-
-<a name="lps" href="#lps">[lps]</a> [**local-port\***][ht-lps]
-
-<a name="rus" href="#rus">[rus]</a> [**request-uri\***][ht-rus]
-
-<a name="rms" href="#rms">[rms]</a> [**request-method\***][ht-rms]
-
-<a name="sps" href="#sps">[sps]</a> [**server-protocol\***][ht-sps]
-
-The implementation of these functions is, as can be readily observed,
-trivial, and the meaning should be easily deducible from the name;
-thus I won't bother the reader with redundant details. However,
-there's also:
-
-<a name="pps" href="#pps">[pps]</a> [**post-parameters\***][ht-pps]:
-This one calls [post-parameters](#pp), which:
-
-<a name="pp" href="#pp">[pp]</a> [**post-parameters**][ht-pp]: Running
-":before" more specific implementations of this method are called;
-given a request, it calls [maybe-read-post-parameters][ht-mrpp]. If
-the post-parameters [slot][ht-pp-slot] is nil, then set the force
-parameter to true.
-
-Similarly:
-
-<a name="rrp" href="#rrp">[rrp]</a>
-[**recompute-request-parameters**][ht-rrp]: Calls
-[maybe-read-post-parameters][ht-mrpp] with the force argument always
-set to true. Also, set the [get-parameters][ht-gp-slot] to the
-[re-parsed][ht-fuelta] value of [query-string][ht-qs-slot].
-
-However, we also have:
-
-<a name="hi" href="#hi">[hi]</a> [**header-in**][ht-hi]: Gets a
-specific header from the [headers-in][ht-hi-slot] slot.
-
-<a name="his2" href="#his2">[his2]</a> [**header-in\***][ht-his2]: The
-same as [header-in](#hi), with the request parameter bound to
-\*request\*.
-
-And there's also these methods which dynamically bind \*request\*,
-although their name doesn't end in "star"[^2]:
-
-<a name="a" href="#a">[a]</a> [**authorization**][ht-a]: Reads the
-"authorization" header and tries to parse the user/password
-combination associated included, if they exist. Notice the magic
-number 5 used in this shitty piece of coad, because writing a proper
-parser for yet-another-set-of-ad-hoc-encoded-parameters is too much
-work.
-
-<a name="rra" href="#rra">[rra]</a> [**real-remote-addr**][ht-rra]:
-Reads the "X-Forwarded-For" header and, if it exists, it parses the
-client and proxy address fields and returns them. Apparently there's
-no "official spec" to be found for this, apparently the whole thing's
-superseded by some other syntax specified in [some
-RFC][rfc-7239]. Hunchentoot doesn't parse this one, however,
-so... well!
-
-<a name="h" href="#h">[h]</a> [**host**][ht-h]: Reads the "Host"
-header.
-
-<a name="ua" href="#ua">[ua]</a> [**user-agent**][ht-ua]: Reads the
-"User-Agent" header.
-
-<a name="ci" href="#ci">[ci]</a> [**cookie-in**][ht-ci]: Looks up the
-cookie with the given name in the [cookies][ht-ci-slot] slot.
-
-<a name="r" href="#r">[r]</a> [**referer**][ht-r]: Reads the "Referer"
-header.
-
-<a name="gp" href="#gp">[gp]</a> [**get-parameter**][ht-gp]: Looks up
-a specific GET parameter in [get-parameters][ht-gp-slot].
-
-<a name="pp2" href="#pp2">[pp2]</a> [**post-parameter**][ht-pp2]:
-Looks up a specific POST parameter in the
-[post-parameters][ht-pp-slot] slot.
-
-<a name="p" href="#p">[p]</a> [**parameter**][ht-p]: Looks up a GET
-parameter, and, if not found, a POST parameter with a specific name.
-
-<a name="hims"
-href="#hims">[hims]</a> [**handle-if-modified-since**][ht-hims]: Reads
-the "If-Modified-Since" header and compares it with a given time
-argument. If the two dates match, then a. set the reply as follows:
-a1. the content-length to nil; a2. remove the content-length header;
-a3. the return-code to http-not-modified; then
-b. [abort-request-handler][ht-arh].
-
-<a name="rpd" href="#rpd">[rpd]</a> [**raw-post-data**][ht-rpd]:
-a\. First, try to set an external-format based on the values of
-arguments force-binary, external-format and force-text; if no
-external-format is set and force-binary is not set, then try getting
-one from [external-format-from-content-type][ht-effct], and if that
-fails, fall back to \*hunchentoot-default-external-format\*.
-
-b\. Get the [raw-post-data][ht-rpd-slot] slot; if not set, then get
-post data by calling [get-post-data][ht-gpd]. Given a local binding to
-raw-post-data, the return value is determined based, in order, on the
-following conditions: b1. if raw-post-data is a stream, then return
-it; else b2. if raw-post-data is t or nil, return nil; else b3. if
-external-format was set, [octets-to-string][flex-ots] on raw-post-data
-and return the result; otherwise b4. return raw-post-data as-is.
-
-<a name="arv" href="#arv">[arv]</a> [**aux-request-value**][ht-arv]:
-If the given request is non-nil, then look up a value in the alist
-given by the [aux-data][ht-ad-slot] slot. Return, as multiple values,
-both the value and the symbol-value pair itself.
-
-There's also a [defsetf][ht-arv2] defined for this function, which
-looks up the value and a. if it exists, sets the new value, and b. if
-it doesn't, pushes a new symbol-value pair to [aux-data][ht-ad-slot].
-
-<a name="darv"
-href="#darv">[darv]</a> [**delete-aux-request-value**][ht-darv]:
-Similarly to [aux-request-value](#arv), operates on
-[aux-data][ht-ad-slot]; this one deletes a symbol-value binding from
-the alist.
-
-<a name="rp" href="#rp">[rp]</a> [**request-pathname**][ht-rp]: Given
-a request, a. get its [script-name][ht-sn-slot]; and
-b. [parse-path](#pp3). If drop-prefix is set to a string representing
-a path prefix, then the pathname is returned sans said prefix.
-
-<a name="pp3" href="#pp3">[pp3]</a> [**parse-path**][ht-pp3]:
-Sanitization function used by [request-pathname](#rp) to bring a
-path-string to the proper abstraction level, i.e. a Common Lisp
-pathname. After a. parsing the namestring using either
-[parse-namestring][clhs-parse-namestring] or some
-implementation-specific function, e.g. SBCL's
-[parse-native-namestring][sbcl-nf], run the following checks:
-
-b\. the host field of the pathname is nil, or it equals that of
-\*default-pathname-defaults\*; c\. the device field of the pathname is
-nil, or it equals that of \*default-pathname-defaults\*; d\. the
-directory component is nil, or it's a relative pathname without :up
-and :wild; e\. the pathname name and type fields are either nil or
-strings; f\. the namestring isn't "..".
-
-g\. When (b)-(f) are satisfied, return the parsed pathname.
-
-Since all these functions bind at least one of their arguments to
-\*request\*, we also have:
-
-<a name="wrp" href="#wrp">[wrp]</a> [**within-request-p**][ht-wrp]:
-When \*request\* is bound, return it.
-
-And finally:
-
-<a name="pr" href="#pr">[pr]</a> [**process-request**][ht-pr]: This
-function is on the main request processing/handling path, and is
-called by [process-connection][ht-pc] whenever a new HTTP request is
-available. It does a bunch of more or less related things, let's take
-them one by one.
-
-a\. the first part of the function a1. defines and binds some special
-variables, e.g. the current \*request\* being processed, and a2. wraps
-everything into some condition handling code, namely: a2i. it "maps"
-all [conditions][ht-wmc][^3]; a2ii. wraps this in an
-[unwind-protect][clhs-unwind-protect][^4]; a2iii. all this wrapped in
-a [catch][clhs-catch][^5].
-
-b\. the code at (a) wraps a b1. local procedure definition,
-report-error-to-client, which [logs][ht-lms] an error and returns a
-http-internal-error, i.e. [start-output][ht-so] on the result of an
-[acceptor-status-message][ht-asm]; and b2. a call to
-[handle-request][ht-hr] wrapped in a catch for "handler-done"[^6],
-with the return values for handle-request bound to "contents", "error"
-and "backtrace"; b3. if "error", then report-error-to-client; b4. if
-headers are not yet sent, [start-output][ht-so] with the return code
-of \*reply\* and whatever contents we have; b4. if no contents are
-set, then get a default page from [acceptor-status-message][ht-asm];
-b5. if an error occurs during (b4), then call report-error-to-client.
-
-c\. this occurs on the "cleanup-form" part of the unwind-protect at
-(a2ii): if there are any temporary files that were set up during the
-function, delete them.
-
-This function is all over the fucking place, owing mainly to the
-pretense of "modularity" and separation between requests and replies
--- I did say they're part of the same logical unit, didn't I? Just
-look at it: "request processing" calls "start output" -- which by the
-way, is part of "headers", right? but this same "start output" is the
-one which actually delivers a response to the client, which response
-delivery oughta be part of "reply"!! So to conclude: this whole shit
-is in dire need of refactoring and ultimately a complete rewrite. No,
-no, this time I'm pretty damn sure it's not the protocol's fault for
-this abomination.
-
-Since we've fortunately exhausted request.lisp, we're left with:
-
-**[[]]** [**reply**][ht-c-rep]: The "response" counterpart to request
-objects; holds the response [content-type][ht-ct-slot],
-[length][ht-cl-slot], [headers][ht-ho-slot], [return
-code][ht-rc-slot], [encoding][ht-ef-slot] and
-[cookies][ht-co-slot]. Implemented methods:
-
-<a name="ii3" href="#ii3">[ii3]</a> [**initialize-instance**][ht-ii3]:
-":after" the reply object is instantiated, set content-type header to
-the \*default-content-type\*.
-
-The following are, similarly to the request accessor functions,
-wrappers which implicitly bind their reply argument to the \*reply\*
-special. Also, most of them also come with a setf definition, which
-simply set the associated slot of the reply argument (also bound to
-\*reply\* unless specified) to a given new-value. I shan't bother to
-give details where there are none, here they are:
-
-<a name="hos" href="#hos">[hos]</a> [**headers-out\***][ht-hos]
-
-<a name="cos" href="#cos">[cos]</a> [**cookies-out\***][ht-cos]:
-[setf-er][ht-cos-setf].
-
-<a name="cts" href="#cts">[cts]</a> [**content-type\***][ht-cts]:
-[setf-er][ht-cts-setf].
-
-<a name="cls" href="#cls">[cls]</a> [**content-length\***][ht-cls]:
-[setf-er][ht-cls-setf].
-
-<a name="rcs" href="#rcs">[rcs]</a> [**return-code\***][ht-rcs]:
-[setf-er][ht-rcs-setf].
-
-<a name="refs"
-href="#refs">[refs]</a> [**reply-external-format\***][ht-refs]:
-[setf-er][ht-refs-setf].
-
-<a name="hosp"
-href="#hosp">[hosp]</a> [**header-out-set-p**][ht-hosp]: Looks up an
-output [header][ht-ho-slot] and returns true if found.
-
-<a name="ho" href="#ho">[ho]</a> [**header-out**][ht-ho]: Looks up a(n
-output) [header][ht-ho-slot] and, if found, returns its value. Unlike
-[the previous](#hosp) function, this one also comes with a
-[setf-er][ht-ho-setf] which e.g. performs conversions if the name
-provided is a string etc. and checks the types of content-type and
-content-length headers.
-
-<a name="co" href="#co">[co]</a> [**cookie-out**][ht-co]: Looks up a
-cookie in the respective [alist][ht-co-slot] and returns its value if
-found.
-
-This is it, then: a complete review of all the [core][hunchentoot-iii]
-architectural components of Hunchentoot. Remember, this program
-actually works, and not by mere happenstance, but quite deliberately,
-as the result of what I expect was hard work to bake this tangled
-shawarma. Note that I don't plan to add *any* functionality to the
-current mess, at least not until completely rewriting it, preferably
-on top of a not-completely-fucked [WWW stack][logs-mp-on-lisp-www]. If
-you want to make a Lisp logger/blog/web front-end using current-day
-WWW, then it seems unfortunately that this is the best you got.
-
-Having said that, the next episode in the Hunchentoot saga will reveal
-two items: a genesis of the Hunchentoot that was reviewed here, and
-one of Hunchentoot plus [all its dependencies][hunchentoot-deps], for
-which I can only vouch by noting that they sorta work, not that they
-do precisely what's written on the label. Then I can start building
-around this: a comment mechanism for this blog, an IRC logger, a
-pastebin and all those other fundamental webthings that keep things
-running.
-
-[^1]: Special variables have a [special meaning][cltl-special] in
- Common Lisp -- see what I did there? Namely, a special variable
- may be declared globally and "bound dynamically", i.e. its value
- may depend on its current execution context, such as, say, the
- function or the thread where execution takes place, if it's been
- bound this way using e.g. a let -- the naive me, who started his
- adventure in Lisp programming in Scheme, thought let can only be
- used for lexical bindings, and yet... look! Now, *if* there is no
- such "dynamic binding" for that variable, then the variable's
- *global* binding is used, which binding is shared between
- execution contexts.
-
- Yeah, what can I say... I didn't write this language, okay?
-
-[^2]: Not like this so-called convention is specified anywhere, so
- we're stuck guessing why these are named this way while the others
- are named the other. I could come up with some ideas, but why
- bother.
-
-[^3]: with-mapped-conditions is actually a [usocket][usocket-wmc]
- macro which, annoyingly enough, is not documented. Long story
- short, it can be used to define a context in which all usocket
- conditions are safely handled. (TODO: add reference to usocket
- coad?)
-
-[^4]: Which ensures that the code at the end of the function (TODO add
- reference) gets executed no matter what conditions occur during
- execution.
-
-[^5]: More precisely, "request-processed" [throw][clhs-throw]s are
- caught here, which, as the comment describes, are thrown by
- [start-output][ht-so] after responding to a HEAD
- request. The idea is, when the request method is HEAD, exit
- process-request once the headers are sent, bypassing all the other
- request handling code.
-
- Since we're here, let's examine [start-output][ht-so]: this is the
- function that pushes the actual response headers and content to
- the socket. a\. determine whether the keep-alive should be set: if
- either: a1. we used chunked transmission; or a2. the request
- method is HEAD; or a3. the return code is http-not-modified; or
- a4. the content-length or the content are set; then set
- keep-alive-p to true. b\. when the acceptor requests output
- chunking, set the Transfer-Encoding header to chunked; c\. if
- c1. keep-alive-p is set to true, then set
- \*finish-processing-socket\* to nil; if a keep-alive was
- requested, then set the Connection header to Keep-Alive, and set
- the keep-alive timeout; otherwise, if c2. the Connection header is
- not set, set it to Close. d\. set the Server header, if the
- acceptor contains one and it wasn't already set. e\. set the Date
- header; f\. do some weird-ass URL rewriting for
- sessions\*. g\. convert the content to the encoding given by
- [reply-external-format\*](#refs). h\. set the
- content-length. i\. if \*headers-sent\* was set, then return;
- otherwise j\. set \*headers-sent\* to true and call
- [send-response][ht-sr], then k\. throw a request-processed to end
- processing. Otherwise, l\. make our stream into a "chunked
- stream".
-
- From the sausage above, [send-response][ht-sr] is what does the
- actual pushing of the first response line, headers, content and so
- on.
-
- The only thing this whole load of crap gives is the ability for
- the user to do his own processing, by writing a custom
- start-output which throws its own request-processed and bypasses
- the entire process-request control flow. This is, as far as I can
- tell, evidence of serious brain rot on the part of whoever wrote
- this, since the user can write his own thing by simply modifying
- the existing code, otherwise there being really no need for all
- this imagined flexibility.
-
- \-\-\-
- \*: I don't know what the fuck this is, make sure to keep note of
- it if you ever plan to use the "sessions" thing.
-
-[^6]: We get there via [abort-request-handler][ht-arh], basically.
-
-[ht-pr]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L219
-[ht-wrp]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L262
-[ht-rrp]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L334
-[ht-sns]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L344
-[ht-qss]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L349
-[ht-gps]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L354
-[ht-pp]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L359
-[ht-pps]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L368
-[ht-his]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L373
-[ht-cis]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L378
-[ht-hi]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L383
-[ht-his2]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L389
-[ht-a]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L394
-[ht-ras]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L407
-[ht-rps]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L411
-[ht-las]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L415
-[ht-lps]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L419
-[ht-rra]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L423
-[ht-h]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L433
-[ht-rus]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L437
-[ht-rms]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L441
-[ht-sps]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L445
-[ht-ua]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L449
-[ht-ci]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L453
-[ht-r]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L458
-[ht-gp]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L462
-[ht-pp2]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L467
-[ht-p]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L472
-[ht-hims]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L480
-[ht-rpd]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L508
-[ht-arv]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L551
-[ht-arv2]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L559
-[ht-darv]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L576
-[ht-pp3]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L585
-[ht-rp]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L613
-[ht-mrpp]: /posts/y06/09c-hunchentoot-via.html#mrpp
-[ht-pc]: http://thetarpit.org/posts/y06/098-hunchentoot-iv.html#pc
-[ht-wmc]: http://coad.thetarpit.org/hunchentoot/c-util.lisp.html#L353
-[ht-so]: http://coad.thetarpit.org/hunchentoot/c-headers.lisp.html#L67
-[ht-lms]: /posts/y06/098-hunchentoot-iv.html#lms
-[ht-asm]: /posts/y06/098-hunchentoot-iv.html#asm
-[ht-hr]: /posts/y06/098-hunchentoot-iv.html#hr
-[ht-pp-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L75
-[ht-gp-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L71
-[ht-qs-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L83
-[ht-fuelta]: /posts/y06/09c-hunchentoot-via.html#fn5
-[ht-hi-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L36
-[ht-ci-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L68
-[ht-arh]: /posts/y06/098-hunchentoot-iv.html#selection-762.0-762.5
-[ht-rpd-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L94
-[ht-gpd]: /posts/y06/09c-hunchentoot-via.html#gpd
-[ht-effct]: /posts/y06/09c-hunchentoot-via.html#effct
-[ht-ad-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L90
-[ht-sn-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L79
-[ht-sr]: http://coad.thetarpit.org/hunchentoot/c-headers.lisp.html#L160
-[ht-c-rep]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L31
-[ht-ct-slot]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L32
-[ht-cl-slot]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L35
-[ht-ho-slot]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L40
-[ht-rc-slot]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L46
-[ht-ef-slot]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L51
-[ht-co-slot]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L55
-[ht-ii3]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L69
-[ht-hos]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L72
-[ht-cos]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L77
-[ht-cos-setf]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L82
-[ht-cts]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L87
-[ht-cts-setf]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L91
-[ht-cls]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L95
-[ht-cls-setf]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L99
-[ht-rcs]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L103
-[ht-rcs-setf]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L108
-[ht-refs]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L112
-[ht-refs-setf]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L116
-[ht-hosp]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L120
-[ht-ho]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L125
-[ht-co]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L130
-[ht-ho-setf]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L135
-[clhs-parse-namestring]: http://clhs.lisp.se/Body/f_pars_1.htm
-[sbcl-nf]: http://www.sbcl.org/manual/#Native-Filenames
-[flex-ots]: http://edicl.github.io/flexi-streams/#octets-to-string
-[cltl-special]: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node58.html
-[rfc-7239]: https://tools.ietf.org/html/rfc7239#section-4
-[usocket-wmc]: https://quickref.common-lisp.net/usocket.html#go-to-the-USOCKET_003ccolon_003e_003ccolon_003eWITH_002dMAPPED_002dCONDITIONS-macro
-[clhs-catch]: http://www.lispworks.com/documentation/HyperSpec/Body/s_catch.htm
-[clhs-throw]: http://clhs.lisp.se/Body/s_throw.htm
-[clhs-unwind-protect]: http://www.lispworks.com/documentation/HyperSpec/Body/s_unwind.htm
-[hunchentoot-iii]: /posts/y06/097-hunchentoot-iii.html
-[logs-mp-on-lisp-www]: http://logs.nosuchlabs.com/log/trilema/2019-08-01#1926066
-[hunchentoot-deps]: /posts/y06/097-hunchentoot-iii.html#fn3
--- /dev/null
+---
+postid: 09d
+title: Hunchentoot: requests and replies [b]
+date: August 30, 2019
+author: Lucian Mogoșanu
+tags: tech, tmsr
+---
+
+This post is part of a series on [Common Lisp WWWism][cl-www], more
+specifically a continuation of "[Hunchentoot: requests and
+replies][hunchentoot-via]". In this second part of "requests and
+replies" we will dissect "replies" and what remains of "requests", in
+precisely the reverse order.
+
+We ended [last time][hunchentoot-via] with a look at code that parses
+GET and POST requests. This time we will look, in the following order,
+at: a set of "interface methods" aimed to be employed by the user when
+implementing request handlers; the [process-request](#pr) method; and,
+last but not least, the methods exposed by the "reply" component.
+
+First, we notice that most request accessors come with wrapper methods
+whose names end in a "star", which, by some arbitrary convention,
+means that they implicitly bind their current "request" parameter to
+the special variable \*request\*[^1]. Let's take a look at a few of
+these:
+
+<a name="sns" href="#sns">[sns]</a> [**script-name\***][ht-sns]
+
+<a name="qss" href="#qss">[qss]</a> [**query-string\***][ht-qss]
+
+<a name="gps" href="#gps">[gps]</a> [**get-parameters\***][ht-gps]
+
+<a name="his" href="#his">[his]</a> [**headers-in\***][ht-his]
+
+<a name="cis" href="#cis">[cis]</a> [**cookies-in\***][ht-cis]
+
+<a name="ras" href="#ras">[ras]</a> [**remote-addr\***][ht-ras]
+
+<a name="rps" href="#rps">[rps]</a> [**remote-port\***][ht-rps]
+
+<a name="las" href="#las">[las]</a> [**local-addr\***][ht-las]
+
+<a name="lps" href="#lps">[lps]</a> [**local-port\***][ht-lps]
+
+<a name="rus" href="#rus">[rus]</a> [**request-uri\***][ht-rus]
+
+<a name="rms" href="#rms">[rms]</a> [**request-method\***][ht-rms]
+
+<a name="sps" href="#sps">[sps]</a> [**server-protocol\***][ht-sps]
+
+The implementation of these functions is, as can be readily observed,
+trivial, and the meaning should be easily deducible from the name;
+thus I won't bother the reader with redundant details. However,
+there's also:
+
+<a name="pps" href="#pps">[pps]</a> [**post-parameters\***][ht-pps]:
+This one calls [post-parameters](#pp), which:
+
+<a name="pp" href="#pp">[pp]</a> [**post-parameters**][ht-pp]: Runs
+":before" more specific implementations of this method are called;
+given a request, it calls [maybe-read-post-parameters][ht-mrpp]. If
+the post-parameters [slot][ht-pp-slot] is nil, then set the force
+parameter to true -- in other words, "force"
+maybe-read-post-parameters to populate that slot with the parsed POST
+parameters if they don't already exist.
+
+Similarly:
+
+<a name="rrp" href="#rrp">[rrp]</a>
+[**recompute-request-parameters**][ht-rrp]: Calls
+[maybe-read-post-parameters][ht-mrpp] with the force argument always
+set to true. Also, set the [get-parameters][ht-gp-slot] to the
+[re-parsed][ht-fuelta] value of [query-string][ht-qs-slot].
+
+However, we also have:
+
+<a name="hi" href="#hi">[hi]</a> [**header-in**][ht-hi]: Gets a
+specific header from the [headers-in][ht-hi-slot] slot.
+
+<a name="his2" href="#his2">[his2]</a> [**header-in\***][ht-his2]: The
+same as [header-in](#hi), with the request parameter bound to
+\*request\*.
+
+And there's also these methods which dynamically bind \*request\*,
+although their name doesn't end in "star"[^2]:
+
+<a name="a" href="#a">[a]</a> [**authorization**][ht-a]: Reads the
+"authorization" header and tries to parse the user/password
+combination associated included, if they exist. Notice the magic
+number 5 used in this shitty piece of coad, because writing a proper
+parser for yet-another-set-of-ad-hoc-encoded-parameters is too much
+work.
+
+<a name="rra" href="#rra">[rra]</a> [**real-remote-addr**][ht-rra]:
+Reads the "X-Forwarded-For" header and, if it exists, it parses the
+client and proxy address fields and returns them. Apparently there's
+no "official spec" to be found for this, apparently the whole thing's
+superseded by some other syntax specified in [some
+RFC][rfc-7239]. Hunchentoot doesn't parse this other one, however,
+so... well!
+
+<a name="h" href="#h">[h]</a> [**host**][ht-h]: Reads the "Host"
+header.
+
+<a name="ua" href="#ua">[ua]</a> [**user-agent**][ht-ua]: Reads the
+"User-Agent" header.
+
+<a name="ci" href="#ci">[ci]</a> [**cookie-in**][ht-ci]: Looks up the
+cookie with the given name in the [cookies][ht-ci-slot] slot.
+
+<a name="r" href="#r">[r]</a> [**referer**][ht-r]: Reads the "Referer"
+header.
+
+<a name="gp" href="#gp">[gp]</a> [**get-parameter**][ht-gp]: Looks up
+a specific GET parameter in [get-parameters][ht-gp-slot].
+
+<a name="pp2" href="#pp2">[pp2]</a> [**post-parameter**][ht-pp2]:
+Looks up a specific POST parameter in the
+[post-parameters][ht-pp-slot] slot.
+
+<a name="p" href="#p">[p]</a> [**parameter**][ht-p]: Looks up a GET
+parameter, and, if not found, a POST parameter with a specific name.
+
+<a name="hims"
+href="#hims">[hims]</a> [**handle-if-modified-since**][ht-hims]: Reads
+the "If-Modified-Since" header and compares it with a given time
+argument. If the two dates match, then a. set the reply as follows:
+a1. the content-length to nil; a2. remove the content-length header;
+a3. the return-code to http-not-modified; then
+b. [abort-request-handler][ht-arh].
+
+<a name="rpd" href="#rpd">[rpd]</a> [**raw-post-data**][ht-rpd]:
+a\. First, try to set an external-format based on the values of
+arguments force-binary, external-format and force-text; if no
+external-format is set and force-binary is not set, then try getting
+one from [external-format-from-content-type][ht-effct], and if that
+fails, fall back to \*hunchentoot-default-external-format\*.
+
+b\. Get the [raw-post-data][ht-rpd-slot] slot; if not set, then get
+post data by calling [get-post-data][ht-gpd]. Given a local binding to
+raw-post-data, the return value is determined based, in order, on the
+following conditions: b1. if raw-post-data is a stream, then return
+it; else b2. if raw-post-data is t or nil, return nil; else b3. if
+external-format was set, [octets-to-string][flex-ots] on raw-post-data
+and return the result; otherwise b4. return raw-post-data as-is.
+
+<a name="arv" href="#arv">[arv]</a> [**aux-request-value**][ht-arv]:
+If the given request is non-nil, then look up a value in the alist
+given by the [aux-data][ht-ad-slot] slot. Return, as multiple values,
+both the value and the symbol-value pair itself.
+
+There's also a [defsetf][ht-arv2] defined for this function, which
+looks up the value and a. if it exists, sets the new value, and b. if
+it doesn't, pushes a new symbol-value pair to [aux-data][ht-ad-slot].
+
+<a name="darv"
+href="#darv">[darv]</a> [**delete-aux-request-value**][ht-darv]:
+Similarly to [aux-request-value](#arv), operates on
+[aux-data][ht-ad-slot]; this one deletes a symbol-value binding from
+the alist.
+
+<a name="rp" href="#rp">[rp]</a> [**request-pathname**][ht-rp]: Given
+a request, a. get its [script-name][ht-sn-slot]; and
+b. [parse-path](#pp3). If drop-prefix is set to a string representing
+a path prefix, then the pathname is returned sans said prefix.
+
+<a name="pp3" href="#pp3">[pp3]</a> [**parse-path**][ht-pp3]:
+Sanitization function used by [request-pathname](#rp) to bring a
+path-string to the proper abstraction level, i.e. a Common Lisp
+pathname. After a. parsing the namestring using either
+[parse-namestring][clhs-parse-namestring] or some
+implementation-specific function, e.g. SBCL's
+[parse-native-namestring][sbcl-nf], run the following checks:
+
+b\. the host field of the pathname is nil, or it equals that of
+\*default-pathname-defaults\*; c\. the device field of the pathname is
+nil, or it equals that of \*default-pathname-defaults\*; d\. the
+directory component is nil, or it's a relative pathname without :up
+and :wild; e\. the pathname name and type fields are either nil or
+strings; f\. the namestring isn't "..".
+
+g\. When (b)-(f) are satisfied, return the parsed pathname.
+
+Since all these functions bind at least one of their arguments to
+\*request\*, we also have:
+
+<a name="wrp" href="#wrp">[wrp]</a> [**within-request-p**][ht-wrp]:
+When \*request\* is bound, return it.
+
+And finally:
+
+<a name="pr" href="#pr">[pr]</a> [**process-request**][ht-pr]: This
+function is on the main request processing/handling path, and is
+called by [process-connection][ht-pc] whenever a new HTTP request is
+available. It does a bunch of more or less related things, let's take
+them one by one.
+
+a\. the first part of the function a1. defines and binds some special
+variables, e.g. the current \*request\* being processed, and a2. wraps
+everything into some condition handling code, namely: a2i. it "maps"
+all [conditions][ht-wmc][^3]; a2ii. wraps this in an
+[unwind-protect][clhs-unwind-protect][^4]; a2iii. all this wrapped in
+a [catch][clhs-catch][^5].
+
+b\. the code at (a) wraps a b1. local procedure definition,
+report-error-to-client, which [logs][ht-lms] an error and returns a
+http-internal-error, i.e. [start-output][ht-so] on the result of an
+[acceptor-status-message][ht-asm]; and b2. a call to
+[handle-request][ht-hr] wrapped in a catch for "handler-done"[^6],
+with the return values for handle-request bound to "contents", "error"
+and "backtrace"; b3. if "error", then report-error-to-client; b4. if
+headers are not yet sent, [start-output][ht-so] with the return code
+of \*reply\* and whatever contents we have; b4. if no contents are
+set, then get a default page from [acceptor-status-message][ht-asm];
+b5. if an error occurs during (b4), then call report-error-to-client.
+
+c\. this occurs on the "cleanup-form" part of the unwind-protect at
+(a2ii): if there are any temporary files that were set up during the
+function, delete them.
+
+This function is all over the fucking place, owing mainly to the
+pretense of "modularity" and separation between requests and replies
+-- I did say they're part of the same logical unit, didn't I? Just
+look at it: "request processing" calls "start output" -- which by the
+way, is part of "headers", right? but this same "start output" is the
+one which actually delivers a response to the client, which response
+delivery oughta be part of "reply"!! So to conclude: this whole shit
+is in dire need of refactoring and ultimately a complete rewrite. No,
+no, this time I'm pretty damn sure it's not the protocol's fault for
+this abomination.
+
+Now that we've exhausted request.lisp, we're left with:
+
+**[[]]** [**reply**][ht-c-rep]: The "response" counterpart to request
+objects; holds the response [content-type][ht-ct-slot],
+[length][ht-cl-slot], [headers][ht-ho-slot], [return
+code][ht-rc-slot], [encoding][ht-ef-slot] and
+[cookies][ht-co-slot]. Implemented methods:
+
+<a name="ii3" href="#ii3">[ii3]</a> [**initialize-instance**][ht-ii3]:
+":after" the reply object is instantiated, set content-type header to
+the \*default-content-type\*.
+
+The following are, similarly to the request accessor functions,
+wrappers which implicitly bind their reply argument to the \*reply\*
+special. Also, most of them also come with a setf definition, which
+simply set the associated slot of the reply argument (also bound to
+\*reply\* unless specified) to a given new-value. I shan't bother to
+give details where there are none, here they are:
+
+<a name="hos" href="#hos">[hos]</a> [**headers-out\***][ht-hos]
+
+<a name="cos" href="#cos">[cos]</a> [**cookies-out\***][ht-cos]:
+[setf-er][ht-cos-setf].
+
+<a name="cts" href="#cts">[cts]</a> [**content-type\***][ht-cts]:
+[setf-er][ht-cts-setf].
+
+<a name="cls" href="#cls">[cls]</a> [**content-length\***][ht-cls]:
+[setf-er][ht-cls-setf].
+
+<a name="rcs" href="#rcs">[rcs]</a> [**return-code\***][ht-rcs]:
+[setf-er][ht-rcs-setf].
+
+<a name="refs"
+href="#refs">[refs]</a> [**reply-external-format\***][ht-refs]:
+[setf-er][ht-refs-setf].
+
+<a name="hosp"
+href="#hosp">[hosp]</a> [**header-out-set-p**][ht-hosp]: Looks up an
+output [header][ht-ho-slot] and returns true if found.
+
+<a name="ho" href="#ho">[ho]</a> [**header-out**][ht-ho]: Looks up a(n
+output) [header][ht-ho-slot] and, if found, returns its value. Unlike
+[the previous](#hosp) function, this one also comes with a
+[setf-er][ht-ho-setf] which e.g. performs conversions if the name
+provided is a string etc. and checks the types of content-type and
+content-length headers.
+
+<a name="co" href="#co">[co]</a> [**cookie-out**][ht-co]: Looks up a
+cookie in the respective [alist][ht-co-slot] and returns its value if
+found.
+
+This is it, then: a complete review of all the [core][hunchentoot-iii]
+architectural components of Hunchentoot. Remember, this program
+actually works, and not by mere happenstance, but quite deliberately,
+as the result of what I expect was hard work to bake this tangled
+shawarma. Note that I don't plan to add *any* functionality to the
+current mess, at least not until refactoring/rewriting/bulldozing it,
+preferably on top of a not-completely-fucked [WWW
+stack][logs-mp-on-lisp-www]. If you want to make a Lisp
+logger/blog/web front-end using current-day WWW, then it seems
+unfortunately that this is the best you got.
+
+Having said that, the next episode in the Hunchentoot saga will reveal
+two items: a genesis of the Hunchentoot that was reviewed here, and
+one of Hunchentoot plus [all its dependencies][hunchentoot-deps], for
+which I can only vouch by noting that they sorta work, not that they
+do precisely what's written on the label. Then I can start building
+around this: a comment mechanism for this blog, an IRC logger, a
+pastebin and all those other fundamental webthings that keep things
+running.
+
+[^1]: Special variables have a [special meaning][cltl-special] in
+ Common Lisp -- see what I did there? Namely, a special variable
+ may be declared globally and "bound dynamically", i.e. its value
+ may depend on its current execution context, such as, say, the
+ function or the thread where execution takes place, if it's been
+ bound this way using e.g. a let -- the naive me, who started his
+ adventure in Lisp programming in Scheme, thought let can only be
+ used for lexical bindings, and yet... look! Now, *if* there is no
+ such "dynamic binding" for that variable, then the variable's
+ *global* binding is used, which binding is shared between
+ execution contexts.
+
+ Yeah, what can I say... I didn't write this language, okay?
+
+[^2]: Not like this so-called convention is specified anywhere, so
+ we're stuck guessing why these are named this way while the others
+ are named the other. I could come up with some ideas, but why
+ bother.
+
+[^3]: with-mapped-conditions is actually a [usocket][usocket-wmc]
+ macro which, annoyingly enough, is not documented. Long story
+ short, it can be used to define a context in which all usocket
+ conditions are safely handled. I guess I'll get to this when
+ usocket is to be either sanitized or ripped altogether out of
+ Hunchentoot.
+
+[^4]: Which ensures that the code [at the end][ht-pr-unwind] of the
+ function gets executed no matter what conditions occur during
+ execution.
+
+[^5]: More precisely, "request-processed" [throw][clhs-throw]s are
+ caught here, which, as the comment describes, are thrown by
+ [start-output][ht-so] after responding to a HEAD
+ request. The idea is, when the request method is HEAD, exit
+ process-request once the headers are sent, bypassing all the other
+ request handling code.
+
+ Since we're here, let's examine [start-output][ht-so]: this is the
+ function that pushes the actual response headers and content to
+ the socket. a\. determine whether the keep-alive should be set: if
+ either: a1. we used chunked transmission; or a2. the request
+ method is HEAD; or a3. the return code is http-not-modified; or
+ a4. the content-length or the content are set; then set
+ keep-alive-p to true. b\. when the acceptor requests output
+ chunking, set the Transfer-Encoding header to chunked; c\. if
+ c1. keep-alive-p is set to true, then set
+ \*finish-processing-socket\* to nil; if a keep-alive was
+ requested, then set the Connection header to Keep-Alive, and set
+ the keep-alive timeout; otherwise, if c2. the Connection header is
+ not set, set it to Close. d\. set the Server header, if the
+ acceptor contains one and it wasn't already set. e\. set the Date
+ header; f\. do some weird-ass URL rewriting for
+ sessions\*. g\. convert the content to the encoding given by
+ [reply-external-format\*](#refs). h\. set the
+ content-length. i\. if \*headers-sent\* was set, then return;
+ otherwise j\. set \*headers-sent\* to true and call
+ [send-response][ht-sr], then k\. throw a request-processed to end
+ processing. Otherwise, l\. make our stream into a "chunked
+ stream".
+
+ From the sausage above, [send-response][ht-sr] is what does the
+ actual pushing of the first response line, headers, content and so
+ on.
+
+ The only thing this whole load of crap gives is the ability for
+ the user to do his own processing, by writing a custom
+ start-output which throws its own request-processed and bypasses
+ the entire process-request control flow. This is, as far as I can
+ tell, evidence of serious brain rot on the part of whoever wrote
+ this, since the user can write his own thing by simply modifying
+ the existing code, otherwise there being really no need for all
+ this imagined flexibility.
+
+ \-\-\-
+ \*: I don't know what the fuck this is, make sure to keep note of
+ it if you ever plan to use the "sessions" thing.
+
+[^6]: We get there via [abort-request-handler][ht-arh], basically.
+
+[ht-pr]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L219
+[ht-wrp]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L262
+[ht-rrp]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L334
+[ht-sns]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L344
+[ht-qss]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L349
+[ht-gps]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L354
+[ht-pp]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L359
+[ht-pps]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L368
+[ht-his]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L373
+[ht-cis]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L378
+[ht-hi]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L383
+[ht-his2]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L389
+[ht-a]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L394
+[ht-ras]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L407
+[ht-rps]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L411
+[ht-las]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L415
+[ht-lps]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L419
+[ht-rra]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L423
+[ht-h]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L433
+[ht-rus]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L437
+[ht-rms]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L441
+[ht-sps]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L445
+[ht-ua]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L449
+[ht-ci]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L453
+[ht-r]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L458
+[ht-gp]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L462
+[ht-pp2]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L467
+[ht-p]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L472
+[ht-hims]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L480
+[ht-rpd]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L508
+[ht-arv]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L551
+[ht-arv2]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L559
+[ht-darv]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L576
+[ht-pp3]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L585
+[ht-rp]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L613
+[ht-mrpp]: /posts/y06/09c-hunchentoot-via.html#mrpp
+[ht-pc]: http://thetarpit.org/posts/y06/098-hunchentoot-iv.html#pc
+[ht-wmc]: http://coad.thetarpit.org/hunchentoot/c-util.lisp.html#L353
+[ht-so]: http://coad.thetarpit.org/hunchentoot/c-headers.lisp.html#L67
+[ht-lms]: /posts/y06/098-hunchentoot-iv.html#lms
+[ht-asm]: /posts/y06/098-hunchentoot-iv.html#asm
+[ht-hr]: /posts/y06/098-hunchentoot-iv.html#hr
+[ht-pp-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L75
+[ht-gp-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L71
+[ht-qs-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L83
+[ht-fuelta]: /posts/y06/09c-hunchentoot-via.html#fn5
+[ht-hi-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L36
+[ht-ci-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L68
+[ht-arh]: /posts/y06/098-hunchentoot-iv.html#selection-762.0-762.5
+[ht-rpd-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L94
+[ht-gpd]: /posts/y06/09c-hunchentoot-via.html#gpd
+[ht-effct]: /posts/y06/09c-hunchentoot-via.html#effct
+[ht-ad-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L90
+[ht-sn-slot]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L79
+[ht-pr-unwind]: http://coad.thetarpit.org/hunchentoot/c-request.lisp.html#L255
+[ht-sr]: http://coad.thetarpit.org/hunchentoot/c-headers.lisp.html#L160
+[ht-c-rep]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L31
+[ht-ct-slot]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L32
+[ht-cl-slot]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L35
+[ht-ho-slot]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L40
+[ht-rc-slot]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L46
+[ht-ef-slot]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L51
+[ht-co-slot]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L55
+[ht-ii3]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L69
+[ht-hos]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L72
+[ht-cos]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L77
+[ht-cos-setf]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L82
+[ht-cts]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L87
+[ht-cts-setf]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L91
+[ht-cls]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L95
+[ht-cls-setf]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L99
+[ht-rcs]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L103
+[ht-rcs-setf]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L108
+[ht-refs]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L112
+[ht-refs-setf]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L116
+[ht-hosp]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L120
+[ht-ho]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L125
+[ht-co]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L130
+[ht-ho-setf]: http://coad.thetarpit.org/hunchentoot/c-reply.lisp.html#L135
+[cl-www]: /posts/y05/090-tmsr-work-ii.html#selection-108.0-108.17
+[hunchentoot-via]: /posts/y06/09c-hunchentoot-via.html
+[clhs-parse-namestring]: http://clhs.lisp.se/Body/f_pars_1.htm
+[sbcl-nf]: http://www.sbcl.org/manual/#Native-Filenames
+[flex-ots]: http://edicl.github.io/flexi-streams/#octets-to-string
+[cltl-special]: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node58.html
+[rfc-7239]: https://tools.ietf.org/html/rfc7239#section-4
+[usocket-wmc]: https://quickref.common-lisp.net/usocket.html#go-to-the-USOCKET_003ccolon_003e_003ccolon_003eWITH_002dMAPPED_002dCONDITIONS-macro
+[clhs-catch]: http://www.lispworks.com/documentation/HyperSpec/Body/s_catch.htm
+[clhs-throw]: http://clhs.lisp.se/Body/s_throw.htm
+[clhs-unwind-protect]: http://www.lispworks.com/documentation/HyperSpec/Body/s_unwind.htm
+[hunchentoot-iii]: /posts/y06/097-hunchentoot-iii.html
+[logs-mp-on-lisp-www]: http://logs.nosuchlabs.com/log/trilema/2019-08-01#1926066
+[hunchentoot-deps]: /posts/y06/097-hunchentoot-iii.html#fn3