From: Lucian Mogosanu Date: Thu, 25 Jul 2019 13:31:47 +0000 (+0300) Subject: drafts: Add full hunchentoot-iii draft X-Git-Tag: v0.11~24 X-Git-Url: https://git.mogosanu.ro/?a=commitdiff_plain;h=5ba904605d608247f726e02bb6b32a8ae79852e3;p=thetarpit.git drafts: Add full hunchentoot-iii draft --- diff --git a/drafts/000-hunchentoot-iii.markdown b/drafts/000-hunchentoot-iii.markdown index f7e80a8..603e5d3 100644 --- a/drafts/000-hunchentoot-iii.markdown +++ b/drafts/000-hunchentoot-iii.markdown @@ -40,7 +40,137 @@ dispatcher function. Now from this airplane view, Hunchentoot's organization looks quite digestible, which should give us a very good idea of how to start -using it. +using it. So let's take a look at that, shall we? + +Assuming we've loaded[^3] Hunchentoot into our CLtron of choice, we +can now create an acceptor instance and start it: + +~~~~ {.commonlisp} +> (defvar *myaccept* + (make-instance 'hunchentoot:acceptor :port 8052)) +> (hunchentoot:start *myaccept*) +~~~~ + +... and now what? Say, for now, that we want to serve a static site -- +I'm using The Tar Pit as my playground, but you can use whatever you +fancy. Looking at +[acceptor-dispatch-request][ht-acceptor-dispatch-request], we notice +that it calls [handle-static-file][ht-handle-static-file] with the +[document-root][ht-document-root] as a parameter. So let's set that, +and additionally the error template directory, to our site: + +~~~~ {.commonlisp} +(setf (hunchentoot:acceptor-document-root *myaccept*) + "/home/spyked/thetarpit/site/" + (hunchentoot:acceptor-error-template-directory *myaccept*) + "/home/spyked/thetarpit/site/") +~~~~ + +and now `curl http://localhost:8052` should serve our site. + +But let's say we want to go one step further and serve some content +(server-side) dynamically. The original Hunchentoot +[documentation][ht-docs] actually provides a neat minimal example, +which I'm going to steal, but not before explaining what we're going +to do here. + +Besides serving files off the disk, a web server can do other useful +stuff, such as, in Apache's case, sending the file to a preprocessing +engine (PHP or whatever), or as we're going to show, executing some +other predefined action that depends on the request parameters (URL, +cookies, HTTP method, variables and so on). For now, let's say that we +want our server to respond to the URL "/yo" (where "/" is the site +root) with the plain-text message "Hey!". Furthermore, let's say that +we want to optionally parameterize requests to this URL by the +variable "name", in which case the response will include the name: for +example, if we do a GET request to "/yo?name=spyked", we want the +server to respond with "Hey, spyked!". + +We have a few possible ways of doing this. We could for example edit +the current implementation of +[acceptor-dispatch-request][ht-acceptor-dispatch-request], which is +also the ugliest possible approach. On the other hand, Hunchentoot +makes us of Common Lisp's Object System mechanism (CLOS), which allows +us to subclass the acceptor to a user-defined class and specialize the +method above for our class. Let's try this out: + +~~~~ {.commonlisp} +(defclass myacceptor (hunchentoot:acceptor) + ()) +(change-class *myaccept* 'myacceptor) +~~~~ + +The change-class thing isn't something that we'd normally do, but if +you've been following along, you'll notice that this didn't break our +code, because well, Common Lisp is cool. Now for the dispatcher +method: + +~~~~ {.commonlisp} +(defmethod hunchentoot:acceptor-dispatch-request + ((acceptor myacceptor) request) + (cond + ((string= (hunchentoot:script-name request) "/yo") + (let ((name (cdr (assoc "name" (hunchentoot:get-parameters request) + :test #'string=)))) + (setf (hunchentoot:content-type*) "text/plain") + (format nil "Hey~@[, ~A~]!" name))) + (t (call-next-method)))) +~~~~ + +In human words: this is an implementation of acceptor-dispatch-request +specialized on myacceptor, that, upon encountering the URL +(script-name) "/yo", takes the value of the "name" parameter and +returns it as plain text. Otherwise it transfers control to the "next +most specific method"[^4], implicitly passing to it the existing +parameters. + +We could stop here, but we won't, as there's a short discussion to be +had, mainly related to the extensibility of our approach, i.e. what +happens when we add other "/yo"s to this recipe? The naive result will +look ugly and will be a pain to maintain and debug; while the more +elaborate approach, involving putting every "/yo" into its own +function, will initially fill our implementation with cond/case +conditions, eventually leading to a more civilized dispatch mechanism, +involving a lookup table from URLs to handler functions. + +Well, it so happens that Hunchentoot already has an implementation for +this type of thing, going under the name of +[easy-acceptor][ht-easy-acceptor]. easy-acceptor defines a dispatch +table whose only dispatcher is (initially, if the user desires) the +[dispatch-easy-handler][ht-dispatch-easy-handler] function, which +looks up URLs in a global handler list, thus making it possible to +e.g. share handlers between acceptors. As things usually go with these +[domain-specific languages][cl-who-ii-fn4], most of the handler +maintenance work is piled up in +[define-easy-handler][ht-define-easy-handler]. + +So, in order to illustrate this easy-stuff, first let's undo some of +our previous work and redo the very basics: + +~~~~ {.commonlisp} +(hunchentoot:stop *myaccept*) +(setq *myaccept* (make-instance 'hunchentoot:easy-acceptor + :port 8052 + :document-root "/home/spyked/thetarpit/site/" + :error-template-directory + "/home/spyked/thetarpit/site/")) +(hunchentoot:start *myaccept*) +~~~~ + +Notice how now we're sublassing easy-acceptor. Now we can define an +equivalent "easy handler" for our previous "/yo" work: + +~~~~ {.commonlisp} +(hunchentoot:define-easy-handler (say-yo :uri "/yo") (name) + (setf (hunchentoot:content-type*) "text/plain") + (format nil "Hey~@[, ~A~]!" name)) +~~~~ + +which about sums up our exercise. Initially I had wanted this to +contain an example doing some fancy prefix/"smart" URL lookup à la +[MP-WP][mp-wp], but by now this post is so large that it can't be +eaten in one sitting. Alas, I will have to leave all my fancy examples +for another episode. Thus, until next time... [^1]: Contrary to popular belief and expectations, the things that some particular X can do that are known to (some particular) me @@ -103,11 +233,11 @@ using it. slightest when inserting the .svg using img tags, because completely counter-intuitively, the browser displays *an image*, not a DOM sub-tree. So then I look at how Phf did it with his - [patch viewe][btcbase-patches], and it looks like he's inserting a - HTML image-map in the HTML document, which kinda beats the purpose - of having links in the SVG in the first place. I really, *really* - don't want to copy-paste the whole SVG file into the post, so what - the fuck am I gonna do, use <object> tags?! + [patch viewer][btcbase-patches], and it looks like he's inserting + a HTML image-map in the HTML document, which kinda beats the + purpose of having links in the SVG in the first place. I really, + *really* don't want to copy-paste the whole SVG file into the + post, so what the fuck am I gonna do, use <object> tags?! So if by now you were curious enough to look at the page source, you'll notice that what I did was to insert an inline svg that @@ -122,6 +252,102 @@ using it. will lie to me that it saves hours of my work, when it instead adds to it. +[^3]: Since I'm trying out the practice of documenting things, let's + also put this here; although now that I think about it, I'm pretty + sure I've dumped this somewhere else before. + + The preferred method of loading large programs among "Common Lisp + enthusiasts" is [Quicklisp][quicklisp], which is a sort of apt-get + for CL, with centralized repositories and all that jazz. I've + never used it, incidentally; and it's not that I'm denying its + quickness or usefulness, but that process of automatically + fetching dependencies from some site obscures my understanding of + the programs that I'm running and their real mass. Instead, I + prefer going through the laborious job of writing down the entire + dependency tree of the program that I'm trying to run, then + grabbing a copy of each dependency from the author's site\*, + putting them all in a directory and defining the path to that in + my CLtron instance. Here's how this looks for Hunchentoot: + + ~~~~ {.commonlisp} + (defvar *ext-dep-base* "/home/spyked/lisp-stolen/") + (defvar *ext-deps* '("chunga/" "trivial-gray-streams/" "cl-base64/" + "cl-fad/" "bordeaux-threads/" "alexandria/" + "cl-ppcre/" "flexi-streams/" "md5/" "rfc2388/" + "trivial-backtrace/" "usocket/")) + ~~~~ + + then I'll define a variable denoting the path to my + work-in-progress Hunchentoot code base: + + ~~~~ {.commonlisp} + (defvar *hunchentoot-path* "/home/spyked/tmsr/hunchentoot/b/hunchentoot/") + ~~~~ + + then I'm making sure I get rid of some useless dependencies, + e.g. SSL: + + ~~~~ {.commonlisp} + (pushnew :drakma-no-ssl *features*) + (pushnew :hunchentoot-no-ssl *features*) + ~~~~ + + and now I have to instruct ASDF to look for "systems", i.e. Common + Lisp programs in each of the directories in the paths above: + + ~~~~ {.commonlisp} + (loop for path in *ext-deps* do + (pushnew (concatenate 'string *ext-dep-base* path) + asdf:*central-registry* + :test #'string=)) + (pushnew *hunchentoot-path* asdf:*central-registry* :test #'string=) + ~~~~ + + Oh, and by the way: + + ~~~~ {.commonlisp} + > (length *ext-deps*) + 12 + ~~~~ + + which are *all* the dependencies\*\* needed to run Hunchentoot + given a Linux-and-SBCL installation. At this point we can call + ASDF with Hunchentoot as a parameter: + + ~~~~ {.commonlisp} + (asdf:load-system :hunchentoot) + ~~~~ + + And after a second or so, we should be all prepped and ready to + start our web server. + + \-\-\- + \*: Not that this makes much of a difference, mind you. By now I + already have most dependencies commonly found in CL programs on + the disk, so I'm e.g. using whatever version of [usocket][usocket] + that I got whenever I got it from wherever. So as per the end of + the first footnote: since I'm already using that shit although I + haven't actually read the code, why haven't I published it + already? The man [makes a good point][btcbase-1924190], I *am* + using it. + \*\*: Now tell me, are these so-called + "[dependencies][dependencies]" part of Hunchentoot, or ar they + separate programs imported by it or what? This seemingly innocent + basement-philosophical question begs for an answer, because the + answer informs my work; and if the answer is "yes, they're part of + Hunchentoot", then I need to include [at the very least] them in + the genesis [but in all honesty, I would need to also include a + SBCL, a Linux and schematics for the computer they're running on], + which means I'd be quite happy to publish a year from now. But + well, one thing at a time. + +[^4]: My CLOS-fu is somewhat lacking, but this "next most specific + method" refers in principle to the method implementation of the + direct superclass, i.e. in our case the acceptor + implementation. This means that if our call to "/yo" doesn't + match, the server will fall back to the default mechanism of + serving static files from the document root. + [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 @@ -135,3 +361,15 @@ using it. [btcbase-patches]: http://btcbase.org/patches [svg-use]: http://archive.is/JfGyb [btcbase-1922361]: http://btcbase.org/log/2019-07-12#1922361 +[quicklisp]: http://archive.is/Bk8Rm +[btcbase-1924190]: http://btcbase.org/log/2019-07-22#1924190 +[dependencies]: /posts/y03/04e-the-myth-of-software-engineering-iii.html#selection-85.0-87.0 +[ht-acceptor-dispatch-request]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L628 +[ht-handle-static-file]: http://coad.thetarpit.org/hunchentoot/c-misc.lisp.html#L151 +[ht-document-root]: http://coad.thetarpit.org/hunchentoot/c-acceptor.lisp.html#L169 +[ht-docs]: http://archive.is/MP2bT +[ht-easy-acceptor]: http://coad.thetarpit.org/hunchentoot/c-easy-handlers.lisp.html#L330 +[ht-dispatch-easy-handler]: http://coad.thetarpit.org/hunchentoot/c-easy-handlers.lisp.html#L319 +[cl-who-ii-fn4]: /posts/y05/095-cl-who-ii.html#fn4 +[ht-define-easy-handler]: http://coad.thetarpit.org/hunchentoot/c-easy-handlers.lisp.html#L164 +[mp-wp]: http://btcbase.org/log-search?q=mp-wp