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
|
||||
// the entire input as host, and it doesn't check the validity of the host.
|
||||
// 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 port := ''
|
||||
colon := host.last_index_u8(`:`)
|
||||
|
@ -253,7 +253,8 @@ pub fn (mut app App) controller_get_user_by_id() vweb.Result {
|
||||
```
|
||||
#### - Host
|
||||
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:**
|
||||
|
||||
@ -267,8 +268,17 @@ pub fn (mut app App) hello_web() vweb.Result {
|
||||
pub fn (mut app App) hello_api() vweb.Result {
|
||||
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
|
||||
|
||||
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
|
||||
`"/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
|
||||
|
||||
Fields with `[vweb_global]` have to passed to each controller individually.
|
||||
|
@ -137,9 +137,9 @@ fn test_duplicate_route() {
|
||||
$if windows {
|
||||
task := spawn os.execute(server_exec_cmd)
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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() {
|
||||
x := http.get('http://${localserver}/shutdown') or {
|
||||
assert err.msg() == ''
|
||||
|
@ -119,6 +119,12 @@ pub fn (mut app App) not_found() vweb.Result {
|
||||
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 {
|
||||
session_key := app.get_cookie('skey') or { return app.not_found() }
|
||||
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)
|
||||
|
||||
pub struct ControllerPath {
|
||||
pub:
|
||||
path string
|
||||
handler ControllerHandler
|
||||
pub mut:
|
||||
host string
|
||||
}
|
||||
|
||||
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`
|
||||
pub fn run[T](global_app &T, port int) {
|
||||
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 {
|
||||
mut paths := []string{}
|
||||
for controller in global_app.controllers {
|
||||
if controller.host == '' {
|
||||
paths << controller.path
|
||||
}
|
||||
}
|
||||
for method_name, route in routes {
|
||||
for controller_path in paths {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
ctx := Context{
|
||||
@ -637,6 +651,10 @@ fn handle_conn[T](mut conn net.TcpConn, global_app &T, routes &map[string]Route,
|
||||
// match controller paths
|
||||
$if T is ControllerInterface {
|
||||
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) {
|
||||
// pass route handling to the controller
|
||||
controller.handler(ctx, mut url, host, tid)
|
||||
|
Loading…
Reference in New Issue
Block a user