📜 ⬆️ ⬇️

Developing web applications in Common Lisp (part three)

This review is a small guide for those who decide (or decide) to entrust the future of their startup to this wonderful language. Despite the fact that the main focus will be on web development, I will try to cover also more general topics related to Common Lisp in one way or another. The material will be gathered from my own experience in developing the AlterMoby web service.

The third part of this review will be devoted to the Hunchentoot web server. Consider its architecture and basic features. In addition, we will touch on some related issues, in particular, the generation of HTML / XML.


Choosing a web server

After deploying a minimal Lisp system in the last part of this review, it's time to choose a web server. This question is not obvious - the same CLiki gives links to 7 different libraries. When I chose a web server for work, it was based on several factors. First, it must be known in the CL-community, supported by a wide range of enthusiasts and be up to date until the present time. Secondly, at least one prospective web framework should be based on it. In addition, it is advisable to verify the presence of commercially successful sites based on it. Based on these criteria, only two products fell into my field of vision: Portable AllegroServ and Hunchentoot.
Portable AllegroServ is a version of AllegroServ - the original web server, written specifically for Allegro CL. The source code of the latter is not compatible with Common Lisp, since it uses non-standard extensions of the native compiler. The fact is that the author AllegroServ sought to achieve maximum efficiency, and therefore was forced to deviate from a strict standard. The result was a fast and elegant web-server, which, to this day, is probably the best choice for Allegro CL. Since the AllegroServ source code was released under the LLGPL license, there were enthusiasts who adapted it to the standard Common Lisp. The resulting product was called the Portable AllegroServ. According to some lispers, the latter is inferior to the original, both in performance and reliability.

The protagonist of this part of our review, the web-server with the strange name Hunchentoot was initially oriented towards standard Common Lisp. Therefore, it can work “right out of the box” with most popular compilers. For me, the essential argument in favor of Hunchentoot was also the fact that a promising Weblocks web framework is based on it. Despite the fact that I ultimately refused to use this framework in my project, Hunchentoot did not disappoint me at all. Next, we consider the structure and functionality of this beautiful product.

Meet the Hunchentoot

First of all, make sure that we have installed Hunchentoot:

( asdf:oos 'asdf:load-op :hunchentoot )

Now create and run an instance of the class acceptor - the receiver of http requests:

( hunchentoot:start ( make-instance 'hunchentoot:acceptor :port 8080 ))

In order for Hunchentoot to accept https requests, you need to replace the 'acceptor with ' the ssl-acceptor (its descendant), remembering to specify the certificate and key files.

Consider the simplest handler taken from the Hunchentoot help:

( hunchentoot:define-easy-handler ( say-yo :uri "/yo" ) ( name )
( setf ( hunchentoot:content-type* ) "text/plain" )
( format nil "Hey~@[ ~a~]!" name ))

This handler is called say-yo, binds to the URL / yo and accepts a single name parameter (via GET or POST). The handler generates a text document containing “Hey name!”, Where name is the value of the name parameter, or simply “Hey!” If the name parameter is not specified (i.e. equals nil). If you are embarrassed by the intricacy of string formatting, familiarize yourself with the directives of the format function. Check the operation of this handler by going to / yo and / yo? Name = Vasia.

Instead of text, the handler can give the desired file:

( hunchentoot:define-easy-handler ( get-file :uri "/file" ) ( name )
( handle-static-file ( format nil "/home/me/folder/~a" name ))

In principle, this functionality should be enough for writing a small amateur crafts. More serious work will require to give up the define-easy-handler macro, having understood the query dispatch mechanism. We outline the main features of the latter.

Each instance of the acceptor class (and hence the ssl-acceptor class ) corresponds to a query manager . The request manager is a function that accepts an instance of the request class (http request) and returns the output http stream (HTML / XML or binary data). Since the output stream depends on the specified URL, in practice the request manager does not generate it, but searches for the corresponding handler, to which it delegates this role. By default, the query manager scans the dispatch-table * list containing the dispatch functions . Each dispatch function receives the request object, analyzes it according to its criteria, and if it does match, returns a handler , a function that generates an output stream (if non-matching returns nil). Thus, the standard query dispatcher in turn starts the dispatch functions until one of them returns a handler. At the end of the * dispatch-table * usually the default dispatch is located - the dispatching function that always returns a handler (by default, this is a message about a page that is not found).

The dispatching scheme described above may seem somewhat complicated, however, in practice such a structure allows for greater flexibility. In particular, the return handler approach allows writing elegant dispatch functions. Dispatch handlers created by define-easy-handlers macro use this standard schema: * dispatch-table * contains dispatch-easy-handlers dispatch function. The latter implements its simplified dispatcher, searching in its own internal list. This list contains descriptions of all handlers defined with define-easy-handlers . Thus, dispatching can be divided into many independent branches with its own handler selection logic.

Hunchentoot defines several generators of standard dispatch functions , as well as some standard handlers . For example, create-prefix-dispatcher at a given URL prefix and handler generates a dispatching function that checks URL requests for compliance with the specified prefix. The create-regex-dispatcher function is similar to the previous one, but it generates a dispatch function that maps URLs to a given regular expression pattern. The create-folder-dispatcher-and-handler function accepts a URL prefix and directory path, returning a dispatch function that distributes files from a given directory. The standard handlers include the handle-static-file used by us in the get-file definition.

The flexibility of Common Lisp allows you to redefine existing functions and methods on the fly, which cannot but contribute to Hunchentoot’s flexibility. This way, you can not only create a new descendant of the acceptor class, but also override the default dispatcher, completely abandoning the handler search scheme described above, or completely change the query processing logic. For example, in the course of working on my project, I made Hunchentoot check the format of multipart / form-data requests even in the process of downloading data, and not after they were fully accepted. This avoids a situation where many machines post multi-megabyte garbage to the server during a DoS attack.

As a powerful and modern web server, Hunchentoot supports many standard features. These include support for cookies, convenient debugging and logging, and much more. A description of this functionality can be found on its web page.

HTML / XML generation

To conclude this part of our review, consider the CL-WHO library, which defines a simple and convenient DSL for generating HTML / XML. As usual, it is better to see once:

( push :tag3 *html-empty-tags* )
( with-html-output-to-string ( http-stream )
( :tag1 :attr1 1 :attr2 "2"
( :tag2 :attr3 ( + 1 2 ))
( loop for i from 1 to 3 do
( with-html-output ( http-stream )
( :tag3 :attr4 ( when ( oddp i ) "odd" ))))))

The result of this design does not contradict intuition:

"<tag1 attr1='1' attr2='2'><tag2 attr3='3'></tag2>
<tag3 attr4='odd' /><tag3 /><tag3 attr4='odd' /></tag1>"

As you can see, the with-html-output-to-string macro builds an XML construct from opening and closing tags corresponding to the given s-expression. The first command adds tag3 to the list of single tags. For such a tag, in the absence of attachments, a closing tag will not be generated. Next, the with-html-output-to-string macro is called, which is a wrapper for with-html-output - the main CL-WHO macro. From the example we see that arbitrary control structures are allowed for automating code generation.

The result string was created in XML mode. In this mode, it is worth generating XML / XHTML documents. To work with HTML, there is an SGML mode in which empty tags do not contain a closing slash, and attributes can be without values. As in the case of Hunchentoot for more in-depth acquaintance with the CL-WHO I send you to her web page.

Source: https://habr.com/ru/post/106067/

All Articles