mid refactor of KVBuffer into Header

This commit is contained in:
Patricio Whittingslow
2026-07-29 15:10:24 -03:00
parent af0f1b8cc6
commit e346cd98d1
8 changed files with 374 additions and 322 deletions
+2 -2
View File
@@ -434,7 +434,7 @@ func (exch *Exchange) RequestParseForm(dst *httpraw.Form, buf []byte) error {
length, present, err := exch.RequestContentLength() length, present, err := exch.RequestContentLength()
if !present { if !present {
dst.Reset(nil) dst.Reset(nil, 0)
return nil // No length is no body, RFC 9112 6.3. return nil // No length is no body, RFC 9112 6.3.
} else if err != nil { } else if err != nil {
return err return err
@@ -454,7 +454,7 @@ func (exch *Exchange) RequestParseForm(dst *httpraw.Form, buf []byte) error {
return err return err
} }
} }
dst.Reset(buf) dst.Reset(buf, 0)
return dst.Parse() return dst.Parse()
} }
+10 -1
View File
@@ -77,8 +77,17 @@ func (c *Cookie) Get(key string) []byte { return c.kv.Get(key) }
// HasKeyOrSingleValue returns true if the cookie contains a pair with the given // HasKeyOrSingleValue returns true if the cookie contains a pair with the given
// key or a valueless attribute with the given text, i.e: "Secure" or "HttpOnly". // key or a valueless attribute with the given text, i.e: "Secure" or "HttpOnly".
// It cannot defer to [KVBuffer.Present]: parseCookie stores a valueless
// attribute with an empty key and the text as the value, so a key-only lookup
// would never match one.
func (c *Cookie) HasKeyOrSingleValue(keyOrSingleValue string) bool { func (c *Cookie) HasKeyOrSingleValue(keyOrSingleValue string) bool {
return c.kv.Present(keyOrSingleValue) for i, nc := 0, c.kv.Len(); i < nc; i++ {
k, v := c.kv.At(i)
if (len(k) == 0 && b2s(v) == keyOrSingleValue) || b2s(k) == keyOrSingleValue {
return true
}
}
return false
} }
// parseCookie parses a cookie inside cookie buffer and adds it to cookie buffer.. // parseCookie parses a cookie inside cookie buffer and adds it to cookie buffer..
+32 -51
View File
@@ -8,39 +8,35 @@ package httpraw
// undecoded, until [Form.Decode] rewrites them in place. The caller bounds the // undecoded, until [Form.Decode] rewrites them in place. The caller bounds the
// data: Form parses the buffer it is handed and reads nothing more. // data: Form parses the buffer it is handed and reads nothing more.
type Form struct { type Form struct {
buf []byte kv KVBuffer
kvs []argsKV
} }
func (f *Form) EnableBufferGrowth(enableGrowth bool) { f.kv.EnableBufferGrowth(enableGrowth) }
// Reset discards parsed pairs and sets the buffer to parse in place. // Reset discards parsed pairs and sets the buffer to parse in place.
// If buf is nil the current buffer is reused. // If buf is nil the current buffer is reused.
func (f *Form) Reset(buf []byte) { func (f *Form) Reset(buf []byte, capKV int) {
if buf == nil { f.kv.Reset(buf, capKV)
buf = f.buf[:0]
}
*f = Form{
buf: buf,
kvs: f.kvs[:0],
}
} }
// ParseBytes copies the argument bytes to the Form's underlying buffer and parses them. // ParseBytes copies the argument bytes to the Form's underlying buffer and parses them.
func (f *Form) ParseBytes(b []byte) error { func (f *Form) ParseBytes(b []byte) error {
f.Reset(nil) f.Reset(nil, 0)
f.buf = append(f.buf[:0], b...) err := f.kv.ReadFromBytes(b)
if err != nil {
return err
}
return f.Parse() return f.Parse()
} }
// Parse parses the form's buffer in place. // Parse parses the form's buffer in place.
func (f *Form) Parse() error { func (f *Form) Parse() error {
f.kvs = f.kvs[:0] f.kv.discardKVs()
key, value, rest := NextQueryPair(f.buf) key, value, rest := NextQueryPair(f.kv.buf)
for key != nil { for key != nil {
kv := argsKV{key: bytes2tok(f.buf, key)} if !f.kv.setInternal(key, value) {
if value != nil { return errOOM
kv.value = bytes2tok(f.buf, value)
} }
f.kvs = append(f.kvs, kv)
key, value, rest = NextQueryPair(rest) key, value, rest = NextQueryPair(rest)
} }
return nil return nil
@@ -50,66 +46,51 @@ func (f *Form) Parse() error {
// '+' with the bytes they encode. Decoding only shrinks, so no memory is added. // '+' with the bytes they encode. Decoding only shrinks, so no memory is added.
func (f *Form) Decode() error { func (f *Form) Decode() error {
const plusAsSpace = true // Form encoded data, unlike a path. const plusAsSpace = true // Form encoded data, unlike a path.
for i := range f.kvs { nkvs := f.kv.Len()
kv := &f.kvs[i] for i := range nkvs {
n, err := CopyDecodedPercentURL(tok2bytes(f.buf, kv.key), tok2bytes(f.buf, kv.key), plusAsSpace) k, v := f.kv.At(i)
nk, err := CopyDecodedPercentURL(k, k, plusAsSpace)
if err != nil { if err != nil {
return err return err
} } else if len(v) == 0 {
kv.key.len = tokint(n) if nk != len(k) {
if !kv.HasValue() { f.kv.setAt(i, k, v)
}
continue continue
} }
n, err = CopyDecodedPercentURL(tok2bytes(f.buf, kv.value), tok2bytes(f.buf, kv.value), plusAsSpace) nv, err := CopyDecodedPercentURL(v, v, plusAsSpace)
if err != nil { if err != nil {
return err return err
} }
kv.value.len = tokint(n) if nk != len(k) || nv != len(v) {
f.kv.setAt(i, k[:nk], v[:nv])
}
} }
return nil return nil
} }
// Len returns the amount of key-value pairs parsed. // Len returns the amount of key-value pairs parsed.
func (f *Form) Len() int { return len(f.kvs) } func (f *Form) Len() int { return f.kv.Len() }
// Pair returns the i'th key-value pair in wire order. The value is nil for a // Pair returns the i'th key-value pair in wire order. The value is nil for a
// pair with no '=', i.e: "ok" in "ok&q=go", which distinguishes it from "ok=" // pair with no '=', i.e: "ok" in "ok&q=go", which distinguishes it from "ok="
// where the value is present and empty. // where the value is present and empty.
func (f *Form) Pair(i int) (key, value []byte) { func (f *Form) Pair(i int) (key, value []byte) {
kv := f.kvs[i] return f.kv.At(i)
key = tok2bytes(f.buf, kv.key)
if kv.HasValue() {
value = tok2bytes(f.buf, kv.value)
}
return key, value
} }
// Get returns the value of the first pair matching key, nil if absent or if the // Get returns the value of the first pair matching key, nil if absent or if the
// pair has no value. Bytes are compared as stored, so call [Form.Decode] first // pair has no value. Bytes are compared as stored, so call [Form.Decode] first
// when keys may be encoded. // when keys may be encoded.
func (f *Form) Get(key string) []byte { func (f *Form) Get(key string) []byte { return f.kv.Get(key) }
for i := range f.kvs {
gotKey, value := f.Pair(i)
if b2s(gotKey) == key {
return value
}
}
return nil
}
// Has returns true if key is present, with or without a value. // Has returns true if key is present, with or without a value.
func (f *Form) Has(key string) bool { func (f *Form) Has(key string) bool { return f.kv.Present(key) }
for i := range f.kvs {
if b2s(tok2bytes(f.buf, f.kvs[i].key)) == key {
return true
}
}
return false
}
// AppendKeyValues appends the form's wire representation to dst and returns it. // AppendKeyValues appends the form's wire representation to dst and returns it.
func (f *Form) AppendKeyValues(dst []byte) []byte { func (f *Form) AppendKeyValues(dst []byte) []byte {
for i := range f.kvs { nkv := f.kv.Len()
for i := range nkv {
key, value := f.Pair(i) key, value := f.Pair(i)
if i > 0 { if i > 0 {
dst = append(dst, '&') dst = append(dst, '&')
+1 -1
View File
@@ -125,7 +125,7 @@ func TestFormParseReuseNoAlloc(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
allocs := testing.AllocsPerRun(100, func() { allocs := testing.AllocsPerRun(100, func() {
f.Reset(body) f.Reset(body, 0)
f.Parse() f.Parse()
}) })
if allocs != 0 { if allocs != 0 {
+81 -140
View File
@@ -3,7 +3,6 @@ package httpraw
import ( import (
"bytes" "bytes"
"io" "io"
"slices"
"strconv" "strconv"
) )
@@ -29,6 +28,7 @@ const (
flagConnClose flagConnClose
flagNoHTTP11 flagNoHTTP11
flagMangledBuffer // set when header fields appended to buffer via Add,Set calls flagMangledBuffer // set when header fields appended to buffer via Add,Set calls
flagKVAppended // set after KV appended to buffer outside Read methods.
flagReaderEOF flagReaderEOF
// set if [Header.SetStatus] or [Header.SetStatusInt] has been called. // set if [Header.SetStatus] or [Header.SetStatusInt] has been called.
FlagStatusSet FlagStatusSet
@@ -57,30 +57,27 @@ type Header struct {
// Response fields. // Response fields.
statusCode headerSlice statusCode headerSlice
statusText headerSlice statusText headerSlice
_ noCopy
flags Flags
_ noCopy
} }
// Flags returns [Flags] to signal status code has been set, Connection:Close or other useful signals provided by flags. // Flags returns [Flags] to signal status code has been set, Connection:Close or other useful signals provided by flags.
func (h *Header) Flags() Flags { return h.flags } func (h *Header) Flags() Flags { return h.hbuf.kv.flags }
// ConfigBufferGrowth configures the memory the header may use. Setting // ConfigBufferGrowth configures the memory the header may use. Setting
// outlives [Header.Reset]. Call before parsing/reading. // outlives [Header.Reset]. Call before parsing/reading.
// //
// enableBufferGrowth enables growing both the header buffer and the header key/value pair slice. // enableBufferGrowth enables growing both the header buffer and the header key/value pair slice.
func (h *Header) ConfigBufferGrowth(enableBufferGrowth bool) { func (h *Header) ConfigBufferGrowth(enableBufferGrowth bool) {
if !enableBufferGrowth { h.hbuf.kv.EnableBufferGrowth(enableBufferGrowth)
h.flags |= flagNoBufferGrow
} else {
h.flags &^= flagNoBufferGrow
}
} }
// ParseBytes copies the bytes into buffer and parses the HTTP header. It fails if HTTP header data is incomplete. // ParseBytes copies the bytes into buffer and parses the HTTP header. It fails if HTTP header data is incomplete.
func (h *Header) ParseBytes(asResponse bool, b []byte) error { func (h *Header) ParseBytes(asResponse bool, b []byte) error {
h.Reset(nil, 0) h.Reset(nil, 0)
h.hbuf.readFromBytes(b) err := h.hbuf.kv.ReadFromBytes(b)
if err != nil {
return err
}
return h.parse(asResponse) return h.parse(asResponse)
} }
@@ -88,7 +85,7 @@ func (h *Header) ParseBytes(asResponse bool, b []byte) error {
// It fails if HTTP data is incomplete. // It fails if HTTP data is incomplete.
func (h *Header) Parse(asResponse bool) error { func (h *Header) Parse(asResponse bool) error {
debuglog("http:parse:reset") debuglog("http:parse:reset")
h.Reset(h.hbuf.buf, 0) h.Reset(h.hbuf.kv.buf, 0)
debuglog("http:parse:start") debuglog("http:parse:start")
return h.parse(asResponse) return h.parse(asResponse)
} }
@@ -110,9 +107,10 @@ func (h *Header) Parse(asResponse bool) error {
// return err // return err
// } // }
func (h *Header) TryParse(asResponse bool) (needMoreData bool, err error) { func (h *Header) TryParse(asResponse bool) (needMoreData bool, err error) {
if h.flags.HasAny(flagDoneParsingHeader) { flags := h.Flags()
if flags.HasAny(flagDoneParsingHeader) {
return false, errAlreadyParsed return false, errAlreadyParsed
} else if h.flags.HasAny(flagMangledBuffer) { } else if flags.HasAny(flagMangledBuffer) {
return false, errMangledBuffer return false, errMangledBuffer
} }
if asResponse && h.statusCode.len == 0 || !asResponse && h.requestTarget.start == 0 { if asResponse && h.statusCode.len == 0 || !asResponse && h.requestTarget.start == 0 {
@@ -121,80 +119,42 @@ func (h *Header) TryParse(asResponse bool) (needMoreData bool, err error) {
return err == ErrNeedMoreData, err return err == ErrNeedMoreData, err
} }
} }
err = h.parseNextHeaders(h.flags) err = h.parseNextHeaders(flags)
return err == ErrNeedMoreData, err return err == ErrNeedMoreData, err
} }
// ParsingSuccess returns true if TryParse was successful, that is to say it returned needMoreData==false and err==nil. // ParsingSuccess returns true if TryParse was successful, that is to say it returned needMoreData==false and err==nil.
func (h *Header) ParsingSuccess() bool { func (h *Header) ParsingSuccess() bool {
return h.flags.HasAny(flagDoneParsingHeader) return h.Flags().HasAny(flagDoneParsingHeader)
} }
// ReadFromLimited reads at most maxBytesToRead from reader and appends them to underlying buffer. // ReadFromLimited reads at most maxBytesToRead from reader and appends them to underlying buffer.
// Used to accumulate HTTP header for later parsing with [Header.TryParse]. // Used to accumulate HTTP header for later parsing with [Header.TryParse].
// If read is successful (read length>0) and reader returns [io.EOF] then ReadFromLimited will return a nil error. // If read is successful (read length>0) and reader returns [io.EOF] then ReadFromLimited will return a nil error.
func (h *Header) ReadFromLimited(r io.Reader, maxBytesToRead int) (int, error) { func (h *Header) ReadFromLimited(r io.Reader, maxBytesToRead int) (int, error) {
if maxBytesToRead <= 0 { return h.hbuf.kv.ReadLimited(r, maxBytesToRead)
return 0, ErrSmallHeaderBuffer
} else if h.flags.HasAny(flagMangledBuffer) {
return 0, errMangledBuffer
} else if h.flags.HasAny(flagReaderEOF) {
return 0, io.EOF // Now we do return EOF.
}
free := h.BufferFree()
if free < maxBytesToRead {
if h.flags.HasAny(flagNoBufferGrow) {
return 0, ErrSmallHeaderBuffer
}
h.hbuf.buf = slices.Grow(h.hbuf.buf, maxBytesToRead)
}
blen := len(h.hbuf.buf)
b := h.hbuf.buf[blen:min(blen+maxBytesToRead, cap(h.hbuf.buf))]
if len(b) == 0 {
return 0, ErrSmallHeaderBuffer
}
n, err := r.Read(b)
if err != nil && err == io.EOF {
h.flags |= flagReaderEOF
if n > 0 {
err = nil // Nil-out error if read was succesful so as to not spook readers.
}
}
h.hbuf.buf = h.hbuf.buf[:blen+n]
return n, err
} }
// ReadFromBytes appends argument buffer to underlying buffer. // ReadFromBytes appends argument buffer to underlying buffer.
// Used to accumulate HTTP header for later parsing with [Header.TryParse]. // Used to accumulate HTTP header for later parsing with [Header.TryParse].
func (h *Header) ReadFromBytes(b []byte) (int, error) { func (h *Header) ReadFromBytes(b []byte) error {
if len(b) == 0 { return h.hbuf.kv.ReadFromBytes(b)
return 0, ErrSmallHeaderBuffer
}
free := h.BufferFree()
if free < len(b) {
if h.flags.HasAny(flagNoBufferGrow) {
return 0, ErrSmallHeaderBuffer
}
h.hbuf.buf = slices.Grow(h.hbuf.buf, len(b))
}
h.hbuf.readFromBytes(b)
return len(b), nil
} }
// BufferReceived returns the amoung of bytes read during calls to Read* methods. // BufferReceived returns the amoung of bytes read during calls to Read* methods.
// Returns 0 if buffer is invalid/mangled. // Returns 0 if buffer is invalid/mangled.
func (h *Header) BufferReceived() int { func (h *Header) BufferReceived() int {
if h.flags.HasAny(flagMangledBuffer | flagOOMReached) { if h.Flags().HasAny(flagMangledBuffer | flagOOMReached) {
return 0 return 0
} }
return len(h.hbuf.buf) return len(h.hbuf.kv.BufferRaw())
} }
// BufferParsed returns the amount of bytes parsed during a call to Parse* methods. // BufferParsed returns the amount of bytes parsed during a call to Parse* methods.
// If the Parse* method completed without error then BufferParsed returns the header's length including the final "\r\n\r\n" text. // If the Parse* method completed without error then BufferParsed returns the header's length including the final "\r\n\r\n" text.
// BufferParsed returns 0 if the buffer is invalid/mangled or if no header data has been parsed succesfully. // BufferParsed returns 0 if the buffer is invalid/mangled or if no header data has been parsed succesfully.
func (h *Header) BufferParsed() int { func (h *Header) BufferParsed() int {
if h.flags.HasAny(flagMangledBuffer | flagOOMReached) { if h.Flags().HasAny(flagMangledBuffer | flagOOMReached) {
return 0 return 0
} }
return h.hbuf.off return h.hbuf.off
@@ -202,13 +162,13 @@ func (h *Header) BufferParsed() int {
// BufferRaw returns the undeerlying buffer as stored currently in memory. // BufferRaw returns the undeerlying buffer as stored currently in memory.
// The length of the returned buffer is the used portion. Capacity of returned slice is [Header.BufferCapacity]. // The length of the returned buffer is the used portion. Capacity of returned slice is [Header.BufferCapacity].
func (h *Header) BufferRaw() []byte { return h.hbuf.buf } func (h *Header) BufferRaw() []byte { return h.hbuf.kv.BufferRaw() }
// BufferUsed returns the raw memory used. // BufferUsed returns the raw memory used.
// //
// BufferUsed + BufferFree == BufferCapacity // BufferUsed + BufferFree == BufferCapacity
func (h *Header) BufferUsed() int { func (h *Header) BufferUsed() int {
return len(h.hbuf.buf) return len(h.hbuf.kv.BufferRaw())
} }
// BufferFree returns amount of bytes free in underlying buffer. // BufferFree returns amount of bytes free in underlying buffer.
@@ -222,29 +182,12 @@ func (h *Header) BufferFree() int {
// //
// BufferUsed + BufferFree == BufferCapacity // BufferUsed + BufferFree == BufferCapacity
func (h *Header) BufferCapacity() int { func (h *Header) BufferCapacity() int {
return cap(h.hbuf.buf) return cap(h.hbuf.kv.BufferRaw())
} }
// ForEach iterates over header key-value field tuples. // ForEach iterates over header key-value field tuples.
func (h *Header) ForEach(cb func(key, value []byte) error) error { func (h *Header) ForEach(cb func(key, value []byte) bool) {
return h.hbuf.forEach(cb) h.hbuf.kv.ForEach(cb)
}
func (hb *headerBuf) forEach(cb func(key, value []byte) error) error {
nh := len(hb.headers)
for i := range nh {
kv := hb.headers[i]
if !kv.isValid() {
continue
}
key := hb.musttoken(kv.key)
value := hb.musttoken(kv.value)
err := cb(key, value)
if err != nil {
return err
}
}
return nil
} }
// Reset discards all parsed data and sets the buffer data to buf. This method // Reset discards all parsed data and sets the buffer data to buf. This method
@@ -259,23 +202,21 @@ func (h *Header) Reset(buf []byte, numHeaderCapacity int) {
const persistentFlags = flagNoBufferGrow const persistentFlags = flagNoBufferGrow
debuglog("http:reset:hbuf") debuglog("http:reset:hbuf")
h.hbuf.reset(buf, numHeaderCapacity) h.hbuf.reset(buf, numHeaderCapacity)
if h.flags.HasAny(flagNoBufferGrow) && cap(h.hbuf.buf) < 32 { if h.Flags().HasAny(flagNoBufferGrow) && h.BufferCapacity() < 32 {
panic("small buffer and flagNoBufferGrow set") panic("small buffer and flagNoBufferGrow set")
} }
*h = Header{ *h = Header{hbuf: h.hbuf}
hbuf: h.hbuf,
flags: h.flags & persistentFlags,
}
debuglog("http:reset:done") debuglog("http:reset:done")
} }
// Body returns the surplus data following headers. It is only valid as long as Parse* or Reset methods are not called. // Body returns the surplus data following headers. It is only valid as long as Parse* or Reset methods are not called.
func (h *Header) Body() ([]byte, error) { func (h *Header) Body() ([]byte, error) {
debuglog("http:body") debuglog("http:body")
if h.flags.HasAny(flagMangledBuffer) { flags := h.Flags()
if flags.HasAny(flagMangledBuffer) {
return nil, errMangledBuffer return nil, errMangledBuffer
} else if h.flags.HasAny(flagDoneParsingHeader) { } else if flags.HasAny(flagDoneParsingHeader) {
return h.hbuf.buf[h.hbuf.off:], nil return h.BufferRaw()[h.hbuf.off:], nil
} }
return nil, errUnparsed return nil, errUnparsed
} }
@@ -303,48 +244,51 @@ func (h *Header) SetInt(key string, value int64, base int) {
// Set sets a key-value pair in the HTTP header. // Set sets a key-value pair in the HTTP header.
// Calling Set mangles the buffer. // Calling Set mangles the buffer.
func (h *Header) Set(key, value string) { func (h *Header) Set(key, value string) (enoughSpace bool) {
useKv := h.takeReusableSlot(key) return h.hbuf.kv.Set(key, value)
if useKv == nil {
h.appendHeader(key, value) // useKv := h.takeReusableSlot(key)
} else { // if useKv == nil {
useKv.value = h.reuseOrAppend(useKv.value, value) // h.appendHeader(key, value)
} // } else {
// useKv.value = h.reuseOrAppend(useKv.value, value)
// }
} }
// takeReusableSlot returns the valid key-value entry for key with the largest // takeReusableSlot returns the valid key-value entry for key with the largest
// value buffer (best candidate for in-place reuse) and invalidates any other // value buffer (best candidate for in-place reuse) and invalidates any other
// entries sharing the key. Returns nil if the key is not present. // entries sharing the key. Returns nil if the key is not present.
func (h *Header) takeReusableSlot(key string) *argsKV { func (h *Header) takeReusableSlot(key string) *argsKV {
hb := &h.hbuf // hb := &h.hbuf
var useKv *argsKV var useKv *argsKV
for i := 0; i < len(hb.headers); i++ { // for i := 0; i < len(hb.headers); i++ {
// Search for key-value with largest buffer for value to store value reusing buffer. // // Search for key-value with largest buffer for value to store value reusing buffer.
gotkv := &hb.headers[i] // gotkv := &hb.headers[i]
if gotkv.isValid() && b2s(hb.musttoken(gotkv.key)) == key { // if gotkv.isValidHeader() && b2s(hb.musttoken(gotkv.key)) == key {
if useKv == nil { // if useKv == nil {
useKv = gotkv // useKv = gotkv
} else if gotkv.value.len > useKv.value.len { // } else if gotkv.value.len > useKv.value.len {
useKv.invalidate() // useKv.invalidate()
useKv = gotkv // useKv = gotkv
} else { // } else {
gotkv.invalidate() // gotkv.invalidate()
} // }
} // }
} // }
return useKv return useKv
} }
// Get gets the first exact-match value of a key found in the headers. Use [Header.ForEach] to find multiple values corresponding to same key. // Get gets the first exact-match value of a key found in the headers. Use [Header.ForEach] to find multiple values corresponding to same key.
func (h *Header) Get(key string) []byte { func (h *Header) Get(key string) []byte {
debuglog("http:get:start") return h.hbuf.kv.Get(key)
kv := h.peekHeader(key) // debuglog("http:get:start")
if kv.isValid() { // kv := h.peekHeader(key)
debuglog("http:get:found") // if kv.isValidHeader() {
return h.hbuf.musttoken(kv.value) // debuglog("http:get:found")
} // return h.hbuf.musttoken(kv.value)
debuglog("http:get:notfound") // }
return nil // debuglog("http:get:notfound")
// return nil
} }
// GetFold gets the first value whose key matches key under ASCII case-insensitive // GetFold gets the first value whose key matches key under ASCII case-insensitive
@@ -352,11 +296,10 @@ func (h *Header) Get(key string) []byte {
// Use [Header.Get] for exact match and [Header.ForEach] to find multiple values // Use [Header.Get] for exact match and [Header.ForEach] to find multiple values
// corresponding to same key. // corresponding to same key.
func (h *Header) GetFold(key string) []byte { func (h *Header) GetFold(key string) []byte {
hb := &h.hbuf nh := h.hbuf.kv.Len()
for i := 0; i < len(hb.headers); i++ { for i := range nh {
kv := hb.headers[i] if asciiEqualFold(key, b2s(h.hbuf.kv.AtKey(i))) {
if kv.isValid() && asciiEqualFold(b2s(hb.musttoken(kv.key)), key) { return h.hbuf.kv.AtValue(i)
return hb.musttoken(kv.value)
} }
} }
return nil return nil
@@ -387,9 +330,9 @@ func asciiEqualFold(a, b string) bool {
// NormalizeKeys normalizes all header keys. i.e: CONTENT-type -> Content-Type // NormalizeKeys normalizes all header keys. i.e: CONTENT-type -> Content-Type
func (h *Header) NormalizeKeys() { func (h *Header) NormalizeKeys() {
for _, kv := range h.hbuf.headers { for i, kv := range h.hbuf.kv.kvs {
if kv.isValid() { if kv.isValidHeader() {
NormalizeHeaderKey(h.hbuf.musttoken(kv.key)) NormalizeHeaderKey(h.hbuf.kv.AtKey(i))
} }
} }
} }
@@ -507,19 +450,19 @@ func (h *Header) Status() (code, statusText []byte) {
if h.statusCode.len == 0 { if h.statusCode.len == 0 {
return nil, nil return nil, nil
} }
return h.hbuf.musttoken(h.statusCode), h.hbuf.musttoken(h.statusText) return h.hbuf.kv.musttoken(h.statusCode), h.hbuf.kv.musttoken(h.statusText)
} }
// SetStatus sets the response header's status code and status text. i.e: "200" "OK". // SetStatus sets the response header's status code and status text. i.e: "200" "OK".
func (h *Header) SetStatus(code, statusText string) { func (h *Header) SetStatus(code, statusText string) {
h.flags |= FlagStatusSet h.hbuf.kv.flags |= FlagStatusSet
h.statusCode = h.reuseOrAppend(h.statusCode, code) h.statusCode = h.reuseOrAppend(h.statusCode, code)
h.statusText = h.reuseOrAppend(h.statusText, statusText) h.statusText = h.reuseOrAppend(h.statusText, statusText)
} }
// SetStatusInt is identical to [Header.SetStatus] but performs integer to text conversion for status code. // SetStatusInt is identical to [Header.SetStatus] but performs integer to text conversion for status code.
func (h *Header) SetStatusInt(code int64, statusText string) { func (h *Header) SetStatusInt(code int64, statusText string) {
h.flags |= FlagStatusSet h.hbuf.kv.flags |= FlagStatusSet
h.statusCode = h.reuseOrAppendInt(h.statusCode, code, 10) h.statusCode = h.reuseOrAppendInt(h.statusCode, code, 10)
h.statusText = h.reuseOrAppend(h.statusText, statusText) h.statusText = h.reuseOrAppend(h.statusText, statusText)
} }
@@ -528,13 +471,13 @@ func (h *Header) getNonEmptyValue(s headerSlice) []byte {
if s.len == 0 { if s.len == 0 {
return nil // If empty then value is invalid, return nil. return nil // If empty then value is invalid, return nil.
} }
return h.hbuf.musttoken(s) return h.hbuf.kv.musttoken(s)
} }
// AppendRequest appends the request header representation to the buffer and returns the result. // AppendRequest appends the request header representation to the buffer and returns the result.
func (h *Header) AppendRequest(dst []byte) ([]byte, error) { func (h *Header) AppendRequest(dst []byte) ([]byte, error) {
proto := h.Protocol() proto := h.Protocol()
if h.flags.HasAny(flagOOMReached) { if h.hbuf.kv.flags.HasAny(flagOOMReached) {
return dst, errOOM return dst, errOOM
} else if h.requestTarget.len == 0 || h.method.len == 0 { } else if h.requestTarget.len == 0 || h.method.len == 0 {
return dst, errNeedMethodURI return dst, errNeedMethodURI
@@ -574,7 +517,7 @@ func (h *Header) AppendResponse(dst []byte) ([]byte, error) {
// AppendResponseNoHeaders appends the first line of the response containing protocol and status code/text: i.e: "HTTP/1.1 200 OK\r\n" // AppendResponseNoHeaders appends the first line of the response containing protocol and status code/text: i.e: "HTTP/1.1 200 OK\r\n"
func (h *Header) AppendResponseNoHeaders(dst []byte) ([]byte, error) { func (h *Header) AppendResponseNoHeaders(dst []byte) ([]byte, error) {
proto := h.Protocol() proto := h.Protocol()
if h.flags.HasAny(flagOOMReached) { if h.hbuf.kv.flags.HasAny(flagOOMReached) {
return dst, errOOM return dst, errOOM
} else if h.statusCode.len == 0 || h.statusText.len == 0 { } else if h.statusCode.len == 0 || h.statusText.len == 0 {
return dst, errBadStatusCodeTxt return dst, errBadStatusCodeTxt
@@ -595,12 +538,10 @@ func (h *Header) AppendResponseNoHeaders(dst []byte) ([]byte, error) {
// AppendHeaders appends headers to buffer. Use AppendRequest and AppendResponse over this. // AppendHeaders appends headers to buffer. Use AppendRequest and AppendResponse over this.
// Does not append extra \r\n to end. Appends nothing if contains no headers. // Does not append extra \r\n to end. Appends nothing if contains no headers.
func (h *Header) AppendHeaders(dst []byte) []byte { func (h *Header) AppendHeaders(dst []byte) []byte {
for i, n := 0, len(h.hbuf.headers); i < n; i++ { for i, kv := range h.hbuf.kv.kvs {
kv := &h.hbuf.headers[i] if kv.isValidHeader() {
if kv.isValid() { k, v := h.hbuf.kv.At(i)
key := h.hbuf.musttoken(kv.key) dst = appendHeaderLine(dst, b2s(k), b2s(v))
value := h.hbuf.musttoken(kv.value)
dst = appendHeaderLine(dst, b2s(key), b2s(value))
} }
} }
return dst return dst
+204 -20
View File
@@ -1,7 +1,9 @@
package httpraw package httpraw
import ( import (
"io"
"slices" "slices"
"strconv"
"github.com/soypat/lneto/internal" "github.com/soypat/lneto/internal"
) )
@@ -26,6 +28,52 @@ func (mb *KVBuffer) EnableBufferGrowth(enableGrowth bool) {
} }
} }
func (mb *KVBuffer) discardKVs() { mb.kvs = mb.kvs[:0] }
func (mb *KVBuffer) BufferGrowthEnabled() bool { return !mb.flags.HasAny(flagNoBufferGrow) }
func (mb *KVBuffer) ReadFromBytes(buf []byte) error {
if mb.flags.HasAny(flagMangledBuffer) {
return errMangledBuffer
} else if len(buf)+cap(mb.buf) > maxBufLen {
return errOOM
}
free := mb.free()
if len(buf) > free && !mb.BufferGrowthEnabled() {
return errOOM
}
mb.buf = append(mb.buf, buf...)
return nil
}
func (mb *KVBuffer) ReadLimited(r io.Reader, limit int) (int, error) {
if mb.flags.HasAny(flagMangledBuffer) {
return 0, errMangledBuffer
} else if mb.flags.HasAny(flagReaderEOF) {
return 0, io.EOF
} else if limit <= 0 {
return 0, io.ErrNoProgress
} else if len(mb.buf) >= maxBufLen {
return 0, errOOM
}
free := mb.free()
if limit > free {
if !mb.BufferGrowthEnabled() {
return 0, errOOM
}
mb.buf = slices.Grow(mb.buf, limit)
}
n, err := r.Read(mb.buf[len(mb.buf):min(len(mb.buf)+limit, maxBufLen)])
mb.buf = mb.buf[:len(mb.buf)+n]
if err != nil {
if n > 0 && err == io.EOF {
mb.flags |= flagReaderEOF
err = nil // Nil out EOF to not scare off readers.
}
}
return n, err
}
func (mb *KVBuffer) Reset(buf []byte, kvCap int) { func (mb *KVBuffer) Reset(buf []byte, kvCap int) {
if buf == nil { if buf == nil {
mb.buf = mb.buf[:0] mb.buf = mb.buf[:0]
@@ -41,15 +89,19 @@ func (mb *KVBuffer) CopyFrom(src *KVBuffer) {
mb.kvs = append(mb.kvs[:0], src.kvs...) mb.kvs = append(mb.kvs[:0], src.kvs...)
} }
// Get returns the value of the first pair matching key.
// Bytes are compared as stored, so if using a Form call [Form.Decode] first when keys may be encoded.
// Returns nil for an absent key and for a valueless pair alike, so use
// [KVBuffer.Present] to tell the two apart.
func (mb *KVBuffer) Get(key string) []byte { func (mb *KVBuffer) Get(key string) []byte {
v := mb.getIdx(key) i := mb.getIdx(key)
if v < 0 { if i < 0 {
return nil return nil
} }
return mb.musttoken(mb.kvs[v].value) return mb.AtValue(i)
} }
// ForEach iterates over the cookie's key-value pairs until cb returns false. // ForEach iterates over the cookie's key-value pairs as stored until cb returns false.
func (c *KVBuffer) ForEach(cb func(key, value []byte) bool) { func (c *KVBuffer) ForEach(cb func(key, value []byte) bool) {
nc := len(c.kvs) nc := len(c.kvs)
for i := range nc { for i := range nc {
@@ -61,18 +113,76 @@ func (c *KVBuffer) ForEach(cb func(key, value []byte) bool) {
} }
} }
func (mb *KVBuffer) Present(key string) bool { // Has returns true if key is present, with or without a value.
func (mb *KVBuffer) Present(key string) bool { // TODO: rename to Has.
return mb.getIdx(key) >= 0 return mb.getIdx(key) >= 0
} }
func (mb *KVBuffer) Add(key, value string) bool {
// Has returns true if key is present, with or without a value.
func (mb *KVBuffer) HasKeyValue(key, value string) bool {
idx := mb.getIdx(key)
if idx >= 0 {
return b2s(mb.musttoken(mb.kvs[idx].value)) == value
}
return false
}
func (mb *KVBuffer) Add(key, value string) (enoughSpace bool) {
mb.appendPair(key, value) mb.appendPair(key, value)
return mb.getIdx(key) >= 0 return mb.getIdx(key) >= 0
} }
func (mb *KVBuffer) setInternal(key, value []byte) bool { // Set replaces key's value and invalidates every other pair sharing the key, so
// a following [KVBuffer.Get] sees exactly one value.
//
// It rewrites in place when it can: of the pairs it would invalidate it keeps
// the smallest whose key and value regions both still hold the new pair,
// leaving the roomier regions for a later Set. When none fits the pair is
// appended with [KVBuffer.Add] and the invalidated regions are stranded, since
// nothing here compacts the buffer.
func (mb *KVBuffer) Set(key, value string) (enoughSpace bool) {
reuse := -1
for i := range mb.kvs {
kv := &mb.kvs[i]
if !kv.isValid() || b2s(mb.musttoken(kv.key)) != key {
continue
}
// A valueless pair holds no value region, so reusing one would write the
// value over byte 0. Let it fall through to Add, which gives the pair a
// real region and keeps "ok" distinct from "ok=".
fits := kv.HasValue() && int(kv.key.len) >= len(key) && int(kv.value.len) >= len(value)
if fits && (reuse < 0 || kv.size() < mb.kvs[reuse].size()) {
if reuse >= 0 {
mb.kvs[reuse].invalidate() // Superseded by a tighter fit.
}
reuse = i
continue
}
kv.invalidate()
}
if reuse < 0 {
return mb.Add(key, value)
}
mb.overwriteAt(reuse, key, value)
return true
}
// overwriteAt writes key and value over the regions pair i already owns. The
// caller must have checked both fit; the bytes freed by a shorter pair are
// stranded, not reclaimed.
func (mb *KVBuffer) overwriteAt(i int, key, value string) {
mb.flags |= flagMangledBuffer
kv := &mb.kvs[i]
copy(mb.buf[kv.key.start:], key)
kv.key.len = tokint(len(key))
copy(mb.buf[kv.value.start:], value)
kv.value.len = tokint(len(value))
}
func (mb *KVBuffer) setInternal(key, value []byte) (enoughSpace bool) {
if !mb.canAddOneKV() { if !mb.canAddOneKV() {
return false return false
} }
mb.flags |= flagKVAppended
mb.kvs = append(mb.kvs, argsKV{ mb.kvs = append(mb.kvs, argsKV{
key: mb.slice(key), key: mb.slice(key),
value: mb.slice(value), value: mb.slice(value),
@@ -83,10 +193,26 @@ func (mb *KVBuffer) setInternal(key, value []byte) bool {
func (mb *KVBuffer) Len() int { return len(mb.kvs) } func (mb *KVBuffer) Len() int { return len(mb.kvs) }
func (mb *KVBuffer) At(i int) (key, value []byte) { func (mb *KVBuffer) At(i int) (key, value []byte) {
kv := mb.kvs[i] kv := mb.kvs[i]
if !kv.HasValue() {
return mb.musttoken(kv.key), nil
}
return mb.musttoken(kv.key), mb.musttoken(kv.value) return mb.musttoken(kv.key), mb.musttoken(kv.value)
} }
func (mb *KVBuffer) AtKey(i int) (key []byte) { return mb.musttoken(mb.kvs[i].key) } func (mb *KVBuffer) setAt(i int, k, v []byte) {
func (mb *KVBuffer) AtValue(i int) (key []byte) { return mb.musttoken(mb.kvs[i].value) } mb.flags |= flagMangledBuffer
mb.kvs[i] = argsKV{
key: bytes2tok(mb.buf, k),
value: bytes2tok(mb.buf, v),
}
}
func (mb *KVBuffer) AtKey(i int) (key []byte) { return mb.musttoken(mb.kvs[i].key) }
func (mb *KVBuffer) AtValue(i int) (key []byte) {
if !mb.kvs[i].HasValue() {
return nil
}
return mb.musttoken(mb.kvs[i].value)
}
func (mb *KVBuffer) getIdx(key string) int { func (mb *KVBuffer) getIdx(key string) int {
for i, kv := range mb.kvs { for i, kv := range mb.kvs {
@@ -106,12 +232,21 @@ func (mb *KVBuffer) getInvalidIdx() int {
return -1 return -1
} }
func (mb *KVBuffer) getInvalidOrKeyIdx(key string) int {
for i, kv := range mb.kvs {
if !kv.isValid() || key == b2s(mb.musttoken(kv.key)) {
return i
}
}
return -1
}
// reserve ensures need free bytes are available in the buffer, growing it when // reserve ensures need free bytes are available in the buffer, growing it when
// permitted. It accounts for the byte-0 reservation on an empty buffer (see // permitted. It accounts for the byte-0 reservation on an empty buffer (see
// mustAppendSlice). It returns false and sets flagOOMReached when the space // mustAppendSlice). It returns false and sets flagOOMReached when the space
// cannot be guaranteed: a tokint offset overflow, or a full buffer with // cannot be guaranteed: a tokint offset overflow, or a full buffer with
// flagNoBufferGrow set. // flagNoBufferGrow set.
func (mb *KVBuffer) reserve(need int) bool { func (mb *KVBuffer) reserve(need int) (enoughSpace bool) {
if len(mb.buf) == 0 { if len(mb.buf) == 0 {
need++ // mustAppend* reserves byte 0 on an empty buffer. need++ // mustAppend* reserves byte 0 on an empty buffer.
} }
@@ -130,24 +265,31 @@ func (mb *KVBuffer) reserve(need int) bool {
} }
func (mb *KVBuffer) appendPair(key, value string) bool { func (mb *KVBuffer) appendPair(key, value string) bool {
// reserve accounts for the byte-0 reservation mustAppendSlice makes on an
// empty buffer, and drops (flagging OOM) rather than panicking when growth
// is disabled and space runs out.
if !mb.canAddOneKV() || !mb.reserve(len(key)+len(value)) { if !mb.canAddOneKV() || !mb.reserve(len(key)+len(value)) {
return false return false
} }
k := mb.mustAppendSlice(key) mb.flags |= flagKVAppended
v := mb.mustAppendSlice(value)
debuglog("http:appendhdr:grow-hdrs")
mb.kvs = append(mb.kvs, argsKV{ mb.kvs = append(mb.kvs, argsKV{
key: k, key: mb.mustAppendSlice(key),
value: v, value: mb.mustAppendSlice(value),
}) })
return true return true
} }
func (mb *KVBuffer) canAddOneKV() bool { func (mb *KVBuffer) appendPairInt(key string, value int64, base int) bool {
vlen := internal.IntLen(value, base)
if !mb.canAddOneKV() || !mb.reserve(len(key)+vlen) {
return false
}
mb.flags |= flagKVAppended
mb.kvs = append(mb.kvs, argsKV{
key: mb.mustAppendSlice(key),
value: mb.mustAppendInt(value, base),
})
return true
}
func (mb *KVBuffer) canAddOneKV() (enoughSpace bool) {
return len(mb.kvs) < cap(mb.kvs) || mb.flags&flagNoBufferGrow == 0 return len(mb.kvs) < cap(mb.kvs) || mb.flags&flagNoBufferGrow == 0
} }
@@ -161,7 +303,20 @@ func (mb *KVBuffer) mustAppendSlice(value string) headerSlice {
return mb.slice(mb.buf[L : L+len(value)]) return mb.slice(mb.buf[L : L+len(value)])
} }
func (hb *KVBuffer) mustAppendInt(value int64, base int) headerSlice {
L := len(hb.buf)
if L == 0 {
L++ // Valid key-values start after byte 0.
}
v := strconv.AppendInt(hb.buf[L:L], value, base)
hb.buf = hb.buf[:L+len(v)]
return hb.slice(hb.buf[L : L+len(v)])
}
func (mb *KVBuffer) slice(value []byte) headerSlice { func (mb *KVBuffer) slice(value []byte) headerSlice {
if value == nil {
return headerSlice{}
}
return bytes2tok(mb.buf, value) return bytes2tok(mb.buf, value)
} }
@@ -169,3 +324,32 @@ func (mb KVBuffer) musttoken(slice headerSlice) []byte {
return tok2bytes(mb.buf, slice) return tok2bytes(mb.buf, slice)
} }
func (mb *KVBuffer) noKV() argsKV { return argsKV{} } func (mb *KVBuffer) noKV() argsKV { return argsKV{} }
type tokint = uint16
type headerSlice struct {
start tokint
len tokint
}
type argsKV struct {
key headerSlice
value headerSlice // value start >0 means value is present.
}
// isValid is for stores parsed in place, where offset 0 is the first key so
// only length can signal presence. Empty keys are valid: see valueless cookies.
func (kv argsKV) isValid() bool {
return kv.key.len > 0 || kv.value.len > 0
}
// isValidHeader is for the append-built [Header] store, where mustAppendSlice
// burns byte 0 so a zero offset means absent. Drops offset-0 pairs otherwise.
func (kv argsKV) isValidHeader() bool { return kv.key.start > 0 }
func (kv *argsKV) invalidate() {
*kv = argsKV{}
}
// size is the buffer a pair occupies, used to pick the tightest slot to reuse.
func (kv argsKV) size() int { return int(kv.key.len) + int(kv.value.len) }
+36 -107
View File
@@ -49,42 +49,21 @@ var (
const maxBufLen = 0xffff const maxBufLen = 0xffff
type headerBuf struct { type headerBuf struct {
kv KVBuffer
// buf[:len] holds entire HTTP header data, which may be normalized by [flags]. buf[off:len] holds data not yet processed during parsing. // buf[:len] holds entire HTTP header data, which may be normalized by [flags]. buf[off:len] holds data not yet processed during parsing.
buf []byte // buf []byte
// offset into buf for parsing. // offset into buf for parsing.
off int off int
// args contains key-value store. // args contains key-value store.
headers []argsKV // headers []argsKV
} }
// reset sets the buffer data and discards all parsed data. The field table is // reset sets the buffer data and discards all parsed data. The field table is
// grown to match the new buffer's capacity and never shrinks, so a header // grown to match the new buffer's capacity and never shrinks, so a header
// reused across requests settles on its largest buffer and stops allocating. // reused across requests settles on its largest buffer and stops allocating.
func (h *headerBuf) reset(buf []byte, numHeaderCapacity int) { func (h *headerBuf) reset(buf []byte, numHeaderCapacity int) {
if buf == nil { h.kv.Reset(buf, numHeaderCapacity)
buf = h.buf[:0] // Reuse buffer but discard raw data on nil input. h.off = 0
}
if numHeaderCapacity != 0 {
internal.SliceReuse(&h.headers, numHeaderCapacity)
} else {
h.headers = h.headers[:0]
}
*h = headerBuf{
buf: buf,
headers: h.headers,
}
}
type tokint = uint16
type headerSlice struct {
start tokint
len tokint
}
type argsKV struct {
key headerSlice
value headerSlice // value start >0 means value is present.
} }
type scannerState struct { type scannerState struct {
@@ -109,20 +88,22 @@ func (h *Header) parse(asResponse bool) (err error) {
return err return err
} }
debuglog("http:firstline:done") debuglog("http:firstline:done")
err = h.parseNextHeaders(h.flags) err = h.parseNextHeaders(h.Flags())
debuglog("http:headers:done") debuglog("http:headers:done")
return err return err
} }
func (h *Header) parseFirstLine(asResponse bool) (err error) { func (h *Header) parseFirstLine(asResponse bool) (err error) {
if len(h.hbuf.buf) > maxBufLen { if len(h.hbuf.kv.buf) > maxBufLen {
return errBufferTooLarge // Offsets would overflow uint16 tokint. return errBufferTooLarge // Offsets would overflow uint16 tokint.
} }
flags := h.Flags()
if asResponse { if asResponse {
h.statusCode, h.statusText, h.flags, err = h.hbuf.parseFirstLineResponse(h.flags) h.statusCode, h.statusText, flags, err = h.hbuf.parseFirstLineResponse(flags)
} else { } else {
h.method, h.requestTarget, h.proto, h.flags, err = h.hbuf.parseFirstLineRequest(h.flags) h.method, h.requestTarget, h.proto, flags, err = h.hbuf.parseFirstLineRequest(flags)
} }
h.hbuf.kv.flags = flags
return err return err
} }
@@ -130,39 +111,33 @@ func (h *Header) parseNextHeaders(flags Flags) error {
var ss scannerState var ss scannerState
h.hbuf.parseNextHeaders(&ss, flags) h.hbuf.parseNextHeaders(&ss, flags)
if ss.err != nil { if ss.err != nil {
h.flags |= flagConnClose h.hbuf.kv.flags |= flagConnClose
return ss.err return ss.err
} }
h.flags |= flagDoneParsingHeader h.hbuf.kv.flags |= flagDoneParsingHeader
return nil return nil
} }
func (hb *headerBuf) readFromBytes(b []byte) { func (hb *headerBuf) free() int { return hb.kv.free() }
hb.buf = append(hb.buf, b...)
}
func (hb *headerBuf) free() int { return cap(hb.buf) - len(hb.buf) }
func (hb *headerBuf) parseNextHeaders(ss *scannerState, flags Flags) { func (hb *headerBuf) parseNextHeaders(ss *scannerState, flags Flags) {
debuglog("http:nexthdr:loop") debuglog("http:nexthdr:loop")
for kv := hb.next(ss); kv.isValid(); kv = hb.next(ss) { for kv := hb.next(ss); kv.isValidHeader(); kv = hb.next(ss) {
if len(hb.headers) == cap(hb.headers) && flags.HasAny(flagNoBufferGrow) { if !hb.kv.canAddOneKV() {
// Refuse to grow the headers slice: the caller granted this much
// memory and no more, see [Header.Reset].
ss.err = ErrHeaderTooMany ss.err = ErrHeaderTooMany
return return
} }
hb.headers = append(hb.headers, kv) hb.kv.kvs = append(hb.kv.kvs, kv)
} }
debuglog("http:nexthdr:done") debuglog("http:nexthdr:done")
} }
func (hb *headerBuf) offBuf() []byte { func (hb *headerBuf) offBuf() []byte {
return hb.buf[hb.off:] return hb.kv.buf[hb.off:]
} }
func (hb *headerBuf) skipLeadingCRLF() { func (hb *headerBuf) skipLeadingCRLF() {
for hb.off < len(hb.buf) && (hb.buf[hb.off] == '\n' || hb.buf[hb.off] == '\r') { for hb.off < len(hb.kv.buf) && (hb.kv.buf[hb.off] == '\n' || hb.kv.buf[hb.off] == '\r') {
hb.off++ hb.off++
} }
} }
@@ -172,7 +147,7 @@ func (hb *headerBuf) scanLine() []byte {
if len(buf) > 0 && buf[len(buf)-1] == '\r' { if len(buf) > 0 && buf[len(buf)-1] == '\r' {
buf = buf[:len(buf)-1] // exclude carriage return. buf = buf[:len(buf)-1] // exclude carriage return.
} }
if hb.off < len(hb.buf) { if hb.off < len(hb.kv.buf) {
hb.off++ // consume newline. hb.off++ // consume newline.
} }
return buf return buf
@@ -206,8 +181,8 @@ func (hb *headerBuf) parseFirstLineRequest(initFlags Flags) (method, uri, proto
reqURIEnd := bytes.IndexByte(b[methodEnd+1:], ' ') reqURIEnd := bytes.IndexByte(b[methodEnd+1:], ' ')
if reqURIEnd > 0 { if reqURIEnd > 0 {
reqURIEnd += methodEnd + 1 reqURIEnd += methodEnd + 1
uri = hb.slice(b[methodEnd+1 : reqURIEnd]) uri = hb.kv.slice(b[methodEnd+1 : reqURIEnd])
proto = hb.slice(b[reqURIEnd+1:]) // Skip space before protocol. proto = hb.kv.slice(b[reqURIEnd+1:]) // Skip space before protocol.
if b2s(b[reqURIEnd+1:]) != strHTTP11 { if b2s(b[reqURIEnd+1:]) != strHTTP11 {
flags |= flagNoHTTP11 flags |= flagNoHTTP11
} }
@@ -216,9 +191,9 @@ func (hb *headerBuf) parseFirstLineRequest(initFlags Flags) (method, uri, proto
} else { } else {
// No version provided. // No version provided.
flags |= flagNoHTTP11 flags |= flagNoHTTP11
uri = hb.slice(b[methodEnd+1:]) uri = hb.kv.slice(b[methodEnd+1:])
} }
method = hb.slice(b[:methodEnd]) method = hb.kv.slice(b[:methodEnd])
return method, uri, proto, flags, nil return method, uri, proto, flags, nil
} }
@@ -260,66 +235,19 @@ func (hb *headerBuf) parseFirstLineResponse(initFlags Flags) (statusCode, status
return statusCode, statusText, flags, errBadStatusCode return statusCode, statusText, flags, errBadStatusCode
} }
} }
statusCode = hb.slice(code) statusCode = hb.kv.slice(code)
if codeEnd < len(b) { if codeEnd < len(b) {
statusText = hb.slice(b[codeEnd+1:]) // Skip space before text. statusText = hb.kv.slice(b[codeEnd+1:]) // Skip space before text.
} }
debuglog("http:resp:done") debuglog("http:resp:done")
return statusCode, statusText, flags, nil return statusCode, statusText, flags, nil
} }
func (kv argsKV) isValid() bool {
return kv.key.start > 0
}
func (kv *argsKV) invalidate() {
*kv = argsKV{}
}
func (tb headerBuf) musttoken(slice headerSlice) []byte {
return tok2bytes(tb.buf, slice)
}
func (tb headerBuf) slice(b []byte) headerSlice {
return bytes2tok(tb.buf, b)
}
func (kv argsKV) HasValue() bool { return kv.value.start > 0 } func (kv argsKV) HasValue() bool { return kv.value.start > 0 }
func (h *Header) hasHeaderValue(key, value string) bool {
kv := h.peekHeader(key)
return kv.isValid() && b2s(h.hbuf.musttoken(kv.value)) == value
}
// peekHeader returns header key-value for the given key.
//
// The returned value is valid until the request is released,
// either though ReleaseRequest or your request handler returning.
// Do not store references to returned value. Make copies instead.
func (h *Header) peekHeader(key string) argsKV {
hb := &h.hbuf
for i := 0; i < len(h.hbuf.headers); i++ {
if b2s(hb.musttoken(h.hbuf.headers[i].key)) == key {
return h.hbuf.headers[i]
}
}
return hb.noKV()
}
func (hb *headerBuf) mustAppendSlice(value string) headerSlice {
L := len(hb.buf)
if L == 0 {
L++ // Valid key-values start after 0.
}
copy(hb.buf[L:L+len(value)], value)
hb.buf = hb.buf[:L+len(value)]
return hb.slice(hb.buf[L : L+len(value)])
}
func (h *Header) reuseOrAppend(tok headerSlice, value string) headerSlice { func (h *Header) reuseOrAppend(tok headerSlice, value string) headerSlice {
if tok.len > tokint(len(value)) { if tok.len > tokint(len(value)) {
copy(h.hbuf.musttoken(tok), value) copy(h.hbuf.kv.musttoken(tok), value)
tok.len = tokint(len(value)) tok.len = tokint(len(value))
return tok return tok
} }
@@ -332,7 +260,7 @@ func (h *Header) appendSlice(value string) headerSlice {
return headerSlice{} return headerSlice{}
} }
h.flags |= flagMangledBuffer h.flags |= flagMangledBuffer
return h.hbuf.mustAppendSlice(value) return h.hbuf.kv.mustAppendSlice(value)
} }
func (h *Header) appendHeader(key, value string) { func (h *Header) appendHeader(key, value string) {
@@ -437,7 +365,7 @@ func (hb *headerBuf) next(ss *scannerState) argsKV {
ss.nextColon = -1 ss.nextColon = -1
ss.nextNewLine = -1 ss.nextNewLine = -1
} }
buf := hb.buf[hb.off:] buf := hb.kv.buf[hb.off:]
blen := len(buf) blen := len(buf)
if blen >= 2 && buf[0] == '\r' && buf[1] == '\n' { if blen >= 2 && buf[0] == '\r' && buf[1] == '\n' {
hb.off += 2 hb.off += 2
@@ -484,7 +412,7 @@ func (hb *headerBuf) next(ss *scannerState) argsKV {
// Ready to store key.. // Ready to store key..
var resultKV argsKV var resultKV argsKV
resultKV.key = hb.slice(buf[:n]) resultKV.key = hb.kv.slice(buf[:n])
n++ // consume colon. n++ // consume colon.
for len(buf) > n && buf[n] == ' ' { for len(buf) > n && buf[n] == ' ' {
n++ // Trim leading spaces. n++ // Trim leading spaces.
@@ -511,18 +439,19 @@ func (hb *headerBuf) next(ss *scannerState) argsKV {
if valueEnd > valueStart && buf[valueEnd-1] == '\r' { if valueEnd > valueStart && buf[valueEnd-1] == '\r' {
valueEnd-- // Trim \r character if present before value. valueEnd-- // Trim \r character if present before value.
} }
resultKV.value = hb.slice(buf[valueStart:valueEnd]) resultKV.value = hb.kv.slice(buf[valueStart:valueEnd])
hb.off += n hb.off += n
return resultKV return resultKV
} }
// ConnectionClose returns true if 'Connection: close' header is set or if a invalid header was found. // ConnectionClose returns true if 'Connection: close' header is set or if a invalid header was found.
func (h *Header) ConnectionClose() bool { func (h *Header) ConnectionClose() bool {
closed := h.flags.HasAny(flagConnClose) || flags := h.Flags()
h.hasHeaderValue(headerConnection, strClose) || closed := flags.HasAny(flagConnClose) ||
(h.flags.HasAny(flagNoHTTP11) && !h.hasHeaderValue(headerConnection, "keep-alive")) h.hbuf.kv.HasKeyValue(headerConnection, strClose) ||
(flags.HasAny(flagNoHTTP11) && !h.hbuf.kv.HasKeyValue(headerConnection, "keep-alive"))
if closed { if closed {
h.flags |= flagConnClose h.hbuf.kv.flags |= flagConnClose
} }
return closed return closed
} }
+8
View File
@@ -331,6 +331,14 @@ func TestCookie_ParseBytes(t *testing.T) {
if string(c.Get("Path")) != "/" { if string(c.Get("Path")) != "/" {
t.Errorf("Path = %q; want /", c.Get("Path")) t.Errorf("Path = %q; want /", c.Get("Path"))
} }
// The first pair sits at buffer offset 0, which a presence check keyed on
// the offset rather than the length reads as absent.
if string(c.Get("session")) != "abc123" {
t.Errorf("Get(session) = %q; want abc123", c.Get("session"))
}
if !c.HasKeyOrSingleValue("session") {
t.Error("expected first pair to be present by key")
}
if !c.HasKeyOrSingleValue("Secure") { if !c.HasKeyOrSingleValue("Secure") {
t.Error("expected Secure flag") t.Error("expected Secure flag")
} }