Skip to content

Commit 1fe5aca

Browse files
authored
vweb: middleware implementation (#17730)
1 parent 713c95f commit 1fe5aca

File tree

11 files changed

+968
-19
lines changed

11 files changed

+968
-19
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>@title</title>
8+
</head>
9+
<body>
10+
<nav>
11+
<a href="/">Home</a>
12+
<a href="/admin/secrets">"/admin/secrets"</a>
13+
<a href="/admin/dynamic">"/admin/dynamic"</a>
14+
<a href="/early">Early exit</a>
15+
</nav>
16+
<main>
17+
@content
18+
</main>
19+
<footer></footer>
20+
</body>
21+
</html>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>Early exit</p>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
3+
<h1>Hello </h1>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>Super secret stuff</p>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
module main
2+
3+
import vweb
4+
5+
// for another example see vlib/vweb/tests/middleware_test_server.v
6+
const (
7+
http_port = 8080
8+
)
9+
10+
struct App {
11+
vweb.Context
12+
middlewares map[string][]vweb.Middleware
13+
mut:
14+
is_authenticated bool
15+
}
16+
17+
fn main() {
18+
mut app := new_app()
19+
vweb.run(app, http_port)
20+
}
21+
22+
fn new_app() &App {
23+
mut app := &App{
24+
middlewares: {
25+
// chaining is allowed, middleware will be evaluated in order
26+
'/admin/': [other_func1, other_func2]
27+
'/early': [middleware_early]
28+
}
29+
}
30+
31+
// do stuff with app
32+
// ...
33+
return app
34+
}
35+
36+
['/']
37+
pub fn (mut app App) index() vweb.Result {
38+
println('Index page')
39+
title := 'Home Page'
40+
41+
content := $tmpl('templates/index.html')
42+
base := $tmpl('templates/base.html')
43+
return app.html(base)
44+
}
45+
46+
[middleware: check_auth]
47+
['/admin/secrets']
48+
pub fn (mut app App) secrets() vweb.Result {
49+
println('Secrets page')
50+
title := 'Secret Admin Page'
51+
52+
content := $tmpl('templates/secret.html')
53+
base := $tmpl('templates/base.html')
54+
return app.html(base)
55+
}
56+
57+
['/admin/:sub']
58+
pub fn (mut app App) dynamic(sub string) vweb.Result {
59+
println('Dynamic page')
60+
title := 'Secret dynamic'
61+
62+
content := sub
63+
base := $tmpl('templates/base.html')
64+
return app.html(base)
65+
}
66+
67+
['/early']
68+
pub fn (mut app App) early() vweb.Result {
69+
println('Early page')
70+
title := 'Early Exit'
71+
72+
content := $tmpl('templates/early.html')
73+
base := $tmpl('templates/base.html')
74+
return app.html(base)
75+
}
76+
77+
// is always executed first!
78+
pub fn (mut app App) before_request() {
79+
app.is_authenticated = false
80+
println('0')
81+
}
82+
83+
pub fn (mut app App) check_auth() bool {
84+
println('3')
85+
if app.is_authenticated == false {
86+
app.redirect('/')
87+
}
88+
return app.is_authenticated
89+
}
90+
91+
fn other_func1(mut ctx vweb.Context) bool {
92+
println('1')
93+
return true
94+
}
95+
96+
fn other_func2(mut ctx vweb.Context) bool {
97+
println('2')
98+
99+
// ...
100+
return true
101+
}
102+
103+
fn middleware_early(mut ctx vweb.Context) bool {
104+
println('4')
105+
ctx.text(':(')
106+
107+
// returns false, so the middleware propogation is stopped and the user will see the text ":("
108+
return false
109+
}

vlib/vweb/README.md

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,10 @@ pub fn (mut app App) controller_get_user_by_id() vweb.Result {
252252
```
253253
### Middleware
254254

255-
V haven't a well defined middleware.
256-
For now, you can use `before_request()`. This method called before every request.
257-
Probably you can use it for check user session cookie or add header
255+
Vweb has different kinds of middleware.
256+
The `before_request()` method is always called before every request before any
257+
other middleware is processed. You could use it to check user session cookies or to add a header.
258+
258259
**Example:**
259260

260261
```v ignore
@@ -263,24 +264,117 @@ pub fn (mut app App) before_request() {
263264
}
264265
```
265266

267+
Middleware functions can be passed directly when creating an App instance and is
268+
executed when the url starts with the defined key.
269+
270+
In the following example, if a user navigates to `/path/to/test` the middleware
271+
is executed in the following order: `middleware_func`, `other_func`, `global_middleware`.
272+
The middleware is executed in the same order as they are defined and if any function in
273+
the chain returns `false` the propogation is stopped.
274+
275+
**Example:**
276+
```v
277+
module main
278+
279+
import vweb
280+
281+
struct App {
282+
vweb.Context
283+
middlewares map[string][]vweb.Middleware
284+
}
285+
286+
fn new_app() &App {
287+
mut app := &App{
288+
middlewares: {
289+
// chaining is allowed, middleware will be evaluated in order
290+
'/path/to/': [middleware_func, other_func]
291+
'/': [global_middleware]
292+
}
293+
}
294+
295+
// do stuff with app
296+
// ...
297+
return app
298+
}
299+
300+
fn middleware_func(mut ctx vweb.Context) bool {
301+
// ...
302+
return true
303+
}
304+
305+
fn other_func(mut ctx vweb.Context) bool {
306+
// ...
307+
return true
308+
}
309+
310+
fn global_middleware(mut ctx vweb.Context) bool {
311+
// ...
312+
return true
313+
}
314+
```
315+
316+
Middleware functions will be of type `vweb.Middleware` and are not methods of App,
317+
so they could also be imported from other modules.
318+
```v ignore
319+
pub type Middleware = fn (mut Context) bool
320+
```
321+
322+
Middleware can also be added to route specific functions via attributes.
323+
324+
**Example:**
325+
```v ignore
326+
[middleware: check_auth]
327+
['/admin/data']
328+
pub fn (mut app App) admin() vweb.Result {
329+
// ...
330+
}
331+
332+
// check_auth is a method of App, so we don't need to pass the context as parameter.
333+
pub fn (mut app App) check_auth () bool {
334+
// ...
335+
return true
336+
}
337+
```
338+
For now you can only add 1 middleware to a route specific function via attributes.
339+
266340
### Redirect
267341

268342
Used when you want be redirected to an url
343+
269344
**Examples:**
270345

271346
```v ignore
272347
pub fn (mut app App) before_request() {
273-
app.user_id = app.get_cookie('id') or { app.redirect('/') }
348+
app.user_id = app.get_cookie('id') or { app.redirect('/') }
274349
}
275350
```
276351

277352
```v ignore
278353
['/articles'; get]
279354
pub fn (mut app App) articles() vweb.Result {
280-
if !app.token {
281-
app.redirect('/login')
282-
}
283-
return app.text("patatoes")
355+
if !app.token {
356+
app.redirect('/login')
357+
}
358+
return app.text('patatoes')
359+
}
360+
```
361+
362+
You can also combine middleware and redirect.
363+
364+
**Example:**
365+
366+
```v ignore
367+
[middleware: with_auth]
368+
['/admin/secret']
369+
pub fn (mut app App) admin_secret() vweb.Result {
370+
// this code should never be reached
371+
return app.text('secret')
372+
}
373+
374+
['/redirect']
375+
pub fn (mut app App) with_auth() bool {
376+
app.redirect('/auth/login')
377+
return false
284378
}
285379
```
286380

vlib/vweb/parse.v

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import net.urllib
44
import net.http
55

66
// Parsing function attributes for methods and path.
7-
fn parse_attrs(name string, attrs []string) !([]http.Method, string) {
7+
fn parse_attrs(name string, attrs []string) !([]http.Method, string, string) {
88
if attrs.len == 0 {
9-
return [http.Method.get], '/${name}'
9+
return [http.Method.get], '/${name}', ''
1010
}
1111

1212
mut x := attrs.clone()
1313
mut methods := []http.Method{}
14+
mut middleware := ''
1415
mut path := ''
1516

1617
for i := 0; i < x.len; {
@@ -30,6 +31,11 @@ fn parse_attrs(name string, attrs []string) !([]http.Method, string) {
3031
x.delete(i)
3132
continue
3233
}
34+
if attr.starts_with('middleware:') {
35+
middleware = attr.all_after('middleware:').trim_space()
36+
x.delete(i)
37+
continue
38+
}
3339
i++
3440
}
3541
if x.len > 0 {
@@ -44,7 +50,7 @@ fn parse_attrs(name string, attrs []string) !([]http.Method, string) {
4450
path = '/${name}'
4551
}
4652
// Make path lowercase for case-insensitive comparisons
47-
return methods, path.to_lower()
53+
return methods, path.to_lower(), middleware
4854
}
4955

5056
fn parse_query_from_url(url urllib.URL) map[string]string {

0 commit comments

Comments
 (0)