1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

vweb: host attribute (#18288)

This commit is contained in:
Lenni0451 2023-05-29 02:11:10 +02:00 committed by GitHub
parent f22ba836fd
commit 2904c399b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 95 additions and 60 deletions

View File

@ -251,6 +251,24 @@ pub fn (mut app App) controller_get_user_by_id() vweb.Result {
return app.text(app.query.str()) 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 ### Middleware
Vweb has different kinds of middleware. Vweb has different kinds of middleware.

View File

@ -4,15 +4,16 @@ import net.urllib
import net.http import net.http
// Parsing function attributes for methods and path. // 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 { if attrs.len == 0 {
return [http.Method.get], '/${name}', '' return [http.Method.get], '/${name}', '', ''
} }
mut x := attrs.clone() mut x := attrs.clone()
mut methods := []http.Method{} mut methods := []http.Method{}
mut middleware := '' mut middleware := ''
mut path := '' mut path := ''
mut host := ''
for i := 0; i < x.len; { for i := 0; i < x.len; {
attr := x[i] attr := x[i]
@ -36,6 +37,11 @@ fn parse_attrs(name string, attrs []string) !([]http.Method, string, string) {
x.delete(i) x.delete(i)
continue continue
} }
if attr.starts_with('host:') {
host = attr.all_after('host:').trim_space()
x.delete(i)
continue
}
i++ i++
} }
if x.len > 0 { if x.len > 0 {
@ -49,8 +55,8 @@ fn parse_attrs(name string, attrs []string) !([]http.Method, string, string) {
if path == '' { if path == '' {
path = '/${name}' path = '/${name}'
} }
// Make path lowercase for case-insensitive comparisons // Make path and host lowercase for case-insensitive comparisons
return methods, path.to_lower(), middleware return methods, path.to_lower(), middleware, host.to_lower()
} }
fn parse_query_from_url(url urllib.URL) map[string]string { fn parse_query_from_url(url urllib.URL) map[string]string {

View File

@ -183,6 +183,7 @@ struct Route {
methods []http.Method methods []http.Method
path string path string
middleware string middleware string
host string
} }
// Defining this method is optional. // Defining this method is optional.
@ -399,7 +400,7 @@ fn generate_routes[T](app &T) !map[string]Route {
// Parsing methods attributes // Parsing methods attributes
mut routes := map[string]Route{} mut routes := map[string]Route{}
$for method in T.methods { $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}') return error('error parsing method attributes: ${err}')
} }
@ -407,12 +408,13 @@ fn generate_routes[T](app &T) !map[string]Route {
methods: http_methods methods: http_methods
path: route_path path: route_path
middleware: middleware middleware: middleware
host: host
} }
} }
return routes 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 { pub struct ControllerPath {
path string 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 // no need to type `ControllerHandler` as generic since it's not needed for closures
return &ControllerPath{ return &ControllerPath{
path: path 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` // request_app is freed in `handle_route`
mut request_app := new_request_app[T](global_app, ctx, tid) mut request_app := new_request_app[T](global_app, ctx, tid)
// transform the url // transform the url
url.path = url.path.all_after_first(path) 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 return
} }
host := req.header.get(http.CommonHeader.host) or { '' }.to_lower()
// Create Context with request data // Create Context with request data
ctx := Context{ ctx := Context{
req: req 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 { for controller in global_app.controllers {
if url.path.len >= controller.path.len && url.path.starts_with(controller.path) { if url.path.len >= controller.path.len && url.path.starts_with(controller.path) {
// pass route handling to the controller // pass route handling to the controller
controller.handler(ctx, mut url, tid) controller.handler(ctx, mut url, host, tid)
return return
} }
} }
} }
mut request_app := new_request_app(global_app, ctx, tid) 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] [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 { defer {
unsafe { unsafe {
free(app) 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 // Used for route matching
route_words := route.path.split('/').filter(it != '') route_words := route.path.split('/').filter(it != '')
// Route immediate matches first // Skip if the host does not match or is empty
// For example URL `/register` matches route `/:user`, but `fn register()` if route.host == '' || route.host == host {
// should be called first. // Route immediate matches first
if !route.path.contains('/:') && url_words == route_words { // For example URL `/register` matches route `/:user`, but `fn register()`
// We found a match // should be called first.
$if T is MiddlewareInterface { if !route.path.contains('/:') && url_words == route_words {
if validate_middleware(mut app, url.path) == false { // We found a match
return $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 { if url_words.len == 0 && route_words == ['index'] && method.name == 'index' {
// Populate method args with form values $if T is MiddlewareInterface {
mut args := []string{cap: method.args.len} if validate_middleware(mut app, url.path) == false {
for param in method.args { return
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 == '' { if route.middleware == '' {
app.$method() app.$method()
} else if validate_app_middleware(mut app, route.middleware, method.name) { } else if validate_app_middleware(mut app, route.middleware, method.name) {
app.$method() app.$method()
} }
return
} }
return
}
if url_words.len == 0 && route_words == ['index'] && method.name == 'index' { if params := route_matches(url_words, route_words) {
$if T is MiddlewareInterface { method_args := params.clone()
if validate_middleware(mut app, url.path) == false { if method_args.len != method.args.len {
return 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) { $if T is MiddlewareInterface {
method_args := params.clone() if validate_middleware(mut app, url.path) == false {
if method_args.len != method.args.len { return
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 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
} }
} }
} }