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

x.vweb: add configuration options and handle incomplete requests #20868

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions vlib/io/buffered_reader.v
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,12 @@ pub fn (mut r BufferedReader) read_line(config BufferedReadLineConfig) !string {
}
return Eof{}
}

// get_read_data returns the data that the buffered reader has read from `start` until `end`
pub fn (r &BufferedReader) get_read_data(start int, end int) ![]u8 {
if end > r.buf.len {
return Eof{}
}

return r.buf[start..end]
}
88 changes: 88 additions & 0 deletions vlib/x/vweb/tests/config/incomplete_request_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// vtest flaky: true
// vtest retry: 3
import time
import x.vweb
import net
import net.http

const port = 13020
const exit_after = time.second * 20

pub struct Context {
vweb.Context
}

pub struct App {
mut:
started chan bool
}

pub fn (mut app App) before_accept_loop() {
app.started <- true
}

pub fn (mut app App) index(mut ctx Context) vweb.Result {
return ctx.text('Hello V!')
}

fn testsuite_begin() {
mut app := &App{}
spawn vweb.run_at[App, Context](mut app,
port: port
timeout_in_seconds: 15
config: vweb.Config{
max_nr_of_incomplete_requests: 1
}
)
_ := <-app.started

spawn fn () {
time.sleep(exit_after)
assert false, 'timeout reached'
exit(1)
}()
}

fn test_incomplete_request() {
// simulate a slow network where the request headers are received in parts
// we set the maximum number of incomplete requests on a connection to 1.
// This means that when we send another incomplete request we expect vweb
// to close the connection.

mut conn := net.dial_tcp('127.0.0.1:${port}')!
defer {
conn.close() or {}
}

conn.write_string('GET / HTTP/1.1\r
User-Agent: V\r
Acc')!

// simulate network delay
time.sleep(time.second * 2)
conn.write_string('ept: */*\r\n\r\n')!

// simulate network delay
time.sleep(time.second * 2)
mut buf := []u8{len: 86}
conn.read(mut buf)!
resp := http.parse_response(buf.bytestr())!
assert resp.status() == .ok
assert resp.body == 'Hello V!'

// do second request

// simulate network delay
time.sleep(time.second * 2)
conn.write_string('GET / HTTP/1.1\r
User-Agent: V\r
Acc')!

// simulate network delay
time.sleep(time.second * 2)

buf = []u8{len: 103}
conn.read(mut buf)!
bad_resp := http.parse_response(buf.bytestr())!
assert bad_resp.status() == .bad_request
}
59 changes: 59 additions & 0 deletions vlib/x/vweb/tests/config/max_header_size_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import net.http
import time
import x.vweb

const port = 13021
const exit_after = time.second * 10
const header_size = 1000
const localserver = 'http://localhost:${port}'

pub struct Context {
vweb.Context
}

pub struct App {
mut:
started chan bool
}

pub fn (mut app App) before_accept_loop() {
app.started <- true
}

pub fn (mut app App) index(mut ctx Context) vweb.Result {
return ctx.text('Hello V!')
}

fn testsuite_begin() {
mut app := &App{}
spawn vweb.run_at[App, Context](mut app,
port: port
timeout_in_seconds: 2
config: vweb.Config{
max_header_len: header_size
}
)
_ := <-app.started

spawn fn () {
time.sleep(exit_after)
assert false, 'timeout reached'
exit(1)
}()
}

fn test_larger_header_size() {
mut buf := []u8{len: header_size * 2, init: `a`}
str := buf.bytestr()

mut header := http.new_custom_header_from_map({
'X-Large-Header': str
})!

mut x := http.fetch(http.FetchConfig{
url: localserver
header: header
})!

assert x.status() == .request_entity_too_large
}
56 changes: 56 additions & 0 deletions vlib/x/vweb/tests/config/max_request_body_len_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import time
import x.vweb
import net
import net.http

const port = 13022
const exit_after = time.second * 10
const body_len = 1000
const localserver = 'http://localhost:${port}'

pub struct Context {
vweb.Context
}

pub struct App {
mut:
started chan bool
}

pub fn (mut app App) before_accept_loop() {
app.started <- true
}

pub fn (mut app App) index(mut ctx Context) vweb.Result {
return ctx.text('Hello V!')
}

fn testsuite_begin() {
mut app := &App{}
spawn vweb.run_at[App, Context](mut app,
port: port
timeout_in_seconds: 15
config: vweb.Config{
max_request_body_len: body_len
}
)
_ := <-app.started

spawn fn () {
time.sleep(exit_after)
assert false, 'timeout reached'
exit(1)
}()
}

fn test_larger_body_len() {
mut buf := []u8{len: body_len * 2, init: `a`}
str := buf.bytestr()

mut x := http.fetch(http.FetchConfig{
url: localserver
data: str
})!

assert x.status() == .request_entity_too_large
}
12 changes: 10 additions & 2 deletions vlib/x/vweb/tests/large_payload_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const port = 13002
const localserver = 'http://127.0.0.1:${port}'

const exit_after = time.second * 10
const header_size = vweb.max_read * 2

const tmp_file = os.join_path(os.vtmp_dir(), 'vweb_large_payload.txt')

Expand Down Expand Up @@ -47,7 +48,14 @@ fn testsuite_begin() {
}()

mut app := &App{}
spawn vweb.run_at[App, Context](mut app, port: port, timeout_in_seconds: 2, family: .ip)
spawn vweb.run_at[App, Context](mut app,
port: port
timeout_in_seconds: 2
family: .ip
config: vweb.Config{
max_header_len: header_size
}
)
// app startup time
_ := <-app.started
}
Expand All @@ -68,7 +76,7 @@ fn test_large_request_body() {
fn test_large_request_header() {
// same test as test_large_request_body, but then with a large header,
// which is parsed seperately
mut buf := []u8{len: vweb.max_read * 2, init: `a`}
mut buf := []u8{len: header_size, init: `a`}

str := buf.bytestr()
// make 1 header longer than vwebs max read limit
Expand Down
Loading