Install


Common Lisp on OpenBSD

With a simple Hunchentoot example

RevisionDescriptionName
0.0.1This tableAngelo Rossi <angelo.rossi.homelab@gmail.com>
   

Preface to this Edition

This is a lone work by Angelo Rossi <angelo.rossi.homelab@gmail.com>

License Information

Copyright (C) 2023 Angelo Rossi <angelo.rossi.homelab@gmail.com>


Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:


1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. Neither the name of the University nor the names of its contributors
   may be used to endorse or promote products derived from this software
   without specific prior written permission.


THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

Introduction

This document explain how to install and configure sbcl on OpenBSD system. Assuming you already installed this OS on your machine with basic tools and desktop environment or, at least, you are able to use emacs as text editor. First of all OpenBSD is a free 4.4BSD-based Unix-like operating system. It has got these unique features:

  • portability;
  • standardization;
  • correctness;
  • proactive security;
  • integrated cryptography.

For all of these you could be interested in run a webserver on it. So let's see how to achieve that and look at a simple way to program a dynamic webserver application which in turn use the Common Lisp language to create html pages on the fly.

Foreword

Why Common Lisp? One of the question that arises most of the times speaking with people involved in computer science as professionals or reasearches. Most of them look at Lisp (and Common Lisp as a reflex) not only the language developed for AI fifty years ago, but something strange and esotheric, something not really useful and related to a distant past made of punched cards and gigantic magnetic tape reader the size of a fridge. No one take Lisp as the language of the following innovations:

  • a regular and most simple syntax among high level programming language;
  • the first high level language used to program an OS (Genera);
  • used to program one of the first graphic user interface for an OS;
  • an advanced macro system to generate Lisp code and extend the language (DSL);
  • A garbage collector;
  • derived from the mathematics idea of lambda calculus by Alonzo Church;
  • a major dialect with a standard: ANSI INCITS 226-1994 (S20018).

Who is this guide for?

The purpose of this guide is to let the reader discovers one of the most advanced and complete language in the history of computer science. The usage of Lisp is quite simple since it is based on very basic ideas about programming. Since OpenBSD is a modern UNIX OS with excels in some areas like security and correctness, this guide could be of some use to people that want to experimenting and deploy secure applications for everyday usage. A basic knowledge of OpenBSD, shell and Common Lisp will boost the process to build and better understand the concept. For those who don't know Common Lisp we suggest to read those books: [BARSKI2010]_ and [SEIBEL2005]_.

Other resources

Online official documentation and info sources from:

Legally speaking...

We referenced to the BSD 3 clauses license for this work and the related code.

Getting Started

The installation process

We refer to a running and functional installation of OpenBSD 7.4 for amd64 platform even if the majority of the ideas can be used on othe platform too as sparc64 and arm64. First of all we open a terminal window: OpenBSD uses csh as standard shell interpreter, int this guide we prefer the bash shell so, as root, we can give the command:

$ doas pkg_add -v bash

After the installation we can add to the system the Stell Bank Common Lisp interpreter/compiler - sbcl with:

$ doas pkg_add -v sbcl

Configuration for sbcl

As installed, sbcl is already working with its default configuration which are specified in its \*features\* global variable, for example, let's run sbcl:

$ sbcl

the system responds with:

This is SBCL 2.3.8.openbsd.sbcl-2.3.8, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.
SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
*

Or with a very similar message, informing us that the interpreter is ready and it is waiting for a command to be entered at the REPL prompt "*".

REPL stands for Read, Eval and Print Loop: a way to interact with a user. At first the informations entered after the prompt are read, then evaluated by the sbcl interpreter and a result is always printed/returned back to the user. The cycle continues with the interpreter waiting for the user to enter informations again. At this point we would like to see some configurations/characteristics of this particular version of the interpreter, so we enter:

* *features*
(:ARENA-ALLOCATOR :X86-64 :GENCGC :64-BIT :ANSI-CL :BSD :COMMON-LISP :ELF
 :IEEE-FLOATING-POINT :LITTLE-ENDIAN :OPENBSD :PACKAGE-LOCAL-NICKNAMES
 :SB-CORE-COMPRESSION :SB-LDB :SB-PACKAGE-LOCKS :SB-THREAD :SB-UNICODE :SBCL
 :UNIX)

Installing quicklisp

Let's quit the interpreter and continue installing other software that we will use to make a Common Lisp development environment, the editor. We choose emacs which is, among other things, programmed largely in Lisp, it has inside it a Common Lisp interpreter which is used to perform operation on text as well as configurations:

$ doas pkg_add -v emacs

At this point the installer can ask us to choose among different version of emacs. Choose the one is best with your desktop environment or, if you like install the version with no desktop support at all, you can still use it from the console or terminal with no great difference from the desktop version. Also emacs has got is own configuration which is accessible with the program interface or modifying a file in the home directory. We will see how to achieve that in the following paragraph, by now let's install the quicklisp common lisp library system. This is a completely written in Common Lisp piece of software, which make the user capable of handling projects related to Common Lisp itself, we will use it to let our software use already written libraries. To install quicklisp we have to download the quicklisp installer from the site https://beta.quicklisp.org. We use curl to do that, so:

$ doas pkg_add -v curl

proceed to retrieve the quicklisp library:

$ curl -O https://beta.quicklisp.org/quicklisp.lisp

and the library file signature to check its authenticity:

$ curl -O https://beta.quicklisp.org/quicklisp.lisp.asc

then we check the signature of the downloaded file:

$ gpg --verify quicklisp.lisp.asc quicklisp.lisp
gpg: Signature made Sat Feb  1 09:25:28 2014 EST using RSA key ID 028B5FF7
gpg: Good signature from "Quicklisp Release Signing Key "

The downloaded file is then usable to install the quicklisp library manager. At This point we face two main choices: we can install the library manager only for the user or system-wide. We assume for now that the installation is limited to one user, so we can proceed as follows:


$ sbcl --load quicklisp.lisp
This is SBCL 2.3.8.openbsd.sbcl-2.3.8, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.

==== quicklisp quickstart 2015-01-28 loaded ====

  To continue with installation, evaluate: (quicklisp-quickstart:install)

  For installation options, evaluate: (quicklisp-quickstart:help)

*

At this point we can proceed to install the library:

* (quicklisp-quickstart:install)
...
==== quicklisp installed ====

  To load a system, use: (ql:quickload "system-name")

  To find systems, use: (ql:system-apropos "term")

  To load Quicklisp every time you start Lisp, use: (ql:add-to-init-file)

  For more information, see http://www.quicklisp.org/beta/

T
*

To use the library with the sbcl REPL we must instruct sbcl itself to load a configuration batch file called .sbclrc which resides in the user home. Quicklisp can do that for us as stated in the message above, in fact let's issue the form in the REPL:

* (ql:add-to-init-file)
I will append the following lines to #P"/home/user/.sbclrc":

;;; The following lines added by ql:add-to-init-file:
#-quicklisp
(let ((quicklisp-init #P"/home/user/quicklisp/setup.lisp"))
  (when (probe-file quicklisp-init)
    (load quicklisp-init)))

Press Enter to continue.

#P"/home/user/.sbclrc"
* (quit)
$

To return to the shell prompt, we can check that the following code is then written in the .sbclrc file:

$ cat ~/.sbclrc
;;; The following lines added by ql:add-to-init-file:
#-quicklisp
(let ((quicklisp-init #P"/home/user/quicklisp/setup.lisp"))
  (when (probe-file quicklisp-init)
    (load quicklisp-init)))

.sbclrc is the file which is read and evaluated from sbcl before entering the REPL, we can place configuration code for sbcl in there. For example we want sbcl and quicklisp to include our personal Common Lisp projects repository which is, for example, in ~/Development/lisp directory. So we add the following code at the end of .sbclrc:

(defun setup-registry (directory-path)
  (format t "; adding components under ~A to asdf registry~%" directory-path)
  (mapc (lambda (asd-pathname)
          (pushnew (make-pathname :name nil
	                          :type nil
	        		  :version nil
			          :defaults asd-pathname)
                   asdf:*central-registry*
	           :test #'equal))
	  (directory (merge-pathnames #p"**/*.asd" directory-path))))

(setup-registry (merge-pathnames #p"Development/lisp/" (user-homedir-pathname)))

Let's see what happens when we launch sbcl:

$ sbcl
This is SBCL 2.3.8.openbsd.sbcl-2.3.8, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
; adding components under /home/angel/Development/lisp/ to asdf registry
*

Now let's check what happened to the \*features\*:

* *features*
(:QUICKLISP :ASDF3.3 :ASDF3.2 :ASDF3.1 :ASDF3 :ASDF2 :ASDF :OS-UNIX
 :NON-BASE-CHARS-EXIST-P :ASDF-UNICODE :ARENA-ALLOCATOR :X86-64 :GENCGC :64-BIT
 :ANSI-CL :BSD :COMMON-LISP :ELF :IEEE-FLOATING-POINT :LITTLE-ENDIAN :OPENBSD
 :PACKAGE-LOCAL-NICKNAMES :SB-CORE-COMPRESSION :SB-LDB :SB-PACKAGE-LOCKS
 :SB-THREAD :SB-UNICODE :SBCL :UNIX)
*

Configuration for emacs

Quicklisp library added more features to the basic sbcl behaviour especially related to ASDF. Now let's configure emacs to allow us to have a REPL inside a window:

$ sbcl
...
* (ql:quickload :quicklisp-slime-helper)
  To load "quicklisp-slime-helper":
    Load 1 ASDF system:
    quicklisp-slime-helper
; Loading "quicklisp-slime-helper"
[package swank-loader]............................
[package quicklisp-slime-helper]
slime-helper.el installed in "/home/user/quicklisp/slime-helper.el"

To use, add this to your ~/.emacs:

 (load (expand-file-name "/home/user/quicklisp/slime-helper.el"))
 ;; Replace "sbcl" with the path to your implementation
 (setq inferior-lisp-program "sbcl")


(:QUICKLISP-SLIME-HELPER)
*

To add the code showed by the message before we shall open ~/.emacs which is in turn the configuration file for emacs and add the lines:

(load (expand-file-name "/home/user/quicklisp/slime-helper.el"))
;; Replace "sbcl" with the path to your implementation
(setq inferior-lisp-program "sbcl")

once the .emacs file is edited we can launch emacs and we have the window:

we start SLIME by pressing the key Alt and x on the keyboard, emacs will let us enter a command:

we enter the command "slime" at the "M-x" prompt:

and press the enter key on the keyboard. The SLIME REPL appears:

Let's see what are the \*features\* now using the SLIME REPL:

We can try to load a package "antik", and then:

CL-USER> (ql:quickload :antik)
...
(:ANTIK)
CL-USER>

antik provides some useful constants and standard quantities handling in scientific calculations such as:

CL-USER> antik:+days-per-month+
30
CL-USER>

Hunchentoot basic application

Hunchentoot installation

Using SLIME the Superior Lisp Interaction Mode for Emacs (https://slime.common-lisp.dev), we enter the form:

CL-USER> (ql:quickload :hunchentoot)
To load "hunchentoot":
Load 1 ASDF system:
  hunchentoot
; Loading "hunchentoot"
.
(:HUNCHENTOOT)
CL-USER>

Documentation for the hunchentoot server is at https://edicl.github.io/hunchentoot. In this example we used the SSL library to deploy an SSL enabled webserver, thus we have to create a self signed certificate to run the server. Following these hints let you create what needed for the server. First of all let's install the openssl library and tools:

$ doas pkg_add -v openssl

we choose 1: openssl-3.1.3, after the installation we issue the command

 $ openssl version
LibreSSL 3.8.2
$

this means the library is correctly installed and ready to be used to create our certificate and key:

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ~/selfsigned.key -out ~/selfsigned.crt
........................................................
.........................................................................................................................................
writing new private key to '/home/angel/selfsigned.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:IT
State or Province Name (full name) []:Italy
Locality Name (eg, city) []:Rome
Organization Name (eg, company) []:Antani S.r.l.
Organizational Unit Name (eg, section) []:R&D
Common Name (eg, fully qualified host name) []:antani.it
Email Address []:antani@antani.it
$

Let's create .pem files to use in clients transactions with the server:

$ openssl x509 -in ~/selfsigned.crt -out ~/selfsigned-crt.pem -outform PEM
$ openssl rsa -in selfsigned.key -text > selfsigned-key.pem
writing RSA key
$

so we finally have:

$ ls ~/selfsigned*
/home/user/selfsigned-crt.pem   /home/user/selfsigned-key.pem   /home/user/selfsigned.crt       /home/user/selfsigned.key
$

we can directly use the .pem files to run our test SSL-based webserver. Those files have to be moved inside our Common Lisp project directory, which at this time is non-existent.

Project creation and configuration

To create an empty project skeleton we have to use another useful quicklisp library: quickproject, so let's return to the SLIME REPL prompt on emacs and enter the form.
A form is something enclosed in "(" and ")" pair, it is the way the Common Lisp interpreter understand the commands and data entered via the REPL. In the same way, Common Lisp programs are composed by forms which may contains other forms as well. There are other objects that can be entered in the REPL and evaluated, they are: numbers, symbols, keywords, strings, characters, t and nil. Those objects it said that evaluate to themselves.

CL-USER> (ql:quickload :quickproject)
To load "quickproject":
Load 1 ASDF system:
  quickproject
; Loading "quickproject"
[package html-template]...........................
[package quickproject].
(:QUICKPROJECT)
CL-USER>

We are ready to create a skeleton project and hack it to perform the required task. Just enter:

CL-USER> (quickproject:make-project #p"/home/user/Development/lisp/test-hunchentoot"
                                    :depends-on '(#:cl-fad
		                                  #:cl-who
			                          #:cl+ssl
						  #:ironclad
						  #:lass
						  #:parenscript
						  #:hunchentoot
						  #:bordeaux-threads
						  #:simple-date-time)
				     :author "antani <antani@antani.it>"
				     :license "BSD")
WARNING:
   Coercing #p"/home/user/Development/lisp/test-hunchentoot" to directory
"test-hunchentoot"
CL-USER>

The project is then created following the path /home/user/Development/lisp/test-hunchentoot. We can check that issuing from the shell:

$ ls ~/Development/lisp
test-hunchentoot

and then:

$ ls ~/Development/lisp/test-hunchentoot
README.md                 package.lisp              test-hunchentoot.asd      test-hunchentoot.lisp
$

For a small project we can leave this layout as it is, but since we do not know the future development of the project, one can follow this example or feel free to reshape the project layout as it fits the purpose:

$ cd ~/Development/lisp/test-hunchentoot
$ mkdir sources scripts docs
$ mv *.lisp sources/
$ ls
README.md                 docs                      scripts                   sources                   test-hunchentoot.asd
$

So we leave .lisp files inside the sources/ directory and the system files on the project root directory. Now let's see what is inside the files created by quickproject, just pick test-hunchentoot.asd using emacs menu "File", then "Open File..." and choose the correct file navigating the user home directory:

;;;; test-hunchentoot.asd

(asdf:defsystem #:testina-hunchentoot
  :description "Describe test-hunchentoot here"
  :author "antani <antani@antani.it>"
  :license  "BSD"
  :version "0.0.1"
  :serial t
  :depends-on (#:cl-fad #:cl-who #:cl+ssl #:ironclad #:lass #:parenscript #:hunchentoot #:bordeaux-threads #:simple-date-time)
  :components ((:file "package")
               (:file "testina-hunchentoot")))

We created a PEM certificate and a key, we can move them inside the project directory with:

$ mv ~/selfsigned-crt.pem ~/Development/lisp/test-hunchentoot/scripts/
$ mv ~/selfsigned-key.pem ~/Development/lisp/test-hunchentoot/scripts/
$

So we can reference them starting from the hunchentoot server document root directory. Before entering the application programming phase, we want to add legal notices (license) to the project files. We follow this convention: every file containing code related to system definitions and packaging will be marked with a license text, here an example test-hunchentoot.asd file in which the test-hunchentoot system is defined:

;;;;***************************************************************************
;;;; test-hunchentoot.asd
;;;;
;;;; Copyright 2023 Antani <antani@antani.it>
;;;;
;;;; Redistribution and use in source and binary forms, with or without
;;;; modification, are permitted provided that the following conditions are met:
;;;;
;;;;     1. Redistributions of source code must retain the above copyright notice,
;;;;        this list of conditions and the following disclaimer.
;;;;
;;;;     2. Redistributions in binary form must reproduce the above copyright
;;;;        notice, this list of conditions and the following disclaimer in the
;;;;        documentation and/or other materials provided with the distribution.
;;;;
;;;;     3. Neither the name of the copyright holder nor the names of its
;;;;        contributors may be used to endorse or promote products derived from
;;;;        this software without specific prior written permission.
;;;;
;;;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
;;;; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
;;;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
;;;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
;;;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
;;;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
;;;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
;;;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;;;;***************************************************************************

(asdf:defsystem #:test-hunchentoot
  :description "Test hunchentoot server (SSL version)."
  :author "Antani <antani@antani.it>"
  :license "BSD"
  :depends-on (#:cl-fad
               #:cl-who
	       #:cl+ssl
	       #:ironclad
	       #:lass
	       #:parenscript
	       #:hunchentoot
	       #:bordeaux-threads
	       #:simple-date-time)
  :serial t
  :components ((:file "sources/package")
               (:file "sources/parameters")
	       (:file "sources/system")
	       (:file "sources/test-hunchentoot")))

;; End of file test-hunchentoot.asd

To automate the process of inserting this license text, we can create a text file and insert it in the source file we want to modify, for example let's call it LICENSE:

;;;; Copyright 2023 Antani <antani@antani.it>
;;;;
;;;; Redistribution and use in source and binary forms, with or without
;;;; modification, are permitted provided that the following conditions are met:
;;;;
;;;;     1. Redistributions of source code must retain the above copyright notice,
;;;;        this list of conditions and the following disclaimer.
;;;;
;;;;     2. Redistributions in binary form must reproduce the above copyright
;;;;        notice, this list of conditions and the following disclaimer in the
;;;;        documentation and/or other materials provided with the distribution.
;;;;
;;;;     3. Neither the name of the copyright holder nor the names of its
;;;;        contributors may be used to endorse or promote products derived from
;;;;        this software without specific prior written permission.
;;;;
;;;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
;;;; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
;;;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
;;;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
;;;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
;;;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
;;;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
;;;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;;;;***************************************************************************

so a source code file related to packaging and system definitions will have this shape:

  • a comment containing emacs configurations and mode for the file;
  • a separator;
  • the name of the file itself;
  • a separator;
  • the licensing information;
  • a separator;
  • source code;
  • an end of file comment.

Other source code files, instead will have this shape:

  • a comment containing emacs configurations and mode for the file;
  • a separator;
  • the file name;
  • a description of the file contents;
  • a separator;
  • source code;
  • an end of file comment.

We remind you that in Common Lisp comments always begin with ";" until the end of the line. There are some conventions on the use of ";" itself. The following rules are taken from "Google Common Lisp Style Guide" at https://google.github.io/styleguide/lispguide.xml

  • File headers and important comments that apply to large sections of code in a source file should begin with four semicolons.
  • You should use three semicolons to begin comments that apply to just one top-level form or small group of top-level forms.
  • Inside a top-level form, you should use two semicolons to begin a comment if it appears between lines.
  • You should use one semicolon if it is a parenthetical remark and occurs at the end of a line. You should use spaces to separate the comment from the code it refers to so the comment stands out. You should try to vertically align consecutive related end-of-line comments.

The complete project would appear as follows:

$ tree
.
|-- LICENCE
|-- README
|-- scripts
|   |-- run-webserver.sh
|   |-- selfsigned-crt.pem
|   `-- selfsigned-key.pem
|-- sources
|   |-- package.lisp
|   |-- parameters.lisp
|   |-- system.lisp
|   `-- test-hunchentoot.lisp
`-- test-hunchentoot.asd

2 directories, 10 files
$

File are listed as follows. For the package.lisp file in sources/:

;;;; -*- mode: common-lisp-mode; electric-indent-mode: t; coding: utf-8 -*-
;;;;***************************************************************************
;;;; package.lisp
;;;;***************************************************************************
;;;; Copyright 2023 Antani <antani@antani.it>
;;;;
;;;; Redistribution and use in source and binary forms, with or without
;;;; modification, are permitted provided that the following conditions are met:
;;;;
;;;;     1. Redistributions of source code must retain the above copyright notice,
;;;;        this list of conditions and the following disclaimer.
;;;;
;;;;     2. Redistributions in binary form must reproduce the above copyright
;;;;        notice, this list of conditions and the following disclaimer in the
;;;;        documentation and/or other materials provided with the distribution.
;;;;
;;;;     3. Neither the name of the copyright holder nor the names of its
;;;;        contributors may be used to endorse or promote products derived from
;;;;        this software without specific prior written permission.
;;;;
;;;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
;;;; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
;;;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
;;;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
;;;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
;;;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
;;;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
;;;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;;;;***************************************************************************

(defpackage #:test-hunchentoot
  (:use #:cl)
  (:export #:start-web-server
           #:stop-web-server))

;;;; End of file package.lisp

For the parameters.lisp file in sources/:

;;;; -*- mode: common-lisp-mode; electric-indent-mode: t; coding: utf-8 -*-
;;;;***************************************************************************
;;;; parameters.lisp
;;;;***************************************************************************
;;;; Parameters and variables definitions for the project test-hunchentoot.
;;;;***************************************************************************

(in-package #:test-hunchentoot)

;;;; Parameters.
(defparameter *ssl-acceptor* nil)
(defparameter *default-document-root-directory-pathname* (cl-fad:merge-pathnames-as-directory (user-homedir-pathname) #p"Development/lisp/test-hunchentoot/"))
(defparameter *default-ssl-private-key-file-pathname* (cl-fad:merge-pathnames-as-file (user-homedir-pathname) #p"Development/lisp/test-hunchentoot/scripts/selfsigned-key.pem"))
(defparameter *default-ssl-certificate-file-pathname* (cl-fad:merge-pathnames-as-file (user-homedir-pathname) #p"Development/lisp/test-hunchentoot/scripts/selfsigned-crt.pem"))
(defparameter *default-port* 4242)
(defparameter *default-ssl-port* 9443)
(defparameter *port* 4242)
(defparameter *ssl-port* 9443)
(defparameter *default-start-page-reload-timeout* 0)
(defparameter *default-info-email* "antani@antani.it")
(defparameter *fruits* '(apple pear banana orange avocado pineapple peach plum))

;;;; End of file parameters.lisp

For the system.lisp file in sources/:

;;;; -*- mode: common-lisp-mode; electric-indent-mode: t; coding: utf-8 -*-
;;;;***************************************************************************
;;;; system.lisp
;;;;***************************************************************************
;;;; Provide data structures and classes definitions for the project
;;;; test-hunchentoot.
;;;;***************************************************************************

(in-package #:test-hunchentoot)

;; System class.
(defclass system-class ()
  ((acceptor :initarg :acceptor :accessor acceptor :initform nil)
   (ssl-acceptor :initarg :ssl-acceptor :accessor ssl-acceptor :initform nil)
   (lock-object :initarg :lock-object :accessor lock-object :initform nil)
   (condition-object :initarg :condition-object :accessor condition-object :initform nil)))

;; functions.

(defun make-system (&rest parameters &key (acceptor nil acceptor-p) (ssl-acceptor nil ssl-acceptor-p) (lock-object nil lock-object-p) (condition-object nil condition-object-p))
  "Create an instance of the system-class."
  (declare (ignorable parameters
                      acceptor
                      ssl-acceptor
		      lock-object
		      condition-object))
  (make-instance 'system-class
                 :acceptor acceptor
                 :ssl-acceptor ssl-acceptor
		 :lock-object lock-object
	         :condition-object condition-object))

;;;; End of file system.lisp

For the test-hunchentoot.lisp file in sources/:

;;;; -*- mode: common-lisp-mode; electric-indent-mode: t; coding: utf-8 -*-
;;;;***************************************************************************
;;;; test-hunchentoot.lisp
;;;;***************************************************************************
;;;; This file contains all the functions for the test-hunchentoot project.
;;;;***************************************************************************
;;;;

(in-package #:test-hunchentoot)

;; "test-hunchentoot" goes here. Hacks and glory await!
;; Initial setup for javascript environment
(eval-when (:compile-toplevel :execute)
  (setq cl-who:*attribute-quote-char* #\"
        ps:*js-string-delimiter* #\"))

;; Methods.
(defmethod hunchentoot:acceptor-dispatch-request ((vhost system-class) request)
  (mapc (lambda (dispatcher)
          (let ((handler (funcall dispatcher request)))
            (when handler
              (return-from hunchentoot:acceptor-dispatch-request (funcall handler)))))
        (dispatch-table vhost))
  (call-next-method))

(defmethod start-web-server ((object system-class) &rest parameters &key 
                                                                      (document-root-directory-pathname *default-document-root-directory-pathname* document-root-directory-pathname-p)
	                                                              (ssl-private-key-file-pathname *default-ssl-private-key-file-pathname* ssl-private-key-file-pathname-p)
	                                                              (ssl-certificate-file-pathname *default-ssl-certificate-file-pathname* ssl-certificate-file-pathname-p)
	                                                              (address "localhost" address-p)
                                                                      (port *default-port* port-p)
	                                                              (ssl-port *default-ssl-port* ssl-port-p)
                                                                      (page-reload-timeout *default-start-page-reload-timeout* page-reload-timeout-p)
                                                                      (verbose nil))
  "Configure and start the hunchentoot webserver."
  (declare (ignorable parameters
                      document-root-directory-pathname
	              ssl-private-key-file-pathname
		      ssl-certificate-file-pathname
		      address
		      port
		      ssl-port
	              page-reload-timeout
		      verbose))
  (when document-root-directory-pathname-p
    (check-type document-root-directory-pathname pathname)
    (assert (cl-fad:directory-exists-p document-root-directory-pathname)))
  (when ssl-private-key-file-pathname-p
    (check-type ssl-private-key-file-pathname pathname)
    (assert (cl-fad:file-exists-p ssl-private-key-file-pathname)))
  (when ssl-certificate-file-pathname-p
    (check-type ssl-certificate-file-pathname pathname)
    (assert (cl-fad:file-exists-p ssl-certificate-file-pathname)))
  (when address-p
    (check-type address string))
  (when port-p
    (check-type port (unsigned-byte 16)))
  (when ssl-port-p
    (check-type ssl-port (unsigned-byte 16)))
  (when (and port-p ssl-port-p)
    (assert (/= port ssl-port)))
  (when page-reload-timeout-p
    (check-type page-reload-timeout (unsigned-byte 16)))
  ;;
  (setq *random-state* (make-random-state t))
  (unwind-protect
      (let* ((hunchentoot:*log-lisp-errors-p* t)
             (hunchentoot:*log-lisp-warnings-p* t)
	     (hunchentoot:*catch-errors-p* nil)
             (stdin (sb-sys:make-fd-stream 0
		                           :input t
					   :buffering :full
					   :element-type '(unsigned-byte 8)))
	     (cl+ssl:make-ssl-client-stream (cl+ssl:stream-fd stdin)))
        (setf (lock-object object) (bt:make-lock (symbol-name (gensym "webserver-lock-"))))
	(setf (condition-object object) (bt:make-condition-variable :name (symbol-name (gensym "webserver-condition-"))))
	;; Server setup and start
	(setf (ssl-acceptor object) (make-instance 'hunchentoot:easy-ssl-acceptor
	                                           :name 'ssl-acceptor
				                   :ssl-privatekey-file ssl-private-key-file-pathname
						   :ssl-certificate-file ssl-certificate-file-pathname
						   :address address
						   :port ssl-port
						   :document-root document-root-directory-pathname))
        (hunchentoot:start (ssl-acceptor object))
	(setq *ssl-acceptor* (ssl-acceptor object))
	(when verbose
	  (format *standard-output*
	          "~%;; Starting SSL Web Server on port ~a.~%~%" ssl-port)
	  (format *standard-output*
	          ";; Certificate pathname ~s.~%" ssl-certificate-file-pathname)
	  (format *standard-output*
	          ";; Private key pathname ~s.~%~%" ssl-private-key-file-pathname)
	  (finish-output *standard-output*))
	(setq hunchentoot:*dispatch-table* (list 'hunchentoot:dispatch-easy-handlers))
	(loop
	    named wait-loop
	    do
	      (handler-case
	          (progn
		    (bt:with-lock-held ((lock-object object))
		      (bt:condition-wait (condition-object object)
		                         (lock-object object)))
		    (return-from wait-loop))
		(hunchentoot:hunchentoot-error (he)
		  (format *standard-output* "~s~%" he)
		  (finish-output *standard-output*))
		(error (e)
		  (format *standard-output* "~s~%" e)
		  (finish-output *standard-output*)
		  (return-from wait-loop))
		(warning (w)
		  (format *standard-output* "~s~%" w)
		  (finish-output *standard-output*)))))
    ;; unwind-protect cleanup form.
    (progn
      (when verbose
        (format *standard-output* ";; Shutting down webserver.~%")
	(finish-output *standard-output*))
      (stop-web-server object :verbose t))))

(defmethod stop-web-server ((object system-class) &rest parameters &key (verbose nil))
  "Stop the hunchentoot webserver."
  (declare (ignorable parameters verbose))
  (when (ssl-acceptor object)
    (hunchentoot:stop (ssl-acceptor object))
    (setq *ssl-acceptor* nil)
    (when verbose
      (format *standard-output*
              "~%;; Stopping SSL Web Server.~%~%")
      (finish-output *standard-output*)))
  (bt:condition-notify (condition-object object)))

;; Functions.
(defun remove-nth (index object)
  "remove the nth element from the list."
   (remove-if (constantly t)
              object
	      :start index
	      :count 1))

;; Hunchentoot html stuffs.
(hunchentoot:define-easy-handler (main-page :uri "/") ()
  "The main page handler for http request."
  (hunchentoot:redirect "/index"))

(hunchentoot:define-easy-handler (index-page :uri "/index") ()
  "The /index page handler for http request."
  (let ((temporary-fruits *fruits*)
        (i nil))
    (cl-who:with-html-output-to-string (s)
      (cl-who:htm
        (:html
	  (:h1 "This is an example of dynamic list:")
	  (:br)
	  (:ul
	    (loop
	        while (> (length temporary-fruits) 0)
		do
		  (setq i (random (length temporary-fruits)))
		  (cl-who:htm
		    (:li (cl-who:str (nth i temporary-fruits))))
		  (setq temporary-fruits (remove-nth i temporary-fruits)))))))))

;; Start at quickload.
(start-web-server (make-system)
                  :ssl-port *default-ssl-port*
                  :verbose t)

;;;; End of file test-hunchentoot.lisp

The file run-webserver.sh is a bash script to launch the webserver without use emacs SLIME or sbcl from the console, for clarity it is listed below:

#!/bin/bash

interpreter="ecl"
ecl_program=`which ecl`
sbcl_program=`which sbcl`
lisp_form="(ql:quickload :test-hunchentoot)"

case $interpreter in
    "ecl") if [ -x $ecl_program ]; then
	       nohup $ecl_program --eval "$lisp_form" > output.txt 2>&1 &
	   fi
	   ;;
    "sbcl") if [ -x $sbcl_program ]; then
		nohup $sbcl_program --eval "$lisp_form"  > output.txt 2>&1 &
	    fi
	    ;;
esac

To test our SSL server we can connect to it using a browser on a machine connected on the same LAN. For example as in the image below: