From 74048e2f170b64ae78d38b3dedb077ad2eb18cd9 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 13 Feb 2022 22:42:30 +0200 Subject: [PATCH] net: simplify the TcpConn.read_line/0 method, accumulate partially read lines, use a string builder, instead of concatenation --- vlib/net/tcp_read_line.v | 64 +++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/vlib/net/tcp_read_line.v b/vlib/net/tcp_read_line.v index ec4a18e450..57ccc9e348 100644 --- a/vlib/net/tcp_read_line.v +++ b/vlib/net/tcp_read_line.v @@ -1,9 +1,12 @@ module net +import strings + const ( - crlf = '\r\n' - msg_peek = 0x02 - max_read = 400 + crlf = '\r\n' + msg_peek = 0x02 + max_read = 400 + max_read_line_len = 1048576 ) // get_blocking returns whether the connection is in a blocking state, @@ -39,52 +42,59 @@ pub fn (mut con TcpConn) set_blocking(state bool) ? { } // read_line is a *simple*, *non customizable*, blocking line reader. -// It will *always* return a line, ending with CRLF, or just '', on EOF. +// It will return a line, ending with LF, or just '', on EOF. // NB: if you want more control over the buffer, please use a buffered IO // reader instead: `io.new_buffered_reader({reader: io.make_reader(con)})` pub fn (mut con TcpConn) read_line() string { - mut buf := [net.max_read]byte{} // where C.recv will store the network data - mut res := '' // The final result, including the ending \n. + return con.read_line_max(net.max_read_line_len) +} + +// read_line_max is a *simple*, *non customizable*, blocking line reader. +// It will return a line, ending with LF, '' on EOF. +// It stops reading, when the result line length exceeds max_line_len. +[manualfree] +pub fn (mut con TcpConn) read_line_max(max_line_len int) string { if !con.is_blocking { con.set_blocking(true) or {} } + mut buf := [net.max_read]byte{} // where C.recv will store the network data + mut res := strings.new_builder(net.max_read) // The final result, including the ending \n. + defer { + unsafe { res.free() } + } + bstart := unsafe { &buf[0] } for { - mut line := '' // The current line. Can be a partial without \n in it. - n := C.recv(con.sock.handle, &buf[0], net.max_read - 1, net.msg_peek | msg_nosignal) - if n == -1 { - return res - } - if n == 0 { - return res + n := C.recv(con.sock.handle, bstart, net.max_read - 1, net.msg_peek | msg_nosignal) + if n <= 0 { + return res.str() } buf[n] = `\0` mut eol_idx := -1 + mut lend := n for i in 0 .. n { - if int(buf[i]) == `\n` { + if buf[i] == `\n` { eol_idx = i - // Ensure that tos_clone(buf) later, - // will return *only* the first line (including \n), - // and ignore the rest - buf[i + 1] = `\0` + lend = i + 1 + buf[lend] = `\0` break } } - line = unsafe { tos_clone(&buf[0]) } if eol_idx > 0 { // At this point, we are sure that recv returned valid data, // that contains *at least* one line. // Ensure that the block till the first \n (including it) // is removed from the socket's receive queue, so that it does // not get read again. - C.recv(con.sock.handle, &buf[0], eol_idx + 1, msg_nosignal) - res += line + C.recv(con.sock.handle, bstart, lend, msg_nosignal) + unsafe { res.write_ptr(bstart, lend) } + break + } + // recv returned a buffer without \n in it, just store it for now: + C.recv(con.sock.handle, bstart, n, msg_nosignal) + unsafe { res.write_ptr(bstart, lend) } + if res.len > max_line_len { break } - // recv returned a buffer without \n in it . - C.recv(con.sock.handle, &buf[0], n, msg_nosignal) - res += line - res += net.crlf - break } - return res + return res.str() }