mirror of
https://github.com/soypat/lneto.git
synced 2026-09-10 00:29:34 +00:00
work on KVBuffer exhausted semantics
This commit is contained in:
@@ -1267,9 +1267,9 @@ func TestHandleBrowserSizedRequest(t *testing.T) {
|
|||||||
sm.Reset(1)
|
sm.Reset(1)
|
||||||
sm.Handle("GET /echo", func(exch *Exchange) {
|
sm.Handle("GET /echo", func(exch *Exchange) {
|
||||||
gotMode = string(exch.RequestHeader("X-Mode"))
|
gotMode = string(exch.RequestHeader("X-Mode"))
|
||||||
exch.RequestHeaderRaw().ForEach(func(key, value []byte) error {
|
exch.RequestHeaderRaw().ForEach(func(key, value []byte) bool {
|
||||||
fields++
|
fields++
|
||||||
return nil
|
return true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
conn := newConn(request)
|
conn := newConn(request)
|
||||||
@@ -1321,7 +1321,7 @@ func TestHandleRequestTooLargeAnswers431(t *testing.T) {
|
|||||||
// Room for the fields, but not for the bytes they arrive in.
|
// Room for the fields, but not for the bytes they arrive in.
|
||||||
name: "more bytes than the buffer holds",
|
name: "more bytes than the buffer holds",
|
||||||
cfg: ExchangeConfig{RawBuf: make([]byte, 2*1024), RequestBufferLim: 1024, NumHeaderKVCap: 1024, NoRequestBufferGrowth: true},
|
cfg: ExchangeConfig{RawBuf: make([]byte, 2*1024), RequestBufferLim: 1024, NumHeaderKVCap: 1024, NoRequestBufferGrowth: true},
|
||||||
wantErr: httpraw.ErrSmallHeaderBuffer,
|
wantErr: httpraw.ErrBufferExhausted,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|||||||
+1
-1
@@ -79,7 +79,7 @@ func Handle(exch *Exchange, mux Mux, backoff lneto.BackoffStrategy) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (exch *Exchange) handleError(err error) {
|
func (exch *Exchange) handleError(err error) {
|
||||||
if err == httpraw.ErrHeaderTooMany || err == httpraw.ErrSmallHeaderBuffer || exch.reqHdr.BufferFree() == 0 {
|
if err == httpraw.ErrHeaderTooMany || err == httpraw.ErrBufferExhausted || exch.reqHdr.BufferFree() == 0 {
|
||||||
// The peer is owed an answer: no larger buffer is coming, so
|
// The peer is owed an answer: no larger buffer is coming, so
|
||||||
// say so instead of dropping the connection, RFC 6585 5.
|
// say so instead of dropping the connection, RFC 6585 5.
|
||||||
exch.StageHeader("Content-Length", "0")
|
exch.StageHeader("Content-Length", "0")
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func (c *Cookie) Parse() error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !c.kv.setInternal(k, v) {
|
if !c.kv.setInternal(k, v) {
|
||||||
return errOOM
|
return ErrBufferExhausted
|
||||||
}
|
}
|
||||||
|
|
||||||
off += n
|
off += n
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func (f *Form) Parse() error {
|
|||||||
key, value, rest := NextQueryPair(f.kv.buf)
|
key, value, rest := NextQueryPair(f.kv.buf)
|
||||||
for key != nil {
|
for key != nil {
|
||||||
if !f.kv.setInternal(key, value) {
|
if !f.kv.setInternal(key, value) {
|
||||||
return errOOM
|
return ErrBufferExhausted
|
||||||
}
|
}
|
||||||
key, value, rest = NextQueryPair(rest)
|
key, value, rest = NextQueryPair(rest)
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-18
@@ -234,12 +234,7 @@ func (h *Header) SetInt(key string, value int64, base int) {
|
|||||||
if base < 2 || base > 36 {
|
if base < 2 || base > 36 {
|
||||||
return // strconv.AppendInt only supports base 2..36.
|
return // strconv.AppendInt only supports base 2..36.
|
||||||
}
|
}
|
||||||
useKv := h.takeReusableSlot(key)
|
h.hbuf.kv.SetInt(key, value, base)
|
||||||
if useKv == nil {
|
|
||||||
h.appendHeaderInt(key, value, base)
|
|
||||||
} else {
|
|
||||||
useKv.value = h.reuseOrAppendInt(useKv.value, value, base)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets a key-value pair in the HTTP header.
|
// Set sets a key-value pair in the HTTP header.
|
||||||
@@ -249,9 +244,9 @@ func (h *Header) Set(key, value string) (enoughSpace bool) {
|
|||||||
|
|
||||||
// useKv := h.takeReusableSlot(key)
|
// useKv := h.takeReusableSlot(key)
|
||||||
// if useKv == nil {
|
// if useKv == nil {
|
||||||
// h.appendHeader(key, value)
|
// h.hbuf.kv.appendPair(key, value)
|
||||||
// } else {
|
// } else {
|
||||||
// useKv.value = h.reuseOrAppend(useKv.value, value)
|
// useKv.value = h.hbuf.kv.reuseOrAppend(useKv.value, value)
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,7 +350,7 @@ func (h *Header) ContentLength() (_ int64, present bool, _ error) {
|
|||||||
|
|
||||||
// Add adds a new key-value pair to the HTTP header. Calling Add mangles the buffer.
|
// Add adds a new key-value pair to the HTTP header. Calling Add mangles the buffer.
|
||||||
func (h *Header) Add(key, value string) {
|
func (h *Header) Add(key, value string) {
|
||||||
h.appendHeader(key, value)
|
h.hbuf.kv.appendPair(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method returns HTTP request method.
|
// Method returns HTTP request method.
|
||||||
@@ -365,12 +360,12 @@ func (h *Header) Method() []byte {
|
|||||||
|
|
||||||
// SetMethod sets the request header's method.
|
// SetMethod sets the request header's method.
|
||||||
func (h *Header) SetMethod(method string) {
|
func (h *Header) SetMethod(method string) {
|
||||||
h.method = h.reuseOrAppend(h.method, method)
|
h.method = h.hbuf.kv.reuseOrAppend(h.method, method)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRequestTarget sets request-target (URI) for the first HTTP request line.
|
// SetRequestTarget sets request-target (URI) for the first HTTP request line.
|
||||||
func (h *Header) SetRequestTarget(requestTarget string) {
|
func (h *Header) SetRequestTarget(requestTarget string) {
|
||||||
h.requestTarget = h.reuseOrAppend(h.requestTarget, requestTarget)
|
h.requestTarget = h.hbuf.kv.reuseOrAppend(h.requestTarget, requestTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestTarget returns a view of the request-target (URI) of the first HTTP request line.
|
// RequestTarget returns a view of the request-target (URI) of the first HTTP request line.
|
||||||
@@ -442,7 +437,7 @@ func (h *Header) Protocol() []byte {
|
|||||||
|
|
||||||
// SetProtocol sets the request header's protocol. Usually "HTTP/1.1".
|
// SetProtocol sets the request header's protocol. Usually "HTTP/1.1".
|
||||||
func (h *Header) SetProtocol(protocol string) {
|
func (h *Header) SetProtocol(protocol string) {
|
||||||
h.proto = h.reuseOrAppend(h.proto, protocol)
|
h.proto = h.hbuf.kv.reuseOrAppend(h.proto, protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status returns the response header's status code and status text. i.e: "200" "OK".
|
// Status returns the response header's status code and status text. i.e: "200" "OK".
|
||||||
@@ -456,15 +451,15 @@ func (h *Header) Status() (code, statusText []byte) {
|
|||||||
// 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.hbuf.kv.flags |= FlagStatusSet
|
h.hbuf.kv.flags |= FlagStatusSet
|
||||||
h.statusCode = h.reuseOrAppend(h.statusCode, code)
|
h.statusCode = h.hbuf.kv.reuseOrAppend(h.statusCode, code)
|
||||||
h.statusText = h.reuseOrAppend(h.statusText, statusText)
|
h.statusText = h.hbuf.kv.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.hbuf.kv.flags |= FlagStatusSet
|
h.hbuf.kv.flags |= FlagStatusSet
|
||||||
h.statusCode = h.reuseOrAppendInt(h.statusCode, code, 10)
|
h.statusCode = h.hbuf.kv.reuseOrAppendInt(h.statusCode, code, 10)
|
||||||
h.statusText = h.reuseOrAppend(h.statusText, statusText)
|
h.statusText = h.hbuf.kv.reuseOrAppend(h.statusText, statusText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Header) getNonEmptyValue(s headerSlice) []byte {
|
func (h *Header) getNonEmptyValue(s headerSlice) []byte {
|
||||||
@@ -478,7 +473,7 @@ func (h *Header) getNonEmptyValue(s headerSlice) []byte {
|
|||||||
func (h *Header) AppendRequest(dst []byte) ([]byte, error) {
|
func (h *Header) AppendRequest(dst []byte) ([]byte, error) {
|
||||||
proto := h.Protocol()
|
proto := h.Protocol()
|
||||||
if h.hbuf.kv.flags.HasAny(flagOOMReached) {
|
if h.hbuf.kv.flags.HasAny(flagOOMReached) {
|
||||||
return dst, errOOM
|
return dst, ErrBufferExhausted
|
||||||
} 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
|
||||||
} else if len(proto) == 0 {
|
} else if len(proto) == 0 {
|
||||||
@@ -518,7 +513,7 @@ func (h *Header) AppendResponse(dst []byte) ([]byte, error) {
|
|||||||
func (h *Header) AppendResponseNoHeaders(dst []byte) ([]byte, error) {
|
func (h *Header) AppendResponseNoHeaders(dst []byte) ([]byte, error) {
|
||||||
proto := h.Protocol()
|
proto := h.Protocol()
|
||||||
if h.hbuf.kv.flags.HasAny(flagOOMReached) {
|
if h.hbuf.kv.flags.HasAny(flagOOMReached) {
|
||||||
return dst, errOOM
|
return dst, ErrBufferExhausted
|
||||||
} 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
|
||||||
} else if len(proto) == 0 {
|
} else if len(proto) == 0 {
|
||||||
|
|||||||
@@ -473,7 +473,7 @@ func TestHeader_SplitBeforeColonStillParses(t *testing.T) {
|
|||||||
|
|
||||||
var h Header
|
var h Header
|
||||||
h.Reset(nil, numHeaderCapacity)
|
h.Reset(nil, numHeaderCapacity)
|
||||||
if _, err := h.ReadFromBytes([]byte(part1)); err != nil {
|
if err := h.ReadFromBytes([]byte(part1)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
needMore, err := h.TryParse(false)
|
needMore, err := h.TryParse(false)
|
||||||
@@ -484,7 +484,7 @@ func TestHeader_SplitBeforeColonStillParses(t *testing.T) {
|
|||||||
t.Fatal("want needMoreData=true after partial input")
|
t.Fatal("want needMoreData=true after partial input")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := h.ReadFromBytes([]byte(part2)); err != nil {
|
if err := h.ReadFromBytes([]byte(part2)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
needMore, err = h.TryParse(false)
|
needMore, err = h.TryParse(false)
|
||||||
|
|||||||
+91
-39
@@ -33,36 +33,34 @@ func (mb *KVBuffer) discardKVs() { mb.kvs = mb.kvs[:0] }
|
|||||||
func (mb *KVBuffer) BufferGrowthEnabled() bool { return !mb.flags.HasAny(flagNoBufferGrow) }
|
func (mb *KVBuffer) BufferGrowthEnabled() bool { return !mb.flags.HasAny(flagNoBufferGrow) }
|
||||||
|
|
||||||
func (mb *KVBuffer) ReadFromBytes(buf []byte) error {
|
func (mb *KVBuffer) ReadFromBytes(buf []byte) error {
|
||||||
if mb.flags.HasAny(flagMangledBuffer) {
|
if len(buf) == 0 {
|
||||||
|
return io.ErrNoProgress // Nothing handed over, not a buffer problem.
|
||||||
|
} else if mb.flags.HasAny(flagMangledBuffer) {
|
||||||
return errMangledBuffer
|
return errMangledBuffer
|
||||||
} else if len(buf)+cap(mb.buf) > maxBufLen {
|
} else if len(buf)+cap(mb.buf) > maxBufLen {
|
||||||
return errOOM
|
return ErrBufferExhausted
|
||||||
}
|
}
|
||||||
free := mb.free()
|
free := mb.free()
|
||||||
if len(buf) > free && !mb.BufferGrowthEnabled() {
|
if len(buf) > free && !mb.BufferGrowthEnabled() {
|
||||||
return errOOM
|
return ErrBufferExhausted
|
||||||
}
|
}
|
||||||
mb.buf = append(mb.buf, buf...)
|
mb.buf = append(mb.buf, buf...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mb *KVBuffer) ReadLimited(r io.Reader, limit int) (int, error) {
|
func (mb *KVBuffer) ReadLimited(r io.Reader, limit int) (int, error) {
|
||||||
if mb.flags.HasAny(flagMangledBuffer) {
|
free := mb.free()
|
||||||
|
growthEnabled := mb.BufferGrowthEnabled()
|
||||||
|
if !growthEnabled && (free == 0 || free < limit) || len(mb.buf) >= maxBufLen {
|
||||||
|
return 0, ErrBufferExhausted
|
||||||
|
} else if mb.flags.HasAny(flagMangledBuffer) {
|
||||||
return 0, errMangledBuffer
|
return 0, errMangledBuffer
|
||||||
} else if mb.flags.HasAny(flagReaderEOF) {
|
} else if mb.flags.HasAny(flagReaderEOF) {
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
} else if limit <= 0 {
|
} else if limit <= 0 {
|
||||||
return 0, io.ErrNoProgress
|
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)
|
|
||||||
}
|
}
|
||||||
|
mb.buf = slices.Grow(mb.buf, limit)
|
||||||
n, err := r.Read(mb.buf[len(mb.buf):min(len(mb.buf)+limit, maxBufLen)])
|
n, err := r.Read(mb.buf[len(mb.buf):min(len(mb.buf)+limit, maxBufLen)])
|
||||||
mb.buf = mb.buf[:len(mb.buf)+n]
|
mb.buf = mb.buf[:len(mb.buf)+n]
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -140,6 +138,37 @@ func (mb *KVBuffer) Add(key, value string) (enoughSpace bool) {
|
|||||||
// appended with [KVBuffer.Add] and the invalidated regions are stranded, since
|
// appended with [KVBuffer.Add] and the invalidated regions are stranded, since
|
||||||
// nothing here compacts the buffer.
|
// nothing here compacts the buffer.
|
||||||
func (mb *KVBuffer) Set(key, value string) (enoughSpace bool) {
|
func (mb *KVBuffer) Set(key, value string) (enoughSpace bool) {
|
||||||
|
reuse := mb.takeReusableSlot(key, len(key), len(value))
|
||||||
|
if reuse < 0 {
|
||||||
|
return mb.Add(key, value)
|
||||||
|
}
|
||||||
|
mb.overwriteAt(reuse, key, value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetInt is [KVBuffer.Set]'s integer counterpart. It formats value straight into
|
||||||
|
// the slot it reuses, so overwriting a pair never allocates.
|
||||||
|
func (mb *KVBuffer) SetInt(key string, value int64, base int) (enoughSpace bool) {
|
||||||
|
reuse := mb.takeReusableSlot(key, len(key), internal.IntLen(value, base))
|
||||||
|
if reuse < 0 {
|
||||||
|
return mb.appendPairInt(key, value, base)
|
||||||
|
}
|
||||||
|
mb.flags |= flagMangledBuffer
|
||||||
|
kv := &mb.kvs[reuse]
|
||||||
|
copy(mb.buf[kv.key.start:], key)
|
||||||
|
kv.key.len = tokint(len(key))
|
||||||
|
// The slot was picked to hold keyLen/valueLen, so AppendInt writes inside
|
||||||
|
// buf and never grows a new backing array.
|
||||||
|
v := strconv.AppendInt(mb.buf[kv.value.start:kv.value.start], value, base)
|
||||||
|
kv.value.len = tokint(len(v))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// takeReusableSlot invalidates every pair matching key except the smallest one
|
||||||
|
// whose key and value regions hold keyLen and valueLen bytes, whose index it
|
||||||
|
// returns. It returns -1 when no surviving slot fits, meaning the caller must
|
||||||
|
// append instead.
|
||||||
|
func (mb *KVBuffer) takeReusableSlot(key string, keyLen, valueLen int) int {
|
||||||
reuse := -1
|
reuse := -1
|
||||||
for i := range mb.kvs {
|
for i := range mb.kvs {
|
||||||
kv := &mb.kvs[i]
|
kv := &mb.kvs[i]
|
||||||
@@ -147,9 +176,9 @@ func (mb *KVBuffer) Set(key, value string) (enoughSpace bool) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// A valueless pair holds no value region, so reusing one would write the
|
// 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
|
// value over byte 0. Let it fall through to the caller's append, which
|
||||||
// real region and keeps "ok" distinct from "ok=".
|
// 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)
|
fits := kv.HasValue() && int(kv.key.len) >= keyLen && int(kv.value.len) >= valueLen
|
||||||
if fits && (reuse < 0 || kv.size() < mb.kvs[reuse].size()) {
|
if fits && (reuse < 0 || kv.size() < mb.kvs[reuse].size()) {
|
||||||
if reuse >= 0 {
|
if reuse >= 0 {
|
||||||
mb.kvs[reuse].invalidate() // Superseded by a tighter fit.
|
mb.kvs[reuse].invalidate() // Superseded by a tighter fit.
|
||||||
@@ -159,11 +188,7 @@ func (mb *KVBuffer) Set(key, value string) (enoughSpace bool) {
|
|||||||
}
|
}
|
||||||
kv.invalidate()
|
kv.invalidate()
|
||||||
}
|
}
|
||||||
if reuse < 0 {
|
return reuse
|
||||||
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
|
// overwriteAt writes key and value over the regions pair i already owns. The
|
||||||
@@ -223,24 +248,6 @@ func (mb *KVBuffer) getIdx(key string) int {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mb *KVBuffer) getInvalidIdx() int {
|
|
||||||
for i, kv := range mb.kvs {
|
|
||||||
if !kv.isValid() {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
||||||
@@ -313,6 +320,51 @@ func (hb *KVBuffer) mustAppendInt(value int64, base int) headerSlice {
|
|||||||
return hb.slice(hb.buf[L : L+len(v)])
|
return hb.slice(hb.buf[L : L+len(v)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reuseOrAppend writes value over tok's slot when it fits there, avoiding any
|
||||||
|
// buffer growth; otherwise it appends a fresh slot.
|
||||||
|
func (mb *KVBuffer) reuseOrAppend(tok headerSlice, value string) headerSlice {
|
||||||
|
if tok.len > tokint(len(value)) {
|
||||||
|
copy(mb.musttoken(tok), value)
|
||||||
|
tok.len = tokint(len(value))
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
return mb.appendSlice(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendSlice reserves space (growing or flagging OOM) and appends value as a
|
||||||
|
// new slot.
|
||||||
|
func (mb *KVBuffer) appendSlice(value string) headerSlice {
|
||||||
|
debuglog("http:appendslice:start")
|
||||||
|
if !mb.reserve(len(value)) {
|
||||||
|
return headerSlice{} // Drop and flag OOM; never panic.
|
||||||
|
}
|
||||||
|
mb.flags |= flagMangledBuffer
|
||||||
|
return mb.mustAppendSlice(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reuseOrAppendInt is [KVBuffer.reuseOrAppend]'s integer counterpart.
|
||||||
|
func (mb *KVBuffer) reuseOrAppendInt(tok headerSlice, value int64, base int) headerSlice {
|
||||||
|
n := internal.IntLen(value, base)
|
||||||
|
if int(tok.len) >= n {
|
||||||
|
// Reuse: format directly over the existing slot. No free space needed
|
||||||
|
// since n <= tok.len and the slot already lives inside buf.
|
||||||
|
v := strconv.AppendInt(mb.buf[tok.start:tok.start], value, base)
|
||||||
|
tok.len = tokint(len(v))
|
||||||
|
mb.flags |= flagMangledBuffer
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
return mb.appendInt(value, base, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendInt reserves space (growing or flagging OOM) and appends value as a new slot.
|
||||||
|
func (mb *KVBuffer) appendInt(value int64, base, n int) headerSlice {
|
||||||
|
if !mb.reserve(n) {
|
||||||
|
return headerSlice{} // Drop and flag OOM; never panic.
|
||||||
|
}
|
||||||
|
mb.flags |= flagMangledBuffer
|
||||||
|
return mb.mustAppendInt(value, base)
|
||||||
|
}
|
||||||
|
|
||||||
func (mb *KVBuffer) slice(value []byte) headerSlice {
|
func (mb *KVBuffer) slice(value []byte) headerSlice {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
return headerSlice{}
|
return headerSlice{}
|
||||||
|
|||||||
+12
-126
@@ -3,8 +3,6 @@ package httpraw
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/soypat/lneto/internal"
|
"github.com/soypat/lneto/internal"
|
||||||
@@ -18,8 +16,11 @@ var (
|
|||||||
errNoBoundary = errors.New("httpraw: multipart boundary not set")
|
errNoBoundary = errors.New("httpraw: multipart boundary not set")
|
||||||
errUnparsed = errors.New("need to finish parsing")
|
errUnparsed = errors.New("need to finish parsing")
|
||||||
errInvalidName = errors.New("invalid header name")
|
errInvalidName = errors.New("invalid header name")
|
||||||
ErrSmallHeaderBuffer = errors.New("httpraw: Header buffer exhausted, increase size")
|
// ErrBufferExhausted signals a buffer with no room left for the data being
|
||||||
errOOM = errors.New("httpraw: Header incomplete due to OOM")
|
// written and no permission to grow, see [KVBuffer.EnableBufferGrowth].
|
||||||
|
// Enlarging the buffer handed to Reset is the only fix; a server answers it
|
||||||
|
// on a request header with 431, RFC 6585 5.
|
||||||
|
ErrBufferExhausted = errors.New("httpraw: buffer exhausted, increase size")
|
||||||
// ErrHeaderTooMany signals a header block carrying more fields than
|
// ErrHeaderTooMany signals a header block carrying more fields than
|
||||||
// the buffer it is parsed into has room for, see [Header.Reset]. A server
|
// the buffer it is parsed into has room for, see [Header.Reset]. A server
|
||||||
// answers it with 431, RFC 6585 5: no larger buffer is coming, so reading
|
// answers it with 431, RFC 6585 5: no larger buffer is coming, so reading
|
||||||
@@ -245,121 +246,6 @@ func (hb *headerBuf) parseFirstLineResponse(initFlags Flags) (statusCode, status
|
|||||||
|
|
||||||
func (kv argsKV) HasValue() bool { return kv.value.start > 0 }
|
func (kv argsKV) HasValue() bool { return kv.value.start > 0 }
|
||||||
|
|
||||||
func (h *Header) reuseOrAppend(tok headerSlice, value string) headerSlice {
|
|
||||||
if tok.len > tokint(len(value)) {
|
|
||||||
copy(h.hbuf.kv.musttoken(tok), value)
|
|
||||||
tok.len = tokint(len(value))
|
|
||||||
return tok
|
|
||||||
}
|
|
||||||
return h.appendSlice(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Header) appendSlice(value string) headerSlice {
|
|
||||||
debuglog("http:appendslice:start")
|
|
||||||
if !h.reserve(len(value)) {
|
|
||||||
return headerSlice{}
|
|
||||||
}
|
|
||||||
h.flags |= flagMangledBuffer
|
|
||||||
return h.hbuf.kv.mustAppendSlice(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Header) appendHeader(key, value string) {
|
|
||||||
// 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 !h.reserve(len(key) + len(value)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.flags |= flagMangledBuffer
|
|
||||||
hb := &h.hbuf
|
|
||||||
k := hb.mustAppendSlice(key)
|
|
||||||
v := hb.mustAppendSlice(value)
|
|
||||||
debuglog("http:appendhdr:grow-hdrs")
|
|
||||||
hb.headers = append(hb.headers, argsKV{
|
|
||||||
key: k,
|
|
||||||
value: v,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendHeaderInt is appendHeader's integer counterpart: it appends key and the
|
|
||||||
// formatted integer value as a new header field.
|
|
||||||
func (h *Header) appendHeaderInt(key string, value int64, base int) {
|
|
||||||
n := internal.IntLen(value, base)
|
|
||||||
if !h.reserve(len(key) + n) {
|
|
||||||
return // Drop and flag OOM; never panic.
|
|
||||||
}
|
|
||||||
h.flags |= flagMangledBuffer
|
|
||||||
hb := &h.hbuf
|
|
||||||
k := hb.mustAppendSlice(key)
|
|
||||||
v := hb.mustAppendInt(value, base)
|
|
||||||
hb.headers = append(hb.headers, argsKV{
|
|
||||||
key: k,
|
|
||||||
value: v,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// mustAppendSlice). It returns false and sets flagOOMReached when the space
|
|
||||||
// cannot be guaranteed: a tokint offset overflow, or a full buffer with
|
|
||||||
// flagNoBufferGrow set.
|
|
||||||
func (h *Header) reserve(need int) bool {
|
|
||||||
hb := &h.hbuf
|
|
||||||
if len(hb.buf) == 0 {
|
|
||||||
need++ // mustAppend* reserves byte 0 on an empty buffer.
|
|
||||||
}
|
|
||||||
if len(hb.buf)+need > maxBufLen {
|
|
||||||
h.flags |= flagOOMReached // Offsets would overflow uint16 tokint.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if need > hb.free() {
|
|
||||||
if h.flags.HasAny(flagNoBufferGrow) {
|
|
||||||
h.flags |= flagOOMReached
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
hb.buf = slices.Grow(hb.buf, need)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// reuseOrAppendInt writes value into tok's slot in place when it fits, avoiding
|
|
||||||
// any buffer growth; otherwise it appends a fresh slot.
|
|
||||||
func (h *Header) reuseOrAppendInt(tok headerSlice, value int64, base int) headerSlice {
|
|
||||||
n := internal.IntLen(value, base)
|
|
||||||
if int(tok.len) >= n {
|
|
||||||
// Reuse: format directly over the existing slot. No free space needed
|
|
||||||
// since n <= tok.len and the slot already lives inside buf.
|
|
||||||
v := strconv.AppendInt(h.hbuf.buf[tok.start:tok.start], value, base)
|
|
||||||
tok.len = tokint(len(v))
|
|
||||||
h.flags |= flagMangledBuffer
|
|
||||||
return tok
|
|
||||||
}
|
|
||||||
return h.appendInt(value, base, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendInt reserves space (growing or flagging OOM) and appends value as a new slot.
|
|
||||||
func (h *Header) appendInt(value int64, base, n int) headerSlice {
|
|
||||||
if !h.reserve(n) {
|
|
||||||
return headerSlice{} // Drop and flag OOM; never panic.
|
|
||||||
}
|
|
||||||
h.flags |= flagMangledBuffer
|
|
||||||
return h.hbuf.mustAppendInt(value, base)
|
|
||||||
}
|
|
||||||
|
|
||||||
// mustAppendInt formats value into the buffer's free region and commits it.
|
|
||||||
// The caller must have reserved at least internal.IntLen(value, base) free bytes.
|
|
||||||
func (hb *headerBuf) 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 (hb *headerBuf) noKV() argsKV { return argsKV{} }
|
|
||||||
|
|
||||||
func (hb *headerBuf) next(ss *scannerState) argsKV {
|
func (hb *headerBuf) next(ss *scannerState) argsKV {
|
||||||
if !ss.initialized {
|
if !ss.initialized {
|
||||||
ss.nextColon = -1
|
ss.nextColon = -1
|
||||||
@@ -369,10 +255,10 @@ func (hb *headerBuf) next(ss *scannerState) argsKV {
|
|||||||
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
|
||||||
return hb.noKV() // \r\n\r\n Ends header.
|
return hb.kv.noKV() // \r\n\r\n Ends header.
|
||||||
} else if blen >= 1 && buf[0] == '\n' {
|
} else if blen >= 1 && buf[0] == '\n' {
|
||||||
hb.off += 1
|
hb.off += 1
|
||||||
return hb.noKV() // \n\n Ends header.
|
return hb.kv.noKV() // \n\n Ends header.
|
||||||
}
|
}
|
||||||
|
|
||||||
// n is parsing offset. Will start by storing colon index.
|
// n is parsing offset. Will start by storing colon index.
|
||||||
@@ -388,18 +274,18 @@ func (hb *headerBuf) next(ss *scannerState) argsKV {
|
|||||||
// A header name should always at some point be followed by a \n
|
// A header name should always at some point be followed by a \n
|
||||||
// even if it's the one that terminates the header block.
|
// even if it's the one that terminates the header block.
|
||||||
ss.err = ErrNeedMoreData
|
ss.err = ErrNeedMoreData
|
||||||
return hb.noKV()
|
return hb.kv.noKV()
|
||||||
} else if x < n {
|
} else if x < n {
|
||||||
// There was a \n before the colon! This is invalid.
|
// There was a \n before the colon! This is invalid.
|
||||||
ss.err = errInvalidName
|
ss.err = errInvalidName
|
||||||
return hb.noKV()
|
return hb.kv.noKV()
|
||||||
} else if n < 0 {
|
} else if n < 0 {
|
||||||
// A newline is present (x>=0 reached here) but the line has no
|
// A newline is present (x>=0 reached here) but the line has no
|
||||||
// colon: malformed, not incomplete. A split arriving before the
|
// colon: malformed, not incomplete. A split arriving before the
|
||||||
// colon has no newline yet and is caught by the x<0 branch above,
|
// colon has no newline yet and is caught by the x<0 branch above,
|
||||||
// so it still returns ErrNeedMoreData.
|
// so it still returns ErrNeedMoreData.
|
||||||
ss.err = errInvalidName
|
ss.err = errInvalidName
|
||||||
return hb.noKV()
|
return hb.kv.noKV()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// n stores colon position by now.
|
// n stores colon position by now.
|
||||||
@@ -407,7 +293,7 @@ func (hb *headerBuf) next(ss *scannerState) argsKV {
|
|||||||
// Spaces between the header key and colon are not allowed.
|
// Spaces between the header key and colon are not allowed.
|
||||||
// See RFC 7230, Section 3.2.4.
|
// See RFC 7230, Section 3.2.4.
|
||||||
ss.err = errInvalidName
|
ss.err = errInvalidName
|
||||||
return hb.noKV()
|
return hb.kv.noKV()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ready to store key..
|
// Ready to store key..
|
||||||
@@ -426,7 +312,7 @@ func (hb *headerBuf) next(ss *scannerState) argsKV {
|
|||||||
if nl < 0 || nl+n+1 == len(buf) {
|
if nl < 0 || nl+n+1 == len(buf) {
|
||||||
// No newline or newline is last character and can't know if is multiline.
|
// No newline or newline is last character and can't know if is multiline.
|
||||||
ss.err = ErrNeedMoreData
|
ss.err = ErrNeedMoreData
|
||||||
return hb.noKV()
|
return hb.kv.noKV()
|
||||||
}
|
}
|
||||||
n += nl + 1 // Index of the newly found newline.
|
n += nl + 1 // Index of the newly found newline.
|
||||||
nextChar := buf[n]
|
nextChar := buf[n]
|
||||||
|
|||||||
@@ -17,13 +17,10 @@ func TestTryParse_IncrementalRequest(t *testing.T) {
|
|||||||
var done bool
|
var done bool
|
||||||
var doneIdx int
|
var doneIdx int
|
||||||
for i, chunk := range chunks {
|
for i, chunk := range chunks {
|
||||||
n, err := hdr.ReadFromBytes([]byte(chunk))
|
err := hdr.ReadFromBytes([]byte(chunk))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ReadFromBytes: %v", err)
|
t.Fatalf("ReadFromBytes: %v", err)
|
||||||
}
|
}
|
||||||
if n != len(chunk) {
|
|
||||||
t.Fatalf("expected %d bytes read, got %d", len(chunk), n)
|
|
||||||
}
|
|
||||||
|
|
||||||
var needMore bool
|
var needMore bool
|
||||||
needMore, err = hdr.TryParse(false)
|
needMore, err = hdr.TryParse(false)
|
||||||
@@ -58,13 +55,10 @@ func TestTryParse_IncrementalRequest(t *testing.T) {
|
|||||||
|
|
||||||
// Verify headers via ForEach.
|
// Verify headers via ForEach.
|
||||||
headers := make(map[string]string)
|
headers := make(map[string]string)
|
||||||
err := hdr.ForEach(func(key, value []byte) error {
|
hdr.ForEach(func(key, value []byte) bool {
|
||||||
headers[string(key)] = string(value)
|
headers[string(key)] = string(value)
|
||||||
return nil
|
return true
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if headers["Host"] != "example.com" {
|
if headers["Host"] != "example.com" {
|
||||||
t.Errorf("Host = %q; want example.com", headers["Host"])
|
t.Errorf("Host = %q; want example.com", headers["Host"])
|
||||||
}
|
}
|
||||||
@@ -176,7 +170,7 @@ func TestReadFromBytes_Empty(t *testing.T) {
|
|||||||
var hdr Header
|
var hdr Header
|
||||||
hdr.Reset(make([]byte, 0, 256), numHeaderCapacity)
|
hdr.Reset(make([]byte, 0, 256), numHeaderCapacity)
|
||||||
|
|
||||||
_, err := hdr.ReadFromBytes(nil)
|
err := hdr.ReadFromBytes(nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error for empty bytes")
|
t.Fatal("expected error for empty bytes")
|
||||||
}
|
}
|
||||||
@@ -209,7 +203,7 @@ func TestEnableBufferGrowth(t *testing.T) {
|
|||||||
for i := range big {
|
for i := range big {
|
||||||
big[i] = 'A'
|
big[i] = 'A'
|
||||||
}
|
}
|
||||||
_, err := hdr.ReadFromBytes(big)
|
err := hdr.ReadFromBytes(big)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error when buffer growth disabled and data exceeds capacity")
|
t.Fatal("expected error when buffer growth disabled and data exceeds capacity")
|
||||||
}
|
}
|
||||||
@@ -228,11 +222,11 @@ func TestHeader_Add(t *testing.T) {
|
|||||||
|
|
||||||
// ForEach should find both.
|
// ForEach should find both.
|
||||||
var values []string
|
var values []string
|
||||||
hdr.ForEach(func(key, value []byte) error {
|
hdr.ForEach(func(key, value []byte) bool {
|
||||||
if string(key) == "X-Custom" {
|
if string(key) == "X-Custom" {
|
||||||
values = append(values, string(value))
|
values = append(values, string(value))
|
||||||
}
|
}
|
||||||
return nil
|
return true
|
||||||
})
|
})
|
||||||
if len(values) != 2 {
|
if len(values) != 2 {
|
||||||
t.Fatalf("expected 2 X-Custom headers, got %d", len(values))
|
t.Fatalf("expected 2 X-Custom headers, got %d", len(values))
|
||||||
|
|||||||
Reference in New Issue
Block a user