From 838083e6108d160df4742696b45e1cf74dd9452a Mon Sep 17 00:00:00 2001
From: Casper Kuethe <43839798+Casper64@users.noreply.github.com>
Date: Tue, 11 Apr 2023 23:50:03 +0200
Subject: [PATCH] vweb: add an overridable .not_found() method, for making a
custom 404 page + tests fixes (#17936)
---
vlib/vweb/README.md | 13 +++++++++++
vlib/vweb/tests/controller_duplicate_server.v | 2 +-
vlib/vweb/tests/controller_test.v | 18 ++++++++++++---
vlib/vweb/tests/controller_test_server.v | 22 ++++++++++++++-----
vlib/vweb/tests/vweb_test.v | 11 +++++-----
vlib/vweb/tests/vweb_test_server.v | 6 +++++
vlib/vweb/vweb.v | 2 +-
7 files changed, 58 insertions(+), 16 deletions(-)
diff --git a/vlib/vweb/README.md b/vlib/vweb/README.md
index 7aa2fc3af1..6bf5250e8b 100644
--- a/vlib/vweb/README.md
+++ b/vlib/vweb/README.md
@@ -378,6 +378,19 @@ pub fn (mut app App) with_auth() bool {
}
```
+### Fallback route
+You can implement a fallback `not_found` route that is called when a request is made and no
+matching route is found.
+
+**Example:**
+
+``` v ignore
+pub fn (mut app App) not_found() vweb.Result {
+ app.set_status(404, 'Not Found')
+ return app.html('
Page not found
')
+}
+```
+
### Controllers
Controllers can be used to split up app logic so you are able to have one struct
per `"/"`. E.g. a struct `Admin` for urls starting with `"/admin"` and a struct `Foo`
diff --git a/vlib/vweb/tests/controller_duplicate_server.v b/vlib/vweb/tests/controller_duplicate_server.v
index 3bb22e3bd6..ced0a75802 100644
--- a/vlib/vweb/tests/controller_duplicate_server.v
+++ b/vlib/vweb/tests/controller_duplicate_server.v
@@ -14,7 +14,7 @@ struct Admin {
}
['/admin/duplicate']
-fn (mut app App) duplicate() vweb.Result {
+pub fn (mut app App) duplicate() vweb.Result {
return app.text('duplicate')
}
diff --git a/vlib/vweb/tests/controller_test.v b/vlib/vweb/tests/controller_test.v
index a4e4a72574..5fc20d807f 100644
--- a/vlib/vweb/tests/controller_test.v
+++ b/vlib/vweb/tests/controller_test.v
@@ -1,9 +1,6 @@
import os
import time
-import json
-import net
import net.http
-import io
const (
sport = 12382
@@ -91,6 +88,21 @@ fn test_other_path() {
assert x.body == 'Other path'
}
+fn test_different_404() {
+ res_app := http.get('http://${localserver}/zxcnbnm') or { panic(err) }
+ assert res_app.status() == .not_found
+ assert res_app.body == '404 From App'
+
+ res_admin := http.get('http://${localserver}/admin/JHKAJA') or { panic(err) }
+ assert res_admin.status() == .not_found
+ assert res_admin.body == '404 From Admin'
+
+ // Other doesn't have a custom 404 so the vweb.Context's not_found is expected
+ res_other := http.get('http://${localserver}/other/unknown') or { panic(err) }
+ assert res_other.status() == .not_found
+ assert res_other.body == '404 Not Found'
+}
+
fn test_shutdown() {
// This test is guaranteed to be called last.
// It sends a request to the server to shutdown.
diff --git a/vlib/vweb/tests/controller_test_server.v b/vlib/vweb/tests/controller_test_server.v
index 9cb0d8aa52..5caabbbf69 100644
--- a/vlib/vweb/tests/controller_test_server.v
+++ b/vlib/vweb/tests/controller_test_server.v
@@ -46,32 +46,42 @@ fn main() {
}
['/']
-fn (mut app App) home() vweb.Result {
+pub fn (mut app App) home() vweb.Result {
return app.text('App')
}
['/path']
-fn (mut app App) app_path() vweb.Result {
+pub fn (mut app App) app_path() vweb.Result {
return app.text('App path')
}
+pub fn (mut app App) not_found() vweb.Result {
+ app.set_status(404, 'Not Found')
+ return app.text('404 From App')
+}
+
['/']
-fn (mut app Admin) admin_home() vweb.Result {
+pub fn (mut app Admin) admin_home() vweb.Result {
return app.text('Admin')
}
['/path']
-fn (mut app Admin) admin_path() vweb.Result {
+pub fn (mut app Admin) admin_path() vweb.Result {
return app.text('Admin path')
}
+pub fn (mut app Admin) not_found() vweb.Result {
+ app.set_status(404, 'Not Found')
+ return app.text('404 From Admin')
+}
+
['/']
-fn (mut app Other) other_home() vweb.Result {
+pub fn (mut app Other) other_home() vweb.Result {
return app.text('Other')
}
['/path']
-fn (mut app Other) other_path() vweb.Result {
+pub fn (mut app Other) other_path() vweb.Result {
return app.text('Other path')
}
diff --git a/vlib/vweb/tests/vweb_test.v b/vlib/vweb/tests/vweb_test.v
index 2cb62094e7..0ee17190eb 100644
--- a/vlib/vweb/tests/vweb_test.v
+++ b/vlib/vweb/tests/vweb_test.v
@@ -124,14 +124,16 @@ fn test_http_client_index() {
}
fn test_http_client_404() {
+ server := 'http://${localserver}'
url_404_list := [
- 'http://${localserver}/zxcnbnm',
- 'http://${localserver}/JHKAJA',
- 'http://${localserver}/unknown',
+ '/zxcnbnm',
+ '/JHKAJA',
+ '/unknown',
]
for url in url_404_list {
- res := http.get(url) or { panic(err) }
+ res := http.get('${server}${url}') or { panic(err) }
assert res.status() == .not_found
+ assert res.body == '404 on "${url}"'
}
}
@@ -234,7 +236,6 @@ fn test_http_client_shutdown_does_not_work_without_a_cookie() {
return
}
assert x.status() == .not_found
- assert x.body == '404 Not Found'
}
fn testsuite_end() {
diff --git a/vlib/vweb/tests/vweb_test_server.v b/vlib/vweb/tests/vweb_test_server.v
index 4e0a2e3930..50761a5b2b 100644
--- a/vlib/vweb/tests/vweb_test_server.v
+++ b/vlib/vweb/tests/vweb_test_server.v
@@ -103,6 +103,12 @@ pub fn (mut app App) json() vweb.Result {
return app.ok(app.req.data)
}
+// Custom 404 page
+pub fn (mut app App) not_found() vweb.Result {
+ app.set_status(404, 'Not Found')
+ return app.html('404 on "${app.req.url}"')
+}
+
pub fn (mut app App) shutdown() vweb.Result {
session_key := app.get_cookie('skey') or { return app.not_found() }
if session_key != 'superman' {
diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v
index 8cbbb316eb..5a6547579c 100644
--- a/vlib/vweb/vweb.v
+++ b/vlib/vweb/vweb.v
@@ -740,7 +740,7 @@ fn handle_route[T](mut app T, url urllib.URL, routes &map[string]Route, tid int)
}
}
// Route not found
- app.conn.write(vweb.http_404.bytes()) or {}
+ app.not_found()
}
// validate_middleware validates and fires all middlewares that are defined in the global app instance