diff --git a/http/httphi/example_test.go b/http/httphi/example_test.go index ea3958f..33944e7 100644 --- a/http/httphi/example_test.go +++ b/http/httphi/example_test.go @@ -80,7 +80,7 @@ func ExampleMuxSlice_query_forms_multipart() { // Request Body Form. The form owns the memory: hand it a buffer and // forbid growth to bound what a request may spend. var form httpraw.Form - form.Reset(make([]byte, 0, 1024), 8) + form.Reset(make([]byte, 0, 1024), 8) // Room for 8 pairs. form.EnableBufferGrowth(false) err := ex.RequestParseForm(&form, false, false) if err != nil { diff --git a/http/httphi/exchange_test.go b/http/httphi/exchange_test.go index 585f5d9..38f8061 100644 --- a/http/httphi/exchange_test.go +++ b/http/httphi/exchange_test.go @@ -23,6 +23,10 @@ func nopBackoff(consecutiveBackoffs uint) time.Duration { return lneto.BackoffFl // where it says so. const defaultNumHeaderKVCap = 32 +// defaultKVCap is the pair table size tests hand to [httpraw.Form.Reset], a +// bounded form needing room for the pairs it parses. See [httpraw.Form.Reset]. +const defaultKVCap = 8 + // newExchange returns an Exchange acquired on conn, ready to serve a request. func newExchange(t *testing.T, conn conn, cfg ExchangeConfig) *Exchange { t.Helper() @@ -872,7 +876,7 @@ func TestExchangeRequestParseForm(t *testing.T) { // The form owns the memory: bufSize bounds it here, growth off so an // oversized body is reported rather than allocated for. var form httpraw.Form - form.Reset(make([]byte, 0, bufSize), 8) + form.Reset(make([]byte, 0, bufSize), defaultKVCap) form.EnableBufferGrowth(false) var gotErr error var sm MuxSlice @@ -1482,40 +1486,6 @@ func TestExchangeRequestParseFormFoldedTransferEncoding(t *testing.T) { } } -// StageHeaderInt defaults to base 10, the only base an HTTP field value uses, -// so callers do not repeat it at every site. -func TestExchangeStageHeaderIntDefaultsBase10(t *testing.T) { - conn := newConn("") - exch := newExchange(t, conn, ExchangeConfig{RawBuf: make([]byte, 256), RequestBufferLim: 128}) - if !exch.StageHeaderInt("Content-Length", 1234567890) { - t.Fatal("want field staged") - } - exch.WriteHeader(200) - const want = "HTTP/1.1 200 OK\r\nContent-Length:1234567890\r\n\r\n" - if got := conn.ViewWritten(); got != want { - t.Errorf("want %q, got %q", want, got) - } -} - -// StageHeaderBytes stages a value already held as bytes without the caller -// converting it to a string first. -func TestExchangeStageHeaderBytes(t *testing.T) { - conn := newConn("") - exch := newExchange(t, conn, ExchangeConfig{RawBuf: make([]byte, 256), RequestBufferLim: 128}) - value := []byte("text/html") - if !exch.StageHeaderBytes("Content-Type", value) { - t.Fatal("want field staged") - } - // The value must be copied, not aliased: mutating it after staging must not - // change what reaches the wire. - value[0] = 'X' - exch.WriteHeader(200) - const want = "HTTP/1.1 200 OK\r\nContent-Type:text/html\r\n\r\n" - if got := conn.ViewWritten(); got != want { - t.Errorf("want %q, got %q", want, got) - } -} - // Respond replaces the stage/stage/stage/write boilerplate every handler paid, // deriving Content-Length from the body so it cannot disagree with what is sent. func TestExchangeRespond(t *testing.T) { diff --git a/http/httpraw/form_test.go b/http/httpraw/form_test.go index 7a920b0..445693e 100644 --- a/http/httpraw/form_test.go +++ b/http/httpraw/form_test.go @@ -148,7 +148,7 @@ func TestFormParseReuseNoAlloc(t *testing.T) { t.Fatal(err) } allocs := testing.AllocsPerRun(100, func() { - f.Reset(body, 0) + f.Reset(body, 0) // 0 preserves the pair storage warmed up above, the reuse under test. f.Parse() }) if allocs != 0 { @@ -161,7 +161,7 @@ func TestFormParseReuseNoAlloc(t *testing.T) { // one. Form.Len is zero until Parse runs and cannot answer that. func TestFormBufferUsed(t *testing.T) { var f Form - f.Reset(nil, 0) + f.Reset(nil, defaultKVCap) if got := f.BufferUsed(); got != 0 { t.Errorf("want 0 on a fresh form, got %d", got) } @@ -191,7 +191,7 @@ func TestFormBufferUsed(t *testing.T) { t.Errorf("BufferUsed must not change on Parse, got %d", got) } // Reset discards the pairs and the buffered bytes with them. - f.Reset(nil, 0) + f.Reset(nil, defaultKVCap) if got := f.BufferUsed(); got != 0 { t.Errorf("want 0 after Reset, got %d", got) } diff --git a/http/httpraw/header_test.go b/http/httpraw/header_test.go index 449f494..351cc1c 100644 --- a/http/httpraw/header_test.go +++ b/http/httpraw/header_test.go @@ -11,7 +11,11 @@ import ( "time" ) -const numHeaderCapacity = 16 +// defaultKVCap is the key/value table size tests hand to Reset. A Reset with 0 +// preserves whatever capacity the value already had, which is what production +// code wants on reuse but leaves a fresh value unable to hold a single pair when +// growth is disabled, see [Form.Reset]. +const defaultKVCap = 16 func TestHeaderParseRequest(t *testing.T) { const ( @@ -62,7 +66,7 @@ func TestHeaderParseRequest(t *testing.T) { } var c Cookie cookie := hdr.Get("Cookie") - c.Reset(cookie, 0) + c.Reset(cookie, defaultKVCap) err = c.Parse() if err != nil { t.Error(err) @@ -394,7 +398,7 @@ func TestCopyDecodedPercentURLInPlace(t *testing.T) { func TestHeaderSetOverwrite(t *testing.T) { var h Header - h.Reset(nil, numHeaderCapacity) + h.Reset(nil, defaultKVCap) h.SetMethod("GET") h.SetRequestTarget("/") h.SetProtocol("HTTP/1.1") @@ -417,7 +421,7 @@ func TestHeaderSetOverwrite(t *testing.T) { func TestHeaderSetBytesEmptyValue(t *testing.T) { var h Header - h.Reset(nil, numHeaderCapacity) + h.Reset(nil, defaultKVCap) h.SetBytes("X-Empty", nil) if got := h.Get("X-Empty"); len(got) != 0 { t.Errorf("want empty value, got %q", got) @@ -472,7 +476,7 @@ func TestHeader_SplitBeforeColonStillParses(t *testing.T) { const part2 = ": example.com\r\n\r\n" var h Header - h.Reset(nil, numHeaderCapacity) + h.Reset(nil, defaultKVCap) if err := h.ReadFromBytes([]byte(part1)); err != nil { t.Fatal(err) } @@ -505,7 +509,7 @@ func TestHeader_AppendHeaderExactCapNoPanic(t *testing.T) { const key, value = "K", "V" buf := make([]byte, 0, len(key)+len(value)) // exact cap, no slack. var h Header - h.Reset(buf, numHeaderCapacity) + h.Reset(buf, defaultKVCap) defer func() { if r := recover(); r != nil { t.Fatalf("appendHeader panicked on exact-cap buffer: %v", r) @@ -522,7 +526,7 @@ func TestHeader_AppendHeaderExactCapNoPanic(t *testing.T) { func TestHeader_AddFullBufferNoPanic(t *testing.T) { buf := make([]byte, 0, 40) // Small cap; enough for Reset (len 0) but not the field below. var h Header - h.Reset(buf, numHeaderCapacity) + h.Reset(buf, defaultKVCap) h.ConfigBufferGrowth(false) h.SetMethod("GET") h.SetRequestTarget("/") @@ -557,7 +561,7 @@ func TestHeader_SetInt(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { var h Header - h.Reset(nil, numHeaderCapacity) + h.Reset(nil, defaultKVCap) h.SetInt("Content-Length", tc.value, tc.base) if got := string(h.Get("Content-Length")); got != tc.want { t.Fatalf("want %q, got %q", tc.want, got) @@ -569,7 +573,7 @@ func TestHeader_SetInt(t *testing.T) { // SetInt on an existing key must reuse the slot in place (single field, latest value). func TestHeader_SetIntOverwrite(t *testing.T) { var h Header - h.Reset(nil, numHeaderCapacity) + h.Reset(nil, defaultKVCap) h.SetMethod("GET") h.SetRequestTarget("/") h.SetProtocol("HTTP/1.1") @@ -593,7 +597,7 @@ func TestHeader_SetIntOverwrite(t *testing.T) { func TestHeader_SetIntNoAlloc(t *testing.T) { buf := make([]byte, 0, 256) var h Header - h.Reset(buf, numHeaderCapacity) + h.Reset(buf, defaultKVCap) h.ConfigBufferGrowth(false) h.Add("Content-Length", "0000000000000000000000") // pre-size a reusable slot. allocs := testing.AllocsPerRun(100, func() { @@ -623,7 +627,7 @@ func TestHeader_FieldTableSizedFromBuffer(t *testing.T) { raw.WriteString("X-Canary: " + wantVal + "\r\n\r\n") var h Header - h.Reset(make([]byte, 0, 8192), numHeaderCapacity) // Room for the block with plenty to spare. + h.Reset(make([]byte, 0, 8192), defaultKVCap) // Room for the block with plenty to spare. err := h.ParseBytes(false, []byte(raw.String())) if err != nil { t.Fatalf("parsing a 42 field request into an 8kB buffer: %s", err) @@ -646,7 +650,7 @@ func TestHeader_FieldTableFullIsReported(t *testing.T) { } raw.WriteString("\r\n") var h Header - h.Reset(make([]byte, 0, 512), numHeaderCapacity) + h.Reset(make([]byte, 0, 512), defaultKVCap) h.ConfigBufferGrowth(false) err := h.ParseBytes(false, []byte(raw.String())) if !errors.Is(err, ErrHeaderTooMany) { diff --git a/http/httpraw/parse_test.go b/http/httpraw/parse_test.go index 0a3160e..de433d3 100644 --- a/http/httpraw/parse_test.go +++ b/http/httpraw/parse_test.go @@ -10,7 +10,7 @@ func TestTryParse_IncrementalRequest(t *testing.T) { // Full HTTP request split across multiple ReadFromBytes calls. full := "GET /index.html HTTP/1.1\r\nHost: example.com\r\nContent-Type: text/html\r\n\r\nbody here" var hdr Header - hdr.Reset(make([]byte, 0, 256), numHeaderCapacity) + hdr.Reset(make([]byte, 0, 256), defaultKVCap) // Feed data in small chunks to exercise incremental parsing. chunks := splitInto(full, 10) @@ -79,7 +79,7 @@ func TestTryParse_IncrementalRequest(t *testing.T) { func TestTryParse_IncrementalResponse(t *testing.T) { full := "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nServer: lneto\r\n\r\nhello" var hdr Header - hdr.Reset(make([]byte, 0, 256), numHeaderCapacity) + hdr.Reset(make([]byte, 0, 256), defaultKVCap) chunks := splitInto(full, 8) var done bool @@ -131,7 +131,7 @@ func TestReadFromLimited(t *testing.T) { r := strings.NewReader(data) var hdr Header - hdr.Reset(make([]byte, 0, 256), numHeaderCapacity) + hdr.Reset(make([]byte, 0, 256), defaultKVCap) // Read in one shot. n, err := hdr.ReadFromLimited(r, 256) @@ -157,7 +157,7 @@ func TestReadFromLimited(t *testing.T) { func TestReadFromLimited_MaxBytes(t *testing.T) { var hdr Header - hdr.Reset(make([]byte, 0, 256), numHeaderCapacity) + hdr.Reset(make([]byte, 0, 256), defaultKVCap) // Zero maxBytesToRead should error. _, err := hdr.ReadFromLimited(strings.NewReader("data"), 0) @@ -168,7 +168,7 @@ func TestReadFromLimited_MaxBytes(t *testing.T) { func TestReadFromBytes_Empty(t *testing.T) { var hdr Header - hdr.Reset(make([]byte, 0, 256), numHeaderCapacity) + hdr.Reset(make([]byte, 0, 256), defaultKVCap) err := hdr.ReadFromBytes(nil) if err == nil { @@ -178,7 +178,7 @@ func TestReadFromBytes_Empty(t *testing.T) { func TestBufferFreeAndCapacity(t *testing.T) { var hdr Header - hdr.Reset(make([]byte, 0, 100), numHeaderCapacity) + hdr.Reset(make([]byte, 0, 100), defaultKVCap) if hdr.BufferCapacity() != 100 { t.Errorf("capacity = %d; want 100", hdr.BufferCapacity()) @@ -196,7 +196,7 @@ func TestBufferFreeAndCapacity(t *testing.T) { func TestEnableBufferGrowth(t *testing.T) { var hdr Header buf := make([]byte, 0, 64) - hdr.Reset(buf, numHeaderCapacity) + hdr.Reset(buf, defaultKVCap) hdr.ConfigBufferGrowth(false) // With growth disabled, reading more than capacity should fail. big := make([]byte, 128) @@ -387,7 +387,7 @@ func TestHeader_MultilineValue(t *testing.T) { func TestHeader_ResponseRoundTrip(t *testing.T) { var hdr Header - hdr.Reset(make([]byte, 0, 256), numHeaderCapacity) + hdr.Reset(make([]byte, 0, 256), defaultKVCap) hdr.SetProtocol("HTTP/1.1") hdr.SetStatus("404", "Not Found") hdr.Add("Content-Type", "text/plain") @@ -427,7 +427,7 @@ func TestHeader_ResponseRoundTrip(t *testing.T) { func TestHeader_RequestRoundTrip(t *testing.T) { var hdr Header - hdr.Reset(make([]byte, 0, 256), numHeaderCapacity) + hdr.Reset(make([]byte, 0, 256), defaultKVCap) hdr.SetProtocol("HTTP/1.1") hdr.SetMethod("POST") hdr.SetRequestTarget("/api/data")