diff --git a/vlib/vweb/README.md b/vlib/vweb/README.md index d0c0c3ac56..7c7ac7835c 100644 --- a/vlib/vweb/README.md +++ b/vlib/vweb/README.md @@ -251,6 +251,24 @@ pub fn (mut app App) controller_get_user_by_id() vweb.Result { return app.text(app.query.str()) } ``` +#### - Host +To restrict an endpoint to a specific host, you can use the `host` attribute +followed by a colon `:` and the host name. + +**Example:** + +```v ignore +['/'; host: 'example.com'] +pub fn (mut app App) hello_web() vweb.Result { + return app.text('Hello World') +} + +['/'; host: 'api.example.org'] +pub fn (mut app App) hello_api() vweb.Result { + return app.text('Hello API') +} +``` + ### Middleware Vweb has different kinds of middleware. diff --git a/vlib/vweb/parse.v b/vlib/vweb/parse.v index 5afd73230f..1b389f4883 100644 --- a/vlib/vweb/parse.v +++ b/vlib/vweb/parse.v @@ -4,15 +4,16 @@ import net.urllib import net.http // Parsing function attributes for methods and path. -fn parse_attrs(name string, attrs []string) !([]http.Method, string, string) { +fn parse_attrs(name string, attrs []string) !([]http.Method, string, string, string) { if attrs.len == 0 { - return [http.Method.get], '/${name}', '' + return [http.Method.get], '/${name}', '', '' } mut x := attrs.clone() mut methods := []http.Method{} mut middleware := '' mut path := '' + mut host := '' for i := 0; i < x.len; { attr := x[i] @@ -36,6 +37,11 @@ fn parse_attrs(name string, attrs []string) !([]http.Method, string, string) { x.delete(i) continue } + if attr.starts_with('host:') { + host = attr.all_after('host:').trim_space() + x.delete(i) + continue + } i++ } if x.len > 0 { @@ -49,8 +55,8 @@ fn parse_attrs(name string, attrs []string) !([]http.Method, string, string) { if path == '' { path = '/${name}' } - // Make path lowercase for case-insensitive comparisons - return methods, path.to_lower(), middleware + // Make path and host lowercase for case-insensitive comparisons + return methods, path.to_lower(), middleware, host.to_lower() } fn parse_query_from_url(url urllib.URL) map[string]string { diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v index ac0810c2be..ab961fae3b 100644 --- a/vlib/vweb/vweb.v +++ b/vlib/vweb/vweb.v @@ -183,6 +183,7 @@ struct Route { methods []http.Method path string middleware string + host string } // Defining this method is optional. @@ -399,7 +400,7 @@ fn generate_routes[T](app &T) !map[string]Route { // Parsing methods attributes mut routes := map[string]Route{} $for method in T.methods { - http_methods, route_path, middleware := parse_attrs(method.name, method.attrs) or { + http_methods, route_path, middleware, host := parse_attrs(method.name, method.attrs) or { return error('error parsing method attributes: ${err}') } @@ -407,12 +408,13 @@ fn generate_routes[T](app &T) !map[string]Route { methods: http_methods path: route_path middleware: middleware + host: host } } return routes } -type ControllerHandler = fn (ctx Context, mut url urllib.URL, tid int) +type ControllerHandler = fn (ctx Context, mut url urllib.URL, host string, tid int) pub struct ControllerPath { path string @@ -436,12 +438,12 @@ pub fn controller[T](path string, global_app &T) &ControllerPath { // no need to type `ControllerHandler` as generic since it's not needed for closures return &ControllerPath{ path: path - handler: fn [global_app, path, routes] [T](ctx Context, mut url urllib.URL, tid int) { + handler: fn [global_app, path, routes] [T](ctx Context, mut url urllib.URL, host string, tid int) { // request_app is freed in `handle_route` mut request_app := new_request_app[T](global_app, ctx, tid) // transform the url url.path = url.path.all_after_first(path) - handle_route[T](mut request_app, url, &routes, tid) + handle_route[T](mut request_app, url, host, &routes, tid) } } } @@ -620,6 +622,8 @@ fn handle_conn[T](mut conn net.TcpConn, global_app &T, routes &map[string]Route, return } + host := req.header.get(http.CommonHeader.host) or { '' }.to_lower() + // Create Context with request data ctx := Context{ req: req @@ -635,18 +639,18 @@ fn handle_conn[T](mut conn net.TcpConn, global_app &T, routes &map[string]Route, for controller in global_app.controllers { if url.path.len >= controller.path.len && url.path.starts_with(controller.path) { // pass route handling to the controller - controller.handler(ctx, mut url, tid) + controller.handler(ctx, mut url, host, tid) return } } } mut request_app := new_request_app(global_app, ctx, tid) - handle_route(mut request_app, url, routes, tid) + handle_route(mut request_app, url, host, routes, tid) } [manualfree] -fn handle_route[T](mut app T, url urllib.URL, routes &map[string]Route, tid int) { +fn handle_route[T](mut app T, url urllib.URL, host string, routes &map[string]Route, tid int) { defer { unsafe { free(app) @@ -690,70 +694,77 @@ fn handle_route[T](mut app T, url urllib.URL, routes &map[string]Route, tid int) // Used for route matching route_words := route.path.split('/').filter(it != '') - // Route immediate matches first - // For example URL `/register` matches route `/:user`, but `fn register()` - // should be called first. - if !route.path.contains('/:') && url_words == route_words { - // We found a match - $if T is MiddlewareInterface { - if validate_middleware(mut app, url.path) == false { - return + // Skip if the host does not match or is empty + if route.host == '' || route.host == host { + // Route immediate matches first + // For example URL `/register` matches route `/:user`, but `fn register()` + // should be called first. + if !route.path.contains('/:') && url_words == route_words { + // We found a match + $if T is MiddlewareInterface { + if validate_middleware(mut app, url.path) == false { + return + } } + + if app.req.method == .post && method.args.len > 0 { + // Populate method args with form values + mut args := []string{cap: method.args.len} + for param in method.args { + args << app.form[param.name] + } + + if route.middleware == '' { + app.$method(args) + } else if validate_app_middleware(mut app, route.middleware, + method.name) + { + app.$method(args) + } + } else { + if route.middleware == '' { + app.$method() + } else if validate_app_middleware(mut app, route.middleware, + method.name) + { + app.$method() + } + } + return } - if app.req.method == .post && method.args.len > 0 { - // Populate method args with form values - mut args := []string{cap: method.args.len} - for param in method.args { - args << app.form[param.name] + if url_words.len == 0 && route_words == ['index'] && method.name == 'index' { + $if T is MiddlewareInterface { + if validate_middleware(mut app, url.path) == false { + return + } } - - if route.middleware == '' { - app.$method(args) - } else if validate_app_middleware(mut app, route.middleware, method.name) { - app.$method(args) - } - } else { if route.middleware == '' { app.$method() } else if validate_app_middleware(mut app, route.middleware, method.name) { app.$method() } + return } - return - } - if url_words.len == 0 && route_words == ['index'] && method.name == 'index' { - $if T is MiddlewareInterface { - if validate_middleware(mut app, url.path) == false { - return + if params := route_matches(url_words, route_words) { + method_args := params.clone() + if method_args.len != method.args.len { + eprintln('[vweb] tid: ${tid:03d}, warning: uneven parameters count (${method.args.len}) in `${method.name}`, compared to the vweb route `${method.attrs}` (${method_args.len})') } - } - if route.middleware == '' { - app.$method() - } else if validate_app_middleware(mut app, route.middleware, method.name) { - app.$method() - } - return - } - if params := route_matches(url_words, route_words) { - method_args := params.clone() - if method_args.len != method.args.len { - eprintln('[vweb] tid: ${tid:03d}, warning: uneven parameters count (${method.args.len}) in `${method.name}`, compared to the vweb route `${method.attrs}` (${method_args.len})') - } - - $if T is MiddlewareInterface { - if validate_middleware(mut app, url.path) == false { - return + $if T is MiddlewareInterface { + if validate_middleware(mut app, url.path) == false { + return + } } + if route.middleware == '' { + app.$method(method_args) + } else if validate_app_middleware(mut app, route.middleware, method.name) { + app.$method(method_args) + } + return } - if route.middleware == '' { - app.$method(method_args) - } else if validate_app_middleware(mut app, route.middleware, method.name) { - app.$method(method_args) - } - return } } }