RSS Feed

Lisp Project of the Day

clack-pretend

You can support this project by donating at:

Donate using PatreonDonate using Liberapay

Or see the list of project sponsors.

clack-pretendweb

Documentation😀
Docstrings🥺
Tests 🥺
Examples🥺
RepositoryActivity🥺
CI 🥺

This is the last middleware in our Clack/Lack series. What does it do? It helps during website development remembering last requests you did from the browser and allowing to replay them from the REPL.

Clack-pretend interposes itself into a Lack middlewares chain. To define the app you need to use a special builder macro and to specify at which point requests and responses should be captured:

POFTHEDAY> (defparameter *app*
             (clack-pretend:pretend-builder (:insert 2)
               :accesslog
               :session
               (lambda (env)
                 (let* ((path (getf env :path-info))
                        (query (getf env :query-string))
                        (message (format nil "Path: ~A, query: ~A"
                                         path query)))
                   (format t "Processing request:~%  ~A~%"
                           message)
                   '(200 (:content-type "text/plain")
                     ("Hello world!"))))))
*APP*
POFTHEDAY> (defparameter *server*
             (clack:clackup *app*
                            :port 8000))
Hunchentoot server is started.
Listening on 127.0.0.1:8000.

Now I'll make a request using curl:

[poftheday] curl -v 'http://localhost:8000/some/route?foo=Bar'
> GET /some/route?foo=Bar HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.54.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Wed, 01 Jul 2020 19:23:12 GMT
< Server: Hunchentoot 1.2.38
< Transfer-Encoding: chunked
< Content-Type: text/plain
< Set-Cookie: lack.session=0d629e3a1d2681d99c40f7b2086ec97d53e2b884; path=/; expires=Sat, 31 Dec 2140 14:45:27 GMT

And we can look up what was the last request:

POFTHEDAY> (clack-pretend:last-input)
(:LACK.SESSION.OPTIONS
 (:ID "0d629e3a1d2681d99c40f7b2086ec97d53e2b884"
  :NEW-SESSION T :CHANGE-ID NIL :EXPIRE NIL)
 :LACK.SESSION #<HASH-TABLE :TEST EQUAL :COUNT 0 {1005EB8A03}>
 :REQUEST-METHOD :GET
 :SCRIPT-NAME ""
 :PATH-INFO "/some/route"
 :SERVER-NAME "localhost"
 :SERVER-PORT 8000
 :SERVER-PROTOCOL :HTTP/1.1
 :REQUEST-URI "/some/route?foo=Bar"
 :URL-SCHEME "http"
 :REMOTE-ADDR "127.0.0.1"
 :REMOTE-PORT 53671
 :QUERY-STRING "foo=Bar"
 :RAW-BODY #<FLEXI-STREAMS:FLEXI-IO-STREAM {1005EB6FD3}>
 :CONTENT-LENGTH NIL
 :CONTENT-TYPE NIL
 :CLACK.STREAMING T
 :CLACK.IO #<CLACK.HANDLER.HUNCHENTOOT::CLIENT {1005EB7043}>
 :HEADERS #<HASH-TABLE :TEST EQUAL :COUNT 3 {1005EB72C3}>
 :QUERY-PARAMETERS (("foo" . "Bar")))

POFTHEDAY> (rutils:hash-table-to-alist
            (getf * :headers))
(("host" . "localhost:8000")
 ("user-agent" . "curl/7.54.0")
 ("accept" . "*/*"))

Now it is time to replay the request from the REPL:

POFTHEDAY> (clack-pretend:run-pretend)
Processing request:
  Path: /some/route, query: foo=Bar
(200 (:CONTENT-TYPE "text/plain") ("Hello world!"))

;; You can override path to check, it with the same
;; headers and session:
POFTHEDAY> (clack-pretend:run-pretend
            :path-info "/other/path")
Processing request:
  Path: /other/path, query: foo=Bar
(200 (:CONTENT-TYPE "text/plain") ("Hello world!"))

Seems, clack-pretend is a great addition for web development with Clack.

It will be interesting to improve it to store not only the last N successful requests but also to store requests resulting unhandled error. This way you'll be able to replay errors your users experience in production!


Brought to you by 40Ants under Creative Commons License