diff --git a/vlib/picoev/picoev.v b/vlib/picoev/picoev.v index 82e6f40b16ec23..5a8024eb2414f1 100644 --- a/vlib/picoev/picoev.v +++ b/vlib/picoev/picoev.v @@ -216,6 +216,12 @@ fn raw_callback(fd int, events int, context voidptr) { $if trace_fd ? { eprintln('timeout ${fd}') } + + if !isnil(pv.raw_cb) { + pv.raw_cb(mut pv, fd, events) + return + } + pv.close_conn(fd) return } else if events & picoev.picoev_read != 0 { diff --git a/vlib/x/vweb/tests/large_payload_test.v b/vlib/x/vweb/tests/large_payload_test.v index bad85dfa5e6a6a..e95cfefaadb50d 100644 --- a/vlib/x/vweb/tests/large_payload_test.v +++ b/vlib/x/vweb/tests/large_payload_test.v @@ -91,8 +91,8 @@ fn test_bigger_content_length() { data: data })! - assert x.status() == .bad_request - assert x.body == 'Mismatch of body length and Content-Length header' + // Content-length is larger than the data sent, so the request should timeout + assert x.status() == .request_timeout } fn test_smaller_content_length() { diff --git a/vlib/x/vweb/vweb.v b/vlib/x/vweb/vweb.v index 15b4f954b0f393..fb726931515a0f 100644 --- a/vlib/x/vweb/vweb.v +++ b/vlib/x/vweb/vweb.v @@ -57,6 +57,15 @@ pub const http_404 = http.new_response( ).join(headers_close) ) +pub const http_408 = http.new_response( + status: .request_timeout + body: '408 Request Timeout' + header: http.new_header( + key: .content_type + value: 'text/plain' + ).join(headers_close) +) + pub const http_413 = http.new_response( status: .request_entity_too_large body: '413 Request entity is too large' @@ -256,6 +265,13 @@ mut: string_responses []StringResponse } +// reset request parameters for `fd`: +// reset content-length index and the http request +pub fn (mut params RequestParams) request_done(fd int) { + params.incomplete_requests[fd] = http.Request{} + params.idx[fd] = 0 +} + // run_at - start a new VWeb server, listening only on a specific address `host`, at the specified `port` // Example: vweb.run_at(new_app(), vweb.RunParams{ host: 'localhost' port: 8099 family: .ip }) or { panic(err) } @[direct_array_access; manualfree] @@ -306,7 +322,13 @@ pub fn run_at[A, X](mut global_app A, params RunParams) ! { fn ev_callback[A, X](mut pv picoev.Picoev, fd int, events int) { mut params := unsafe { &RequestParams(pv.user_data) } - if events == picoev.picoev_write { + if events == picoev.picoev_timeout { + $if trace_picoev_callback ? { + eprintln('> request timeout on file descriptor ${fd}') + } + + handle_timeout(mut pv, mut params, fd) + } else if events == picoev.picoev_write { $if trace_picoev_callback ? { eprintln('> write event on file descriptor ${fd}') } @@ -320,14 +342,30 @@ fn ev_callback[A, X](mut pv picoev.Picoev, fd int, events int) { eprintln('[vweb] error: write event on connection should be closed') pv.close_conn(fd) } - } else { + } else if events == picoev.picoev_read { $if trace_picoev_callback ? { eprintln('> read event on file descriptor ${fd}') } handle_read[A, X](mut pv, mut params, fd) + } else { + // should never happen + eprintln('[vweb] error: invalid picoev event ${events}') } } +fn handle_timeout(mut pv picoev.Picoev, mut params RequestParams, fd int) { + mut conn := &net.TcpConn{ + sock: net.tcp_socket_from_handle_raw(fd) + handle: fd + is_blocking: false + } + + fast_send_resp(mut conn, vweb.http_408) or {} + pv.close_conn(fd) + + params.request_done(fd) +} + // handle_write_file reads data from a file and sends that data over the socket. @[direct_array_access; manualfree] fn handle_write_file(mut pv picoev.Picoev, mut params RequestParams, fd int) { @@ -507,9 +545,7 @@ fn handle_read[A, X](mut pv picoev.Picoev, mut params RequestParams, fd int) { } defer { - // reset content-length index, the http request and close the connection - params.incomplete_requests[fd] = http.Request{} - params.idx[fd] = 0 + params.request_done(fd) } if completed_context := handle_request[A, X](mut conn, req, params) {