Lack Middleware Roundup

Background

Clack applications can be configured with a middleware chain, enabling modifications to the request or the response at each link in the chain. This architecture enables the extraction of common functionalities like request parsing, access logging, and error handling from application-specific or server-specific logic, promoting code reuse and a clearer separation of concerns.

Clack is directly inspired by Ruby’s Rack, but the general approach of a server abstraction interface and use of application middleware has been applied in a number of programming languages, including: Python (WSGI, ASGI), Clojure (Ring), Perl (Plack), Java (Servlet), and Elixir (Plug).

Official middlewares

The lack repository on GitHub contains the official middlewares for Clack applications.

accesslog

Source

Symbol: LACK/MIDDLEWARE/ACCESSLOG:*LACK-MIDDLEWARE-ACCESSLOG*

The accesslog middleware logs each request. By default the middleware prints to *STANDARD-OUTPUT*, but this can be overridden by defining keyword parameter LOGGER, a function. The logger function takes a single parameter representing the output string returned by the formatter.

The default formatter may be overridden by providing the FORMATTER keyword parameter, a function. The formatter function takes three parameters: the environment plist, the response, and optionally the current time, a LOCAL-TIME:TIMESTAMP.

auth-basic

Source

Symbol: LACK/MIDDLEWARE/AUTH/BASIC:*LACK-MIDDLEWARE-AUTH-BASIC*

The auth-basic middleware implements the Basic Authentication protocol, which proceeds as follows: when the client sends a request to access a protected resource, the server responds with a 401 response status code along with the “www-authenticate” header to indicate that the endpoint requires Basic Authentication (example response header: “www-authenticate: Basic realm=example-realm”), which causes the browser to display a dialogue for username and password input. When the user submits the dialogue, the browser submits another request and includes the Authorization header that includes the base64 encoded username and password, like: “Authorization: Basic ”.

The AUTHENTICATOR keyword parameter is required for the auth-basic middleware, which must be a function that takes two parameters: the user identifier and the password. The authenticator function returns one or two values, the first representing whether the operation was successful, and the second optional value the user; on success, the middleware sets the REMOTE-USER key in the Clack environment, setting the value to the either the second returned value (when present), or else the username.

The optional keyword parameter REALM should be a string, and defaults to “restricted area”. This parameter represents the realm returned in the “www-authenticate” response header.

backtrace

Source

Symbol: LACK/MIDDLEWARE/BACKTRACE:*LACK-MIDDLEWARE-BACKTRACE*

The backtrace middleware wraps the Clack app in a HANDLER-BIND statement and defines a single handler for ERROR conditions, meaning that the handler will run when any error is signalled. By default, the printed error is logged to *ERROR-OUTPUT*, but this can be overridden by defining optional keyword parameter OUTPUT. Optional keyword parameter RESULT-ON-ERROR is NIL by default and may be assigned to either a function or a Clack response list; when a list is given and an error is signalled, the middleware returns this list as the response instead of calling the next middleware.

csrf

Source

Symbol: LACK/MIDDLEWARE/CSRF:*LACK-MIDDLEWARE-CSRF*

CSRF middleware is an implementation of the synchronizer token pattern used to protect against cross-site request forgery attacks. It requires the session hash-table associated with environment key LACK.SESSION to be set, meaning the app first needs to be wrapped with the Lack session middleware before being wrapped with CSRF middleware.

When the request is made with an unsafe HTTP method (POST, PUT, PATCH, DELETE), the middleware finds the synchronizer token stored in the session hash under the key specified by keyword parameter SESSION-KEY, which defaults to “csrftoken”, and when found, compares the value of the session token to the CSRF parameter from the request body. Keyword parameter SESSION-TOKEN determines the parameter in the request body that contains the submitted token, which also defaults to “csrftoken”.

When the tokens strings match, the middleware proceeds to the next middleware, otherwise returning a 400 response. The error response can be customized by setting optional keyword parameter BLOCK-APP, a function that takes the Clack environment and returns a Clack response list. When provided with a non-NIL value, optional keyword parameter ONE-TIME causes the token stored in the session to be deleted after a successful comparison.

The package also exports functions CSRF-TOKEN and CSRF-HTML-TAG. Call CSRF-TOKEN to retrieve the synchronizer token from the session. Call CSRF-HTML-TAG to return a hidden form input HTML string that contains the synchronizer token as its value; the hidden field should be included in every form submission handled by the application.

dbpool

Source

Symbol: LACK/MIDDLEWARE/DBPOOL:*LACK-MIDDLEWARE-DBPOOL*

The dbpool middleware depends on libraries anypool, which provides a generic approach to connection pooling based on user-provided callbacks, and cl-dbi, which provides an interface for accessing a DBMS (SQLite3, MySQL, and PostgreSQL are supported). The dbpool middleware requires providing a symbol representing the database as an ordinal parameter. Keyword parameter CONNECT-ARGS is also required and is a plist passed as the arguments to function DBI:CONNECT. Optional keyword POOL-ARGS is also a plist, and is passed to function ANYPOOL:MAKE-POOL, allowing for pool customization with options MAX-OPEN-COUNT, MAX-IDLE-COUNT, TIMEOUT, and IDLE-TIMEOUT.

The package also exports macro WITH-CONNECTION, that you can use in your route handlers to checkout a connection from the specified pool. The macro takes a two-item list followed by the body. The list contains first a name for the variable for which to bind to the connection, and second the database symbol established in the middleware configuration. A HANDLER-BIND is defined with a handler for ANYPOOL:TOO-MANY-OPEN-CONNECTION: when this error is signalled, a 503 Clack response is returned instead of calling the next middleware.

;; NOTE: This assumes that maindb was specified as the database-id (the
;; ordinal parameter) in the middleware configuration.

(with-connection (conn 'maindb)
  (format t "Connected to database.~%")
  ;; Code to perform database operations using the connection "conn"
  )

mount

Source

Symbol: LACK/MIDDLEWARE/MOUNT:*LACK-MIDDLEWARE-MOUNT*

The mount middleware allows for mounting another Clack application at a specific URL path prefix within the parent application, specified by ordinal parameter PATH. The next ordinal parameter, MOUNT-APP, represents the application to mount; it can be either a standard function that takes the Clack environment as its only parameter, or a CLOS instance that inherits from LACK/COMPONENT:LACK-COMPONENT.

When the middleware processes the request, it compares the current path (from the PATH-INFO environment key) to the prefix specified by PATH. On an exact match, the middleware updates PATH-INFO to “/” and calls the mounted application; when the current path otherwise starts with the prefix identified by PATH, it extracts the remaining path information, updates PATH-INFO to the extracted remainder, and and calls the mounted application. If the path does not match the mounted application’s path prefix, the middleware calls the parent application as normal.

session

Source

Symbol: LACK/MIDDLEWARE/SESSION:*LACK-MIDDLEWARE-SESSION*

The session store adds a hash-table under the LACK.SESSION environment key for storing session data; the middleware also adds LACK.SESSION.OPTIONS to the environment, a list which may be updated to control session behavior.

Keyword parameter STORE signifies which backend to use, defaulting to the memory store. Alternative store implementations include cl-redis and cl-dbi based stores. Keyword parameter STATE signifies the state management mechanism, defaulting to cookies, and this is the only implementation defined.

Example: set a cookie named “_sid” with session state stored in process memory.

;; NOTE: the store parameter here is optional and reflects the default value

(funcall lack/middleware/session:*lack-middleware-session*
         *app*
         :store (lack/session/store/memory:make-memory-store)
         :state (lack/session/state/cookie:make-cookie-state
                 :cookie-key "_sid"
                 :path "/"
                 :domain "localhost"
                 :expires (+ 600 (get-universal-time))
                 :httponly t
                 :secure nil
                 :samesite :strict))

static

Source

Symbol: LACK/MIDDLEWARE/STATIC:*LACK-MIDDLEWARE-STATIC*

Static middleware serves static files from disk and is intended primarily for use in development; in production, it is recommended to serve static files from a CDN or a server optimized for serving files such as NGINX. The middleware takes two keyword parameters, both of which are optional.

Keyword parameter ROOT, which defaults to the current directory, represents the location of the static files on disk to be served by the application; set it to a pathname if your static files are located outside of the current directory.

Optional keyword parameter PATH may be either NIL, a string, or a function that returns a string or NIL. When PATH is provided as a string and the request path starts with the specified string, the middleware updates the PATH-INFO key in the environment and serves the static file. When PATH is specified as a function, it takes takes a single parameter representing the current path; when the function returns a (string) value, the middleware updates the PATH-INFO key in the environment calls the CALL-APP-FILE function to serve the file.

Lack Builder

Lack Builder can be used to effortlessly compose a middleware pipeline, avoiding cumbersome nested FUNCALL expressions.

The final argument to macro LACK:BUILDER is the Clack application to be wrapped, and every other argument is either a keyword or a list representing the middleware to place in the chain. Lists allow passing arguments to the middleware function.

Lack uses a naming convention to look up the middleware function for each specified middleware item. If the pipeline were to include keyword EXAMPLE, Lack would search for the following symbol: LACK/MIDDLEWARE/EXAMPLE:*LACK-MIDDLEWARE-EXAMPLE*.

(lack:builder
  (:session :state (lack/session/state/cookie:make-cookie-state
                    :cookie-key "_sid"
                    :path "/"
                    :domain "localhost"
                    :httponly t
                    :secure nil
                    :samesite :strict))
  :csrf
  (:backtrace :result-on-error `(500
                                 (:content-type "text/plain"
                                  :content-length 21)
                                 ("Internal Server Error")))
  *app*)