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