Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[veb] a response cannot be sent twice over one connection #23715

Open
xD0135 opened this issue Feb 13, 2025 · 7 comments
Open

[veb] a response cannot be sent twice over one connection #23715

xD0135 opened this issue Feb 13, 2025 · 7 comments
Labels
Bug This tag is applied to issues which reports bugs. Status: Confirmed This bug has been confirmed to be valid by a contributor.

Comments

@xD0135
Copy link
Contributor

xD0135 commented Feb 13, 2025

Describe the bug

In the following Veb code, I expect the logic flow to stop execution when the first return is executed inside the or block, but looks like it continues on:

@['/orders/:token'; get]
pub fn (app &App) index(mut ctx Context, token string) veb.Result {
	record := db_client.get_order(token) or {
		ctx.res.set_status(.not_found)
		return $veb.html('html/order_not_found.html')
	}
	println("continued")
	ctx.set_csrf_token(mut ctx)
	return $veb.html('html/index.html')
}

We can then see the following output in the terminal:

[veb] Running app on http://localhost:8080/
continued
[veb] a response cannot be sent twice over one connection

What's the proper way to handle this scenario?

Reproduction Steps

Simply code/paste the following code, run v run main.v, then open http://localhost:8080/orders/foo and you'll see the issue in the terminal output.

index.html

Index page!

not_found.html

Order Not found!

main.v

module main

import veb

pub struct Context {
	veb.Context
}

pub struct App {}

pub struct Order {}

pub fn get_order(valid bool) !Order {
	if valid {
		return Order{}
	} else {
		return error('dummy')
	}
}

@['/orders/:token'; get]
pub fn (app &App) index(mut ctx Context, token string) veb.Result {
	record := get_order(false) or {
		ctx.res.set_status(.not_found)
		return $veb.html('not_found.html')
	}
	println('continued')
	return $veb.html('index.html')
}

fn main() {
	mut app := &App{}
	veb.run[App, Context](mut app, 8080)
}

Expected Behavior

The logic flow should not continue, therefore the code should stop running when the first return is encountered.

Current Behavior

As described at the beginning of this issue.

Possible Solution

No response

Additional Information/Context

No response

V version

V 0.4.9 3953445

Environment details (OS name and version, etc.)

V full version: V 0.4.9 3953445
OS: linux, "Manjaro Linux"
Processor: 16 cpus, 64bit, little endian, Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz

getwd: /home/user/Projects/v/sample1
vexe: /usr/lib/vlang/v
vexe mtime: 2025-02-10 22:55:45

vroot: NOT writable, value: /usr/lib/vlang
VMODULES: OK, value: /home/user/.vmodules
VTMP: OK, value: /tmp/v_1000

Git version: git version 2.48.1
Git vroot status: Error: fatal: not a git repository (or any of the parent directories): .git
.git/config present: false

CC version: cc (GCC) 14.2.1 20240910
emcc version: N/A
thirdparty/tcc status: thirdparty-linux-amd64 0134e9b9-dirty

Note

You can use the 👍 reaction to increase the issue's priority for developers.

Please note that only the 👍 reaction to the issue itself counts as a vote.
Other reactions and those to comments will not be taken into account.

@xD0135 xD0135 added the Bug This tag is applied to issues which reports bugs. label Feb 13, 2025
Copy link

Connected to Huly®: V_0.6-22133

@xD0135
Copy link
Contributor Author

xD0135 commented Feb 13, 2025

As a workaround I can use the following to get the desired result, but I still want to understand the original issue.

@['/orders/:token'; get]
pub fn (app &App) index(mut ctx Context, token string) veb.Result {
    if record := db_client.get_order(token) {
        ctx.set_csrf_token(mut ctx)
        return $veb.html('html/index.html')
    } else {
        ctx.res.set_status(.not_found)
        return $veb.html('html/order_not_found.html')
    }
}

@jorgeluismireles
Copy link

Your version V 0.4.9 3953445 is too old! (dic 2024)

First do a v up

I tested this code and worked in V 0.4.9 81a2e7a (today)

module main

import veb

pub struct Context {
	veb.Context
}

pub struct App {}

pub struct Order {}

pub fn do_login(token string) ! string {
	if token == 'secret' {
		return '200'
	} else {
		return error('401')
	}
}

@['/login/:token'; get]
pub fn (app &App) index(mut ctx Context, token string) veb.Result {
	valid := do_login(token) or {
		ctx.res.set_status(.not_found)
		return ctx.html('${err}')
	}
	return ctx.html(valid)
}

fn main() {
	mut app := &App{}
	veb.run[App, Context](mut app, 8080)
}

For http://localhost:8080/login/secret I got 200
For http://localhost:8080/login/??? I got 401

@xD0135
Copy link
Contributor Author

xD0135 commented Feb 14, 2025

@jorgeluismireles note that in your example you're not using $veb.html(...) which is where the problem is. I too do not have an issue with my version of V if I simply use ctx.html(...), that's why in my repro steps I included the exact code that has problem, and your example is testing something else unrelated to my issue.

@jorgeluismireles
Copy link

note that in your example you're not using $veb.html(...)

Yes, I just simplify in order to have a single program file, and yes $veb.html() could be the problem, having a chance today I'll try to reproduce your finding.

@JalonSolov
Copy link
Contributor

JalonSolov commented Feb 14, 2025

With V 0.4.9 49fb7ca (current as of right now), I see the following:

$ curl http://localhost:8080/orders/foo
Order Not found!
$

Which seems correct, but the output on the server side is

$ v run main.v
index.html:23:2: warning: unused variable: `record`
main.v:23:2: warning: unused variable: `record`
   21 | @['/orders/:token'; get]
   22 | pub fn (app &App) index(mut ctx Context, token string) veb.Result {
   23 |     record := get_order(false) or {
      |     ~~~~~~
   24 |         ctx.res.set_status(.not_found)
   25 |         return $veb.html('not_found.html')
[veb] Running app on http://localhost:8080/
continued
[veb] a response cannot be sent twice over one connection

which is the original complaint.

Since it returned the not_found info, it should never have printed the continued string, nor should it have complained about sending something twice over the same connection.

@JalonSolov JalonSolov added the Status: Confirmed This bug has been confirmed to be valid by a contributor. label Feb 14, 2025
@jorgeluismireles
Copy link

IMHO another alternative is to pass to a single template $veb.html() the order with valid or invalid fields:

import veb

pub struct Context {
	veb.Context
}

pub struct App {}

struct Order {
	error string
	rows  []string
}

@['/orders/:token'; get]
pub fn (app &App) order(mut ctx Context, token string) veb.Result {
	order := if token == '1000' {
		Order{ rows:[ 'one', 'thousand', 'dollars' ] }
	} else {
		Order{ error: 'invalid'}
	}
	return $veb.html()
}

fn main() {
	mut app := &App{}
	veb.run[App, Context](mut app, 8080)
}

order.html

<html>
 <head></head>
 <body>
  <h3>Order @token</h3>
  @if order.error != ""
   <div style='color:#f00'>@order.error</div>
  @else
   <table>
   	<caption>Content</caption>
    @for row in order.rows
     <tr><td style='border: 1px solid blue'>@row</td></tr>
     @end
   </table>
  @end
 </body>
</html>

Then http://localhost:8080/orders/1000 gives:

Order 1000

Content
one
thousand
dollars

and http://localhost:8080/orders/100 gives:

Order 100

invalid

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug This tag is applied to issues which reports bugs. Status: Confirmed This bug has been confirmed to be valid by a contributor.
Projects
None yet
Development

No branches or pull requests

3 participants