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())
}
```
#### - 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.

View File

@ -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 {

View File

@ -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
}
}
}