drafts: Add full hunchentoot-iii draft
authorLucian Mogosanu <lucian@mogosanu.ro>
Thu, 25 Jul 2019 13:31:47 +0000 (16:31 +0300)
committerLucian Mogosanu <lucian@mogosanu.ro>
Thu, 25 Jul 2019 13:31:47 +0000 (16:31 +0300)
drafts/000-hunchentoot-iii.markdown

index f7e80a8..603e5d3 100644 (file)
@@ -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 &lt;object&gt; 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 &lt;object&gt; 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