feat: std.http read buffers grow on demand toward read_buf_cap
read_buf_cap is now the per-request LIMIT, not a preallocation: slots start at 16K, double when full (one-step sizing when a Content-Length declares the body), and keep their grown capacity for slot reuse. At the limit the refusal distinguishes oversized headers (431) from an oversized body (413). Unblocks A1: distd accepts multi-hundred-MB artifact uploads — preallocating that per slot was never an option. examples/1633 adds a body past the initial capacity echoing intact.
This commit is contained in:
@@ -33,12 +33,18 @@ Config :: struct {
|
||||
port: i64 = 8080;
|
||||
backlog: i32 = 128;
|
||||
max_conn: i64 = 256; // workers.max_conn
|
||||
read_buf_cap: i64 = 65536; // workers.large_buffer_size
|
||||
// MAXIMUM bytes one request (headers + body) may occupy. The
|
||||
// per-connection buffer starts at READ_BUF_INITIAL and grows on
|
||||
// demand toward this limit (a declared Content-Length sizes it in
|
||||
// one step); a grown buffer is retained for slot reuse.
|
||||
read_buf_cap: i64 = 1048576;
|
||||
timeout_request_ms: i64 = 5000; // deliver a full request, or evicted
|
||||
timeout_keepalive_ms: i64 = 5000;// idle between requests, or evicted
|
||||
request_count: i64 = 100; // requests per connection, then close
|
||||
}
|
||||
|
||||
READ_BUF_INITIAL :: 16384;
|
||||
|
||||
// One parsed request, viewed in place over the connection's read
|
||||
// buffer — valid for the duration of the handler call only.
|
||||
Request :: struct {
|
||||
@@ -125,7 +131,8 @@ CONN_KEEPALIVE :u8: 3; // between requests (deadline: keepalive)
|
||||
Conn :: struct {
|
||||
fd: i32 = -1;
|
||||
state: u8 = 0;
|
||||
read_buf: [*]u8 = null; // cap = config.read_buf_cap, reused across connections
|
||||
read_buf: [*]u8 = null; // grows toward config.read_buf_cap, reused across connections
|
||||
read_cap: i64 = 0;
|
||||
read_len: i64 = 0;
|
||||
out_buf: [*]u8 = null; // per-response allocation, freed when sent
|
||||
out_len: i64 = 0;
|
||||
@@ -296,7 +303,10 @@ Server :: struct {
|
||||
if !socket.set_nonblocking(fd) { socket.close(fd); return; }
|
||||
c := @self.conns[slot];
|
||||
if c.read_buf == null {
|
||||
c.read_buf = xx self.own_alloc.alloc_bytes(self.cfg.read_buf_cap);
|
||||
init_cap : i64 = READ_BUF_INITIAL;
|
||||
if init_cap > self.cfg.read_buf_cap { init_cap = self.cfg.read_buf_cap; }
|
||||
c.read_buf = xx self.own_alloc.alloc_bytes(init_cap);
|
||||
c.read_cap = init_cap;
|
||||
}
|
||||
c.fd = fd;
|
||||
c.state = CONN_READING;
|
||||
@@ -312,6 +322,22 @@ Server :: struct {
|
||||
|
||||
// ── read → parse → dispatch ──────────────────────────────────────
|
||||
|
||||
// Grow the slot's read buffer to at least `target` (0 = double),
|
||||
// never past cfg.read_buf_cap. False when already at the limit.
|
||||
grow_read_buf :: (self: *Server, slot: i64, target: i64) -> bool {
|
||||
c := @self.conns[slot];
|
||||
want := if target > 0 then target else c.read_cap * 2;
|
||||
if want <= c.read_cap { return true; }
|
||||
if want > self.cfg.read_buf_cap { want = self.cfg.read_buf_cap; }
|
||||
if want <= c.read_cap { return false; } // already at the limit
|
||||
nb : [*]u8 = xx self.own_alloc.alloc_bytes(want);
|
||||
if c.read_len > 0 { memcpy(nb, c.read_buf, xx c.read_len); }
|
||||
self.own_alloc.dealloc_bytes(xx c.read_buf, c.read_cap);
|
||||
c.read_buf = nb;
|
||||
c.read_cap = want;
|
||||
return true;
|
||||
}
|
||||
|
||||
read_more :: (self: *Server, slot: i64) {
|
||||
c := @self.conns[slot];
|
||||
if c.state == CONN_KEEPALIVE {
|
||||
@@ -319,12 +345,20 @@ Server :: struct {
|
||||
c.deadline = event.deadline_in(self.cfg.timeout_request_ms);
|
||||
}
|
||||
while true {
|
||||
cap := self.cfg.read_buf_cap - c.read_len;
|
||||
if cap <= 0 {
|
||||
self.respond_error_close(slot, 431);
|
||||
return;
|
||||
if c.read_len == c.read_cap {
|
||||
if !self.grow_read_buf(slot, 0) {
|
||||
// over the limit: oversized headers (431) or body (413)
|
||||
hdr_done := false;
|
||||
i : i64 = 0;
|
||||
while i + 3 < c.read_len {
|
||||
if c.read_buf[i] == 13 and c.read_buf[i+1] == 10 and c.read_buf[i+2] == 13 and c.read_buf[i+3] == 10 { hdr_done = true; break; }
|
||||
i += 1;
|
||||
}
|
||||
self.respond_error_close(slot, if hdr_done then 413 else 431);
|
||||
return;
|
||||
}
|
||||
}
|
||||
nq, re := socket.read_nb(c.fd, @c.read_buf[c.read_len], xx cap);
|
||||
nq, re := socket.read_nb(c.fd, @c.read_buf[c.read_len], xx (c.read_cap - c.read_len));
|
||||
if re == error.WouldBlock { break; }
|
||||
if re { // Closed or Fault
|
||||
self.conn_close(slot);
|
||||
@@ -392,7 +426,11 @@ Server :: struct {
|
||||
}
|
||||
total := xx he + 4 + clen;
|
||||
if total > self.cfg.read_buf_cap { self.respond_error_close(slot, 413); return false; }
|
||||
if c.read_len < total { return false; }
|
||||
if c.read_len < total {
|
||||
// size the buffer for the declared body in one step
|
||||
self.grow_read_buf(slot, total);
|
||||
return false;
|
||||
}
|
||||
req.body = string.{ ptr = @c.read_buf[he + 4], len = xx clen };
|
||||
|
||||
// keep-alive: 1.1 default on, 1.0 default off, header overrides
|
||||
|
||||
Reference in New Issue
Block a user