diff --git a/vlib/vweb/tests/vweb_should_listen_on_both_ipv4_and_ipv6_by_default_test.v b/vlib/vweb/tests/vweb_should_listen_on_both_ipv4_and_ipv6_by_default_test.v new file mode 100644 index 0000000000..353167c6f8 --- /dev/null +++ b/vlib/vweb/tests/vweb_should_listen_on_both_ipv4_and_ipv6_by_default_test.v @@ -0,0 +1,120 @@ +import os +import log +import time +import vweb +import net.http + +const vexe = os.getenv('VEXE') +const vroot = os.dir(vexe) +const port = 28871 +const welcome_text = 'Welcome to our simple vweb server' + +// Use a known good http client like `curl` (if it exists): +const curl_executable = os.find_abs_path_of_executable('curl') or { '' } +const curl_ok = curl_supports_ipv6() + +fn curl_supports_ipv6() bool { + if curl_executable == '' { + return false + } + curl_res := os.execute('${curl_executable} --version') + if curl_res.exit_code != 0 { + return false + } + if !curl_res.output.match_glob('curl*Features: * IPv6 *') { + return false + } + return true +} + +fn testsuite_begin() { + log.set_level(.debug) + log.debug(@FN) + os.chdir(vroot) or {} + if curl_ok { + log.info('working curl_executable found at: ${curl_executable}') + } else { + log.warn('no working working curl_executable found') + } + start_services() +} + +fn testsuite_end() { + log.debug(@FN) +} + +// + +fn ensure_curl_works(tname string) ? { + if !curl_ok { + log.warn('skipping test ${tname}, since it needs a working curl') + return none + } +} + +fn test_curl_connecting_through_ipv4_works() { + ensure_curl_works(@FN) or { return } + res := os.execute('${curl_executable} --connect-timeout 0.5 --silent http://127.0.0.1:${port}/') + assert res.exit_code == 0, res.output + assert res.output == welcome_text + log.info('> ${@FN}') +} + +fn test_net_http_connecting_through_ipv4_works() { + res := http.get('http://127.0.0.1:${port}/')! + assert res.status_code == 200, res.str() + assert res.status_msg == 'OK', res.str() + assert res.body == welcome_text, res.str() + log.info('> ${@FN}') +} + +fn test_curl_connecting_through_ipv6_works() { + ensure_curl_works(@FN) or { return } + res := os.execute('${curl_executable} --silent --connect-timeout 0.5 http://[::1]:${port}/') + assert res.exit_code == 0, res.output + assert res.output == welcome_text + log.info('> ${@FN}') +} + +fn test_net_http_connecting_through_ipv6_works() { + $if windows { + log.warn('skipping test ${@FN} on windows for now') + return + } + res := http.get('http://[::1]:${port}/')! + assert res.status_code == 200, res.str() + assert res.status_msg == 'OK', res.str() + assert res.body == welcome_text, res.str() + log.info('> ${@FN}') +} + +// + +struct App { + vweb.Context +mut: + started chan bool +} + +pub fn (mut app App) before_accept_loop() { + app.started <- true +} + +pub fn (mut app App) index() vweb.Result { + return app.text(welcome_text) +} + +fn start_services() { + log.debug('starting watchdog thread to ensure the test will always exit one way or another...') + spawn fn (timeout_in_ms int) { + time.sleep(timeout_in_ms * time.millisecond) + log.error('Timeout of ${timeout_in_ms} ms reached, for webserver: pid: ${os.getpid()}. Exiting ...') + exit(1) + }(10_000) + + log.debug('starting webserver...') + mut app := &App{} + spawn vweb.run(app, port) + _ := <-app.started + log.debug('webserver started') +} diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v index 8bb1ac1a20..e1743f0e96 100644 --- a/vlib/vweb/vweb.v +++ b/vlib/vweb/vweb.v @@ -204,9 +204,19 @@ pub fn (ctx Context) init_server() { eprintln('init_server() has been deprecated, please init your web app in `fn main()`') } +// before_accept_loop is called once the vweb app is started, and listening, but before the loop that accepts +// incomming request connections. +// It will be called in the main thread, that runs vweb.run/2 or vweb.run_at/2. +// It allows you to be notified about the successfull start of your app, and to synchronise your other threads +// with the webserver start, without error prone and slow pooling or time.sleep waiting. +// Defining this method is optional. +pub fn (ctx &Context) before_accept_loop() { +} + +// before_request is called once before each request is routed. +// It will be called in one of multiple threads in a pool, serving requests, +// the same one, in which the matching route method will be executed right after it. // Defining this method is optional. -// This method is called before every request (aka middleware). -// You can use it for checking user session cookies or to add headers. pub fn (ctx Context) before_request() {} // TODO - test @@ -567,6 +577,10 @@ pub fn run_at[T](global_app &T, params RunParams) ! { } flush_stdout() + unsafe { + global_app.before_accept_loop() + } + // Forever accept every connection that comes, and // pass it through the channel, to the thread pool: for {