Skip to content

Commit e2f2a1c

Browse files
committed
mv decorators and caveman session around
1 parent c5c3915 commit e2f2a1c

File tree

3 files changed

+156
-154
lines changed

3 files changed

+156
-154
lines changed

content/building-blocks/routing.md

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
+++
2-
title = "Routing"
2+
title = "Routes and URL parameters"
33
weight = 16
44
+++
55

@@ -8,6 +8,11 @@ routes, as we did in the tutorial, so skip to its section below if you
88
want. However, it can only be benificial to know the built-in
99
Hunchentoot ways.
1010

11+
{{% notice info %}}
12+
Please see the tutorial where we define routes with **path parameters**
13+
and where we also access **URL parameters**.
14+
{{% /notice %}}
15+
1116

1217
## Hunchentoot
1318

@@ -134,34 +139,86 @@ Hunchentoot to convert the argument to this given type. For example,
134139
defining an `:integer` will give you an integer and not a string (all
135140
URL parameters are given as a string by default, but more on that on the next section).
136141

137-
**Decorators** are functions that are executed before the route body. They
138-
should call the `next` parameter function to continue executing the
139-
decoration chain and the route body finally. Examples:
142+
### easy-routes' decorators
140143

141-
~~~lisp
142-
;; Ensure a user is logged in:
143-
(defun @auth (next)
144-
(let ((*user* (hunchentoot:session-value 'user)))
145-
(if (not *user*)
146-
(hunchentoot:redirect "/login")
147-
(funcall next))))
148-
149-
;; Define content types:
150-
(defun @html (next)
151-
(setf (hunchentoot:content-type*) "text/html")
152-
(funcall next))
144+
`easy-routes` provides us with an easy way to call any function before
145+
the route body. Following the naming of a popular language, they are
146+
called "decorators".
147+
148+
In the end route definitions are only functions, right? Decorators are
149+
only functions too, but they are run before the route body.
150+
151+
Remember the shape of our routes:
152+
153+
```lisp
154+
(easy-routes:defroute root ("/") ()
155+
"hello app")
156+
```
157+
158+
We add a list of decorators after the `"/"` part, like this:
159+
160+
```lisp
161+
(defroute my-protected-route ("/foo" :method :get
162+
:decorators ((@json))) ()
163+
"hello decorated route")
164+
```
153165

166+
but what is `@json`? It's a function:
167+
168+
```lisp
154169
(defun @json (next)
155170
(setf (hunchentoot:content-type*) "application/json")
156171
(funcall next))
172+
```
173+
174+
You can ignore the `@` sign, it doesn't mean anything in Common Lisp
175+
(but as it's part of the function name it can help you reference all
176+
your route decorators).
157177

178+
Route "decorators" must accept at least one argument: here called `next`, it is the
179+
function that will be called next (so, at one point, our route body)
180+
*if we want to*. Look at `(funcall next)`: our decorator correctly
181+
calls it.
182+
183+
If you declare a list of decorators, calling "next" will get you
184+
through the chain of decorator functions, and finally to the route
185+
body (if no "decorator" exited before).
186+
187+
So what it is function doing? Keep it mind that it is called in the
188+
context of a web request. So we can call `hunchentoot:content-type*`
189+
(note the `*`, this function is applied on the current web
190+
request). We are setting this request's content-type to
191+
`application/json`.
192+
193+
Yes, you can copy-paste the `setf` line directly into your function.
194+
195+
Here's another "decorator":
196+
197+
```lisp
198+
(defun @auth (next)
199+
(let ((user (hunchentoot:session-value 'user)))
200+
(if (not user)
201+
(hunchentoot:redirect "/login")
202+
(funcall next))))
203+
```
204+
205+
Now that's interesting. It's doing this:
206+
- it gets a value from the current web session. This can be any Lisp object.
207+
- if a user was registered in the session, we call the `next` method to run other decorators and the route body
208+
- otherwise, we redirect to the login page.
209+
210+
We use them in the "User log in" section.
211+
212+
Here's another decorator from easy-routes' README:
213+
214+
```lisp
158215
;; Ensure our PostgreSQL database is connected:
159216
(defun @db (next)
160217
(postmodern:with-connection *db-spec*
161218
(funcall next)))
162-
~~~
219+
```
163220

164-
I mostly use the `@auth` and `@json` decorators, which I'll demo later. See the `easy-routes` readme for more.
221+
See the `easy-routes` readme for more.
165222

166223

167224
## Accessing GET and POST parameters
@@ -170,7 +227,7 @@ You probably have nothing to do to get the value of those parameters: if you def
170227

171228
However, here's how to interact more with URL parameters. In particular, we can define the default type of a parameter: they are strings by default, but we can ask to receive an integer.
172229

173-
### Hunchentoot URL parameters
230+
### Hunchentoot and easy-routes URL parameters
174231

175232
First of all, note that we can access query parameters anytime with
176233

content/building-blocks/user-log-in.md

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ both the login page or the "dashboard" for logged-in users.
2828

2929
<!-- - when we don't find a user ID in the session, we render the log-in page -->
3030

31-
Let's start with the model functions.
3231

3332
We use these libraries:
3433

@@ -43,6 +42,7 @@ this at the top of your file:
4342
(in-package :myproject)
4443
```
4544

45+
Let's start with the model functions.
4646

4747
## Get users
4848

@@ -63,6 +63,30 @@ later, we focus on the web stack. Here we return a user object, a
6363
(string= password (getf user :password)))))
6464
~~~
6565

66+
Look, what if we stored our own name and password in a file? No need
67+
of a DB for a personal or a toy web app.
68+
69+
In `creds.lisp-expr`:
70+
71+
```lisp
72+
(:name "me" :password "yadadada")
73+
```
74+
75+
the ".lisp-expr" is just a convention, so that your tools won't see it
76+
as a lisp source.
77+
78+
Read it back in with `uiop:read-file-form`:
79+
80+
```lisp
81+
(defparameter *me* (uiop:read-file-form "creds.lisp-expr"))
82+
83+
(getf *me* :name)
84+
;; => "me"
85+
```
86+
87+
Cool? my 2c.
88+
89+
6690
## Templates: login, welcome
6791

6892
For convenience we again define our templates as strings.
@@ -141,7 +165,7 @@ You can start with this route:
141165
We are simply querying the session for the user name. If it's present,
142166
that means we have established it at login.
143167

144-
Now is a great time to use easy-routes' "decorators".
168+
Now is a great time to use easy-routes' "decorators" (see the [Routing](/building-blocks/routing/) section).
145169

146170
We can shorten the route to this:
147171

@@ -294,10 +318,10 @@ This is the equivalent Hunchentoot route:
294318
Remarks:
295319

296320
- we can't dispatch on the request type, so we use the `ecase` on `request-method*`
297-
- we can't use "decorators" so if use branching
321+
- we can't use "decorators" so we use branching
298322
- it isn't very clear but `name` and `password` are only used in the POST part.
299323
- we can also use `(hunchentoot:post-parameter "name")` (the parameter as a string)
300-
- otherwise, it's pretty similar.
324+
- all this adds nesting in our function but otherwise, it's pretty similar.
301325

302326
## Full code
303327

@@ -416,3 +440,55 @@ Remarks:
416440
(defun stop-server ()
417441
(hunchentoot:stop *server*))
418442
```
443+
444+
## Caveman
445+
446+
In Caveman, `*session*` is a hash-table that represents the session's
447+
data. Here are our login and logout functions:
448+
449+
```lisp
450+
(defun login (user)
451+
"Log the user into the session"
452+
(setf (gethash :user *session*) user))
453+
454+
(defun logout ()
455+
"Log the user out of the session."
456+
(setf (gethash :user *session*) nil))
457+
```
458+
459+
We define a simple predicate:
460+
461+
```lisp
462+
(defun logged-in-p ()
463+
(gethash :user cm:*session*))
464+
```
465+
466+
We don't know a mechanism as easy-routes' "decorators" but we define a
467+
`with-logged-in` macro:
468+
469+
```lisp
470+
(defmacro with-logged-in (&body body)
471+
`(if (logged-in-p)
472+
(progn ,@body)
473+
(render #p"login.html"
474+
'(:message "Please log-in to access this page."))))
475+
```
476+
477+
If the user isn't logged in, there will be nothing stored in the session store,
478+
and we render the login page. When all is well, we execute the macro's
479+
body. We use it like this:
480+
481+
```lisp
482+
(defroute "/account/logout" ()
483+
"Show the log-out page, only if the user is logged in."
484+
(with-logged-in
485+
(logout)
486+
(render #p"logout.html")))
487+
488+
(defroute ("/account/review" :method :get) ()
489+
(with-logged-in
490+
(render #p"review.html"
491+
(list :review (get-review (gethash :user *session*))))))
492+
```
493+
494+
and so on.

content/building-blocks/users-and-passwords.md

Lines changed: 0 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -26,137 +26,6 @@ If you use a database, you'll have to create at least to save users. A
2626
- optionally, a key to the table listing roles.
2727

2828

29-
## Checking a user is logged-in
30-
31-
<!-- https://www.reddit.com/r/Common_Lisp/comments/1f7bfql/simple_session_management_with_hunchentoot/ -->
32-
33-
A framework provides a way to work with sessions.
34-
35-
Our web app defines routes. Routes are, in the end, only functions.
36-
37-
We must find a way to check for users before the route function. If
38-
our check is successful (the user registered in the current session)
39-
is logged-in, we call the next function (the route body). If our check
40-
is falsy, we don't execute the route but we redirect to the login page
41-
instead. Simple right?
42-
43-
44-
### Hunchentoot and easy-routes
45-
46-
`easy-routes` provides us with an easy way to call any function before
47-
the route body. Following the naming of a popular language, they are
48-
called "decorators".
49-
50-
Remember the shape of our routes:
51-
52-
```lisp
53-
(easy-routes:defroute root ("/") ()
54-
"hello app")
55-
```
56-
57-
We add a list of decorators after the `"/"` part, like this:
58-
59-
```lisp
60-
(defroute my-protected-route ("/foo" :method :get
61-
:decorators ((@json)))
62-
()
63-
"hello decorated route")
64-
```
65-
66-
but what is `@json`? It's a function:
67-
68-
```lisp
69-
(defun @json (next)
70-
(setf (hunchentoot:content-type*) "application/json")
71-
(funcall next))
72-
```
73-
74-
You can ignore the `@` sign, it doesn't mean anything in Common Lisp
75-
(but as it's part of the function name it can help you reference all
76-
your route decorators).
77-
78-
Route "decorators" must accept at least one argument: `next`, the
79-
function that will be called next (so, at one point, our route body)
80-
*if we want to*. Look at `(funcall next)`: our decorator correctly
81-
calls it.
82-
83-
So what it is function doing? Keep it mind that it is called in the
84-
context of a web request. So we can call `hunchentoot:content-type*`
85-
(note the `*`, this function is applied on the current web
86-
request). We are setting this request's content-type to
87-
`application/json`.
88-
89-
Yes, you can copy-paste the `setf` line directly into your function.
90-
91-
Here's another "decorator" (straight from easy-routes' documentation):
92-
93-
```lisp
94-
(defun @auth (next)
95-
(let ((*user* (hunchentoot:session-value 'user)))
96-
(if (not *user*)
97-
(hunchentoot:redirect "/login")
98-
(funcall next))))
99-
```
100-
101-
Now that's interesting. It's doing this:
102-
- it gets a value from the current web session. This can be any Lisp object.
103-
- it binds it to a global variable (we suppose the application re-uses it later) (global variables are thread-local)
104-
- if a user was registered in the session, we call the `next` method to run other decorators and the route body
105-
- otherwise, we redirect to the login page.
106-
107-
108-
### Caveman
109-
110-
In Caveman, `*session*` is a hash table that represents the session's
111-
data. Here are our login and logout functions:
112-
113-
```lisp
114-
(defun login (user)
115-
"Log the user into the session"
116-
(setf (gethash :user *session*) user))
117-
118-
(defun logout ()
119-
"Log the user out of the session."
120-
(setf (gethash :user *session*) nil))
121-
```
122-
123-
We define a simple predicate:
124-
125-
```lisp
126-
(defun logged-in-p ()
127-
(gethash :user cm:*session*))
128-
```
129-
130-
We don't know a mechanism as easy-routes' "decorators" but we define a
131-
`with-logged-in` macro:
132-
133-
```lisp
134-
(defmacro with-logged-in (&body body)
135-
`(if (logged-in-p)
136-
(progn ,@body)
137-
(render #p"login.html"
138-
'(:message "Please log-in to access this page."))))
139-
```
140-
141-
If the user isn't logged in, there will be nothing stored in the session store,
142-
and we render the login page. When all is well, we execute the macro's
143-
body. We use it like this:
144-
145-
```lisp
146-
(defroute "/account/logout" ()
147-
"Show the log-out page, only if the user is logged in."
148-
(with-logged-in
149-
(logout)
150-
(render #p"logout.html")))
151-
152-
(defroute ("/account/review" :method :get) ()
153-
(with-logged-in
154-
(render #p"review.html"
155-
(list :review (get-review (gethash :user *session*))))))
156-
```
157-
158-
and so on.
159-
16029

16130
## Encrypting passwords
16231

0 commit comments

Comments
 (0)