mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
vweb: add host option to controller (#18303)
This commit is contained in:
parent
05b832a317
commit
4174048f96
@ -1003,7 +1003,7 @@ pub fn (u &URL) port() string {
|
|||||||
// split_host_port separates host and port. If the port is not valid, it returns
|
// split_host_port separates host and port. If the port is not valid, it returns
|
||||||
// the entire input as host, and it doesn't check the validity of the host.
|
// the entire input as host, and it doesn't check the validity of the host.
|
||||||
// Per RFC 3986, it requires ports to be numeric.
|
// Per RFC 3986, it requires ports to be numeric.
|
||||||
fn split_host_port(hostport string) (string, string) {
|
pub fn split_host_port(hostport string) (string, string) {
|
||||||
mut host := hostport
|
mut host := hostport
|
||||||
mut port := ''
|
mut port := ''
|
||||||
colon := host.last_index_u8(`:`)
|
colon := host.last_index_u8(`:`)
|
||||||
|
@ -253,7 +253,8 @@ pub fn (mut app App) controller_get_user_by_id() vweb.Result {
|
|||||||
```
|
```
|
||||||
#### - Host
|
#### - Host
|
||||||
To restrict an endpoint to a specific host, you can use the `host` attribute
|
To restrict an endpoint to a specific host, you can use the `host` attribute
|
||||||
followed by a colon `:` and the host name.
|
followed by a colon `:` and the host name. You can test the Host feature locally
|
||||||
|
by adding a host to the "hosts" file of your device.
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
@ -267,8 +268,17 @@ pub fn (mut app App) hello_web() vweb.Result {
|
|||||||
pub fn (mut app App) hello_api() vweb.Result {
|
pub fn (mut app App) hello_api() vweb.Result {
|
||||||
return app.text('Hello API')
|
return app.text('Hello API')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// define the handler without a host attribute last if you have conflicting paths.
|
||||||
|
['/']
|
||||||
|
pub fn (mut app App) hello_others() vweb.Result {
|
||||||
|
return app.text('Hello Others')
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also [create a controller](#hosts) to handle all requests from a specific
|
||||||
|
host in one app.
|
||||||
|
|
||||||
### Middleware
|
### Middleware
|
||||||
|
|
||||||
Vweb has different kinds of middleware.
|
Vweb has different kinds of middleware.
|
||||||
@ -679,6 +689,43 @@ pub fn (mut app App) admin_path vweb.Result {
|
|||||||
There will be an error, because the controller `Admin` handles all routes starting with
|
There will be an error, because the controller `Admin` handles all routes starting with
|
||||||
`"/admin"`; the method `admin_path` is unreachable.
|
`"/admin"`; the method `admin_path` is unreachable.
|
||||||
|
|
||||||
|
#### Hosts
|
||||||
|
You can also set a host for a controller. All requests coming from that host will be handled
|
||||||
|
by the controller.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```v
|
||||||
|
module main
|
||||||
|
|
||||||
|
import vweb
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
vweb.Context
|
||||||
|
vweb.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut app App) index() vweb.Result {
|
||||||
|
return app.text('App')
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Example {
|
||||||
|
vweb.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can only access this route at example.com: http://example.com/
|
||||||
|
pub fn (mut app Example) index() vweb.Result {
|
||||||
|
return app.text('Example')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
vweb.run(&App{
|
||||||
|
controllers: [
|
||||||
|
vweb.controller_host('example.com', '/', &Example{}),
|
||||||
|
]
|
||||||
|
}, 8080)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Databases and `[vweb_global]` in controllers
|
#### Databases and `[vweb_global]` in controllers
|
||||||
|
|
||||||
Fields with `[vweb_global]` have to passed to each controller individually.
|
Fields with `[vweb_global]` have to passed to each controller individually.
|
||||||
|
@ -137,9 +137,9 @@ fn test_duplicate_route() {
|
|||||||
$if windows {
|
$if windows {
|
||||||
task := spawn os.execute(server_exec_cmd)
|
task := spawn os.execute(server_exec_cmd)
|
||||||
res := task.wait()
|
res := task.wait()
|
||||||
assert res.output.contains('V panic: method "duplicate" with route "/admin/duplicate" should be handled by the Controller of "/admin"')
|
assert res.output.contains('V panic: conflicting paths')
|
||||||
} $else {
|
} $else {
|
||||||
res := os.execute(server_exec_cmd)
|
res := os.execute(server_exec_cmd)
|
||||||
assert res.output.contains('V panic: method "duplicate" with route "/admin/duplicate" should be handled by the Controller of "/admin"')
|
assert res.output.contains('V panic: conflicting paths')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,6 +238,20 @@ fn test_http_client_multipart_form_data() {
|
|||||||
assert x.body == files[0].data
|
assert x.body == files[0].data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_host() {
|
||||||
|
mut req := http.Request{
|
||||||
|
url: 'http://${localserver}/with_host'
|
||||||
|
method: .get
|
||||||
|
}
|
||||||
|
|
||||||
|
mut x := req.do()!
|
||||||
|
assert x.status() == .not_found
|
||||||
|
|
||||||
|
req.add_header(.host, 'example.com')
|
||||||
|
x = req.do()!
|
||||||
|
assert x.status() == .ok
|
||||||
|
}
|
||||||
|
|
||||||
fn test_http_client_shutdown_does_not_work_without_a_cookie() {
|
fn test_http_client_shutdown_does_not_work_without_a_cookie() {
|
||||||
x := http.get('http://${localserver}/shutdown') or {
|
x := http.get('http://${localserver}/shutdown') or {
|
||||||
assert err.msg() == ''
|
assert err.msg() == ''
|
||||||
|
@ -119,6 +119,12 @@ pub fn (mut app App) not_found() vweb.Result {
|
|||||||
return app.html('404 on "${app.req.url}"')
|
return app.html('404 on "${app.req.url}"')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[host: 'example.com']
|
||||||
|
['/with_host']
|
||||||
|
pub fn (mut app App) with_host() vweb.Result {
|
||||||
|
return app.ok('')
|
||||||
|
}
|
||||||
|
|
||||||
pub fn (mut app App) shutdown() vweb.Result {
|
pub fn (mut app App) shutdown() vweb.Result {
|
||||||
session_key := app.get_cookie('skey') or { return app.not_found() }
|
session_key := app.get_cookie('skey') or { return app.not_found() }
|
||||||
if session_key != 'superman' {
|
if session_key != 'superman' {
|
||||||
|
@ -417,8 +417,11 @@ fn generate_routes[T](app &T) !map[string]Route {
|
|||||||
type ControllerHandler = fn (ctx Context, mut url urllib.URL, host string, tid int)
|
type ControllerHandler = fn (ctx Context, mut url urllib.URL, host string, tid int)
|
||||||
|
|
||||||
pub struct ControllerPath {
|
pub struct ControllerPath {
|
||||||
|
pub:
|
||||||
path string
|
path string
|
||||||
handler ControllerHandler
|
handler ControllerHandler
|
||||||
|
pub mut:
|
||||||
|
host string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ControllerInterface {
|
interface ControllerInterface {
|
||||||
@ -448,6 +451,13 @@ pub fn controller[T](path string, global_app &T) &ControllerPath {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// controller_host generates a controller which only handles incoming requests from the `host` domain
|
||||||
|
pub fn controller_host[T](host string, path string, global_app &T) &ControllerPath {
|
||||||
|
mut ctrl := controller(path, global_app)
|
||||||
|
ctrl.host = host
|
||||||
|
return ctrl
|
||||||
|
}
|
||||||
|
|
||||||
// run - start a new VWeb server, listening to all available addresses, at the specified `port`
|
// run - start a new VWeb server, listening to all available addresses, at the specified `port`
|
||||||
pub fn run[T](global_app &T, port int) {
|
pub fn run[T](global_app &T, port int) {
|
||||||
run_at[T](global_app, host: '', port: port, family: .ip6) or { panic(err.msg()) }
|
run_at[T](global_app, host: '', port: port, family: .ip6) or { panic(err.msg()) }
|
||||||
@ -487,12 +497,14 @@ pub fn run_at[T](global_app &T, params RunParams) ! {
|
|||||||
$if T is ControllerInterface {
|
$if T is ControllerInterface {
|
||||||
mut paths := []string{}
|
mut paths := []string{}
|
||||||
for controller in global_app.controllers {
|
for controller in global_app.controllers {
|
||||||
|
if controller.host == '' {
|
||||||
paths << controller.path
|
paths << controller.path
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for method_name, route in routes {
|
for method_name, route in routes {
|
||||||
for controller_path in paths {
|
for controller_path in paths {
|
||||||
if route.path.starts_with(controller_path) {
|
if route.path.starts_with(controller_path) {
|
||||||
return error('method "${method_name}" with route "${route.path}" should be handled by the Controller of "${controller_path}"')
|
return error('conflicting paths: method "${method_name}" with route "${route.path}" should be handled by the Controller of path "${controller_path}"')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -622,7 +634,9 @@ 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()
|
// remove the port from the HTTP Host header
|
||||||
|
host_with_port := req.header.get(.host) or { '' }
|
||||||
|
host, _ := urllib.split_host_port(host_with_port)
|
||||||
|
|
||||||
// Create Context with request data
|
// Create Context with request data
|
||||||
ctx := Context{
|
ctx := Context{
|
||||||
@ -637,6 +651,10 @@ fn handle_conn[T](mut conn net.TcpConn, global_app &T, routes &map[string]Route,
|
|||||||
// match controller paths
|
// match controller paths
|
||||||
$if T is ControllerInterface {
|
$if T is ControllerInterface {
|
||||||
for controller in global_app.controllers {
|
for controller in global_app.controllers {
|
||||||
|
// skip controller if the hosts don't match
|
||||||
|
if controller.host != '' && host != controller.host {
|
||||||
|
continue
|
||||||
|
}
|
||||||
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, host, tid)
|
controller.handler(ctx, mut url, host, tid)
|
||||||
|
Loading…
Reference in New Issue
Block a user