mirror of
https://github.com/soypat/lneto.git
synced 2026-09-03 21:39:04 +00:00
fix Mux bug not matching paths correctly; httpraw HTTP V1 naming applied
This commit is contained in:
@@ -274,7 +274,7 @@ func handleConnNet(conn net.Conn) error {
|
|||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
conn.SetDeadline(time.Now().Add(10 * time.Second))
|
conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
|
||||||
var hdr httpraw.Header
|
var hdr httpraw.HeaderV1
|
||||||
needMore := true
|
needMore := true
|
||||||
for needMore {
|
for needMore {
|
||||||
_, err := hdr.ReadFromLimited(conn, 1024)
|
_, err := hdr.ReadFromLimited(conn, 1024)
|
||||||
@@ -291,7 +291,7 @@ func handleConnNet(conn net.Conn) error {
|
|||||||
uri := string(hdr.RequestTarget())
|
uri := string(hdr.RequestTarget())
|
||||||
fmt.Printf("< %s %s\n", method, uri)
|
fmt.Printf("< %s %s\n", method, uri)
|
||||||
|
|
||||||
var resp httpraw.Header
|
var resp httpraw.HeaderV1
|
||||||
resp.SetProtocol("HTTP/1.1")
|
resp.SetProtocol("HTTP/1.1")
|
||||||
resp.SetStatus("200", "OK")
|
resp.SetStatus("200", "OK")
|
||||||
resp.Set("Content-Type", "text/html")
|
resp.Set("Content-Type", "text/html")
|
||||||
@@ -368,7 +368,7 @@ func mockClient(stack *xnet.StackAsync, port uint16, subnet netip.Prefix) {
|
|||||||
panic("mock client deadline exceeded to establish")
|
panic("mock client deadline exceeded to establish")
|
||||||
}
|
}
|
||||||
|
|
||||||
var hdr httpraw.Header
|
var hdr httpraw.HeaderV1
|
||||||
hdr.SetMethod("GET")
|
hdr.SetMethod("GET")
|
||||||
hdr.SetRequestTarget("/")
|
hdr.SetRequestTarget("/")
|
||||||
hdr.SetProtocol("HTTP/1.1")
|
hdr.SetProtocol("HTTP/1.1")
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func run() error {
|
|||||||
flag.IntVar(&port, "lport", 13337, "Local port over which to hit server")
|
flag.IntVar(&port, "lport", 13337, "Local port over which to hit server")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
// Prepare GET request.
|
// Prepare GET request.
|
||||||
var hdr httpraw.Header
|
var hdr httpraw.HeaderV1
|
||||||
hdr.SetMethod("GET")
|
hdr.SetMethod("GET")
|
||||||
hdr.SetRequestTarget("/")
|
hdr.SetRequestTarget("/")
|
||||||
hdr.SetProtocol("HTTP/1.1")
|
hdr.SetProtocol("HTTP/1.1")
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ func handleConnection(conn *tcp.Conn) error {
|
|||||||
conn.SetDeadline(time.Now().Add(10 * time.Second))
|
conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
|
||||||
// Read HTTP request.
|
// Read HTTP request.
|
||||||
var hdr httpraw.Header
|
var hdr httpraw.HeaderV1
|
||||||
var needMore bool = true
|
var needMore bool = true
|
||||||
for needMore {
|
for needMore {
|
||||||
_, err := hdr.ReadFromLimited(conn, 1024)
|
_, err := hdr.ReadFromLimited(conn, 1024)
|
||||||
@@ -288,7 +288,7 @@ func handleConnection(conn *tcp.Conn) error {
|
|||||||
// Build response body.
|
// Build response body.
|
||||||
|
|
||||||
// Build HTTP response.
|
// Build HTTP response.
|
||||||
var resp httpraw.Header
|
var resp httpraw.HeaderV1
|
||||||
resp.SetProtocol("HTTP/1.1")
|
resp.SetProtocol("HTTP/1.1")
|
||||||
resp.SetStatus("200", "OK")
|
resp.SetStatus("200", "OK")
|
||||||
resp.Set("Content-Type", "text/html")
|
resp.Set("Content-Type", "text/html")
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ func run() (err error) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
timeHTTPCreate := timer("create HTTP GET request")
|
timeHTTPCreate := timer("create HTTP GET request")
|
||||||
var hdr httpraw.Header
|
var hdr httpraw.HeaderV1
|
||||||
hdr.SetMethod("GET")
|
hdr.SetMethod("GET")
|
||||||
hdr.SetRequestTarget("/")
|
hdr.SetRequestTarget("/")
|
||||||
hdr.SetProtocol("HTTP/1.1")
|
hdr.SetProtocol("HTTP/1.1")
|
||||||
|
|||||||
+20
-22
@@ -34,7 +34,7 @@ type Exchange struct {
|
|||||||
rawbuf []byte
|
rawbuf []byte
|
||||||
respHeaderOff uint16
|
respHeaderOff uint16
|
||||||
respHeaderLen uint16
|
respHeaderLen uint16
|
||||||
reqHdr httpraw.Header
|
reqHdr httpraw.HeaderV1
|
||||||
pathValues []PathValue
|
pathValues []PathValue
|
||||||
// bodyRW is the reader handed to [httpraw.Form.ReadLimited], kept here so
|
// bodyRW is the reader handed to [httpraw.Form.ReadLimited], kept here so
|
||||||
// boxing it into an io.Reader allocates nothing per request.
|
// boxing it into an io.Reader allocates nothing per request.
|
||||||
@@ -171,13 +171,18 @@ func (exch *Exchange) Release() {
|
|||||||
// written to and used without modifying the staged response first line.
|
// written to and used without modifying the staged response first line.
|
||||||
//
|
//
|
||||||
// Staging headers will write to this buffer so use mindfully.
|
// Staging headers will write to this buffer so use mindfully.
|
||||||
// To access only the request header buffer portion use [httpraw.Header.BufferRaw] limited
|
// To access only the request header buffer portion use [httpraw.HeaderV1.BufferRaw] limited
|
||||||
// to [httpraw.Header.BufferParsed] as returned by [Exchange.RequestHeaderRaw].
|
// to [httpraw.HeaderV1.BufferParsed] as returned by [Exchange.requestHeaderRaw].
|
||||||
// Writing to this section will not change the contents read by [Exchange.ReadBody].
|
// Writing to this section will not change the contents read by [Exchange.ReadBody].
|
||||||
//
|
//
|
||||||
// In [Router] context, the size of this buffer is influenced directly by [RouterConfig] HeaderBufferSize fields.
|
// In [Router] context, the size of this buffer is influenced directly by [RouterConfig] HeaderBufferSize fields.
|
||||||
func (exch *Exchange) UnsafeRawBuffer() []byte { return exch.rawbuf }
|
func (exch *Exchange) UnsafeRawBuffer() []byte { return exch.rawbuf }
|
||||||
|
|
||||||
|
// RequestHeaderV1Raw returns the parsed request header for access beyond the
|
||||||
|
// Request* methods, such as [httpraw.HeaderV1.ForEach]. Valid until the exchange
|
||||||
|
// is released, and writing to it corrupts the response.
|
||||||
|
func (exch *Exchange) RequestHeaderV1Raw() *httpraw.HeaderV1 { return &exch.reqHdr }
|
||||||
|
|
||||||
// StageHeader stages a response header field, written on the first
|
// StageHeader stages a response header field, written on the first
|
||||||
// [Exchange.FlushHeader], [Exchange.WriteHeader] or [Exchange.WriteBody].
|
// [Exchange.FlushHeader], [Exchange.WriteHeader] or [Exchange.WriteBody].
|
||||||
// Returns false and drops the field if the response buffer cannot fit it.
|
// Returns false and drops the field if the response buffer cannot fit it.
|
||||||
@@ -478,13 +483,6 @@ func (exch *Exchange) MuxPattern() string {
|
|||||||
return exch.matchedPattern
|
return exch.matchedPattern
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestHeaderRaw returns the parsed request header for access beyond the
|
|
||||||
// Request* methods, such as [httpraw.Header.ForEach]. Valid until the exchange
|
|
||||||
// is released, and writing to it corrupts the response.
|
|
||||||
func (exch *Exchange) RequestHeaderRaw() *httpraw.Header {
|
|
||||||
return &exch.reqHdr
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestParseCookie parses the request's key header field into dst, i.e:
|
// RequestParseCookie parses the request's key header field into dst, i.e:
|
||||||
// "Cookie". The caller owns dst and its buffer, so it may be reused between
|
// "Cookie". The caller owns dst and its buffer, so it may be reused between
|
||||||
// requests.
|
// requests.
|
||||||
@@ -499,14 +497,14 @@ func (exch *Exchange) RequestParseCookie(dst *httpraw.Cookie, key string) error
|
|||||||
func (exch *Exchange) RequestContentType() []byte {
|
func (exch *Exchange) RequestContentType() []byte {
|
||||||
// Folded: field names are case insensitive and HTTP/2 mandates lowercase, so
|
// Folded: field names are case insensitive and HTTP/2 mandates lowercase, so
|
||||||
// a proxy translating h2 to h1 sends "content-type", RFC 9110 5.1.
|
// a proxy translating h2 to h1 sends "content-type", RFC 9110 5.1.
|
||||||
return exch.RequestHeaderRaw().GetFold("Content-Type")
|
return exch.RequestHeaderV1Raw().GetFold("Content-Type")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestContentLength returns the body length declared by the request's
|
// RequestContentLength returns the body length declared by the request's
|
||||||
// Content-Length field. An absent field is signalled with present=false and no error.
|
// Content-Length field. An absent field is signalled with present=false and no error.
|
||||||
// See [httpraw.Header.ContentLength].
|
// See [httpraw.HeaderV1.ContentLength].
|
||||||
func (exch *Exchange) RequestContentLength() (_ int64, present bool, _ error) {
|
func (exch *Exchange) RequestContentLength() (_ int64, present bool, _ error) {
|
||||||
return exch.RequestHeaderRaw().ContentLength()
|
return exch.RequestHeaderV1Raw().ContentLength()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestParseForm parses "application/x-www-form-urlencoded" pairs into dst
|
// RequestParseForm parses "application/x-www-form-urlencoded" pairs into dst
|
||||||
@@ -569,7 +567,7 @@ func (exch *Exchange) readBodyForm(dst *httpraw.Form) error {
|
|||||||
return nil // No declared encoding is no form, RFC 9110 8.3.
|
return nil // No declared encoding is no form, RFC 9110 8.3.
|
||||||
} else if !httpraw.MediaTypeIs(contentType, "application/x-www-form-urlencoded") {
|
} else if !httpraw.MediaTypeIs(contentType, "application/x-www-form-urlencoded") {
|
||||||
return errNotFormEncoded
|
return errNotFormEncoded
|
||||||
} else if exch.RequestHeaderRaw().GetFold("Transfer-Encoding") != nil {
|
} else if exch.RequestHeaderV1Raw().GetFold("Transfer-Encoding") != nil {
|
||||||
// Chunked bodies are framed, so reading Content-Length bytes off the
|
// Chunked bodies are framed, so reading Content-Length bytes off the
|
||||||
// wire would parse chunk sizes as form data. httpraw does not decode them.
|
// wire would parse chunk sizes as form data. httpraw does not decode them.
|
||||||
return errUnsupportedTransferCoding
|
return errUnsupportedTransferCoding
|
||||||
@@ -723,26 +721,26 @@ func (exch *Exchange) ReadMultiparts(dst []MultipartSink, buf []byte, newSink fu
|
|||||||
// RequestHeader returns the value of the first request header field matching
|
// RequestHeader returns the value of the first request header field matching
|
||||||
// key, or nil if absent. Key matching is case sensitive.
|
// key, or nil if absent. Key matching is case sensitive.
|
||||||
func (exch *Exchange) RequestHeader(key string) []byte {
|
func (exch *Exchange) RequestHeader(key string) []byte {
|
||||||
header := exch.RequestHeaderRaw()
|
header := exch.RequestHeaderV1Raw()
|
||||||
return header.Get(key)
|
return header.Get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestTarget returns the request-target (URI) of the request line, i.e:
|
// RequestTarget returns the request-target (URI) of the request line, i.e:
|
||||||
// "/search?q=go". See [httpraw.Header.RequestTarget].
|
// "/search?q=go". See [httpraw.HeaderV1.RequestTarget].
|
||||||
func (exch *Exchange) RequestTarget() []byte {
|
func (exch *Exchange) RequestTarget() []byte {
|
||||||
return exch.RequestHeaderRaw().RequestTarget()
|
return exch.RequestHeaderV1Raw().RequestTarget()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestPath returns the request-target (URI) up to the query string. This is
|
// RequestPath returns the request-target (URI) up to the query string. This is
|
||||||
// what the [Mux] matches on, i.e: "/search" for a request to "/search?q=go".
|
// what the [Mux] matches on, i.e: "/search" for a request to "/search?q=go".
|
||||||
func (exch *Exchange) RequestPath() []byte {
|
func (exch *Exchange) RequestPath() []byte {
|
||||||
return exch.RequestHeaderRaw().RequestPath()
|
return exch.RequestHeaderV1Raw().RequestPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestQuery returns the request's query string as it appears on the wire.
|
// RequestQuery returns the request's query string as it appears on the wire.
|
||||||
// Iterate it with [httpraw.NextQueryPair]. See [httpraw.Header.RequestQuery].
|
// Iterate it with [httpraw.NextQueryPair]. See [httpraw.HeaderV1.RequestQuery].
|
||||||
func (exch *Exchange) RequestQuery() []byte {
|
func (exch *Exchange) RequestQuery() []byte {
|
||||||
return exch.RequestHeaderRaw().RequestQuery()
|
return exch.RequestHeaderV1Raw().RequestQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestQueryValue returns an undecoded view of the first query parameter
|
// RequestQueryValue returns an undecoded view of the first query parameter
|
||||||
@@ -835,11 +833,11 @@ func (exch *Exchange) RequestMethod() Method {
|
|||||||
|
|
||||||
// RequestMethod returns the request line's method as a []byte view, i.e: "GET".
|
// RequestMethod returns the request line's method as a []byte view, i.e: "GET".
|
||||||
func (exch *Exchange) RequestMethodRaw() []byte {
|
func (exch *Exchange) RequestMethodRaw() []byte {
|
||||||
return exch.RequestHeaderRaw().Method()
|
return exch.RequestHeaderV1Raw().Method()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestConnectionClose returns true if the client asked for the connection to
|
// RequestConnectionClose returns true if the client asked for the connection to
|
||||||
// be closed after this exchange with a "Connection: close" header field.
|
// be closed after this exchange with a "Connection: close" header field.
|
||||||
func (exch *Exchange) RequestConnectionClose() bool {
|
func (exch *Exchange) RequestConnectionClose() bool {
|
||||||
return exch.RequestHeaderRaw().ConnectionClose()
|
return exch.RequestHeaderV1Raw().ConnectionClose()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1282,7 +1282,7 @@ 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) bool {
|
exch.RequestHeaderV1Raw().ForEach(func(key, value []byte) bool {
|
||||||
fields++
|
fields++
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const (
|
|||||||
|
|
||||||
// segSizes are the chunk sizes a request may be delivered in, index 0 meaning
|
// segSizes are the chunk sizes a request may be delivered in, index 0 meaning
|
||||||
// "all at once". Splitting a request mid-CRLF or before a colon is what drives
|
// "all at once". Splitting a request mid-CRLF or before a colon is what drives
|
||||||
// [httpraw.Header.TryParse]'s resumption path. Entries may be appended, never
|
// [httpraw.HeaderV1.TryParse]'s resumption path. Entries may be appended, never
|
||||||
// changed: an existing index must keep splitting exactly as it does today.
|
// changed: an existing index must keep splitting exactly as it does today.
|
||||||
var segSizes = [16]int{0, 1, 2, 3, 5, 7, 11, 16, 23, 37, 64, 101, 173, 256, 509, 1024}
|
var segSizes = [16]int{0, 1, 2, 3, 5, 7, 11, 16, 23, 37, 64, 101, 173, 256, 509, 1024}
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ func checkResponse(t *testing.T, written string) {
|
|||||||
if !strings.Contains(written, "\r\n\r\n") {
|
if !strings.Contains(written, "\r\n\r\n") {
|
||||||
t.Fatalf("header block never terminated: %q", written)
|
t.Fatalf("header block never terminated: %q", written)
|
||||||
}
|
}
|
||||||
var resp httpraw.Header
|
var resp httpraw.HeaderV1
|
||||||
const asResponse = true
|
const asResponse = true
|
||||||
if err := resp.ParseBytes(asResponse, []byte(written)); err != nil {
|
if err := resp.ParseBytes(asResponse, []byte(written)); err != nil {
|
||||||
t.Fatalf("response does not parse back: %s in %q", err, written)
|
t.Fatalf("response does not parse back: %s in %q", err, written)
|
||||||
|
|||||||
+82
-9
@@ -81,6 +81,18 @@ func Handle(exch *Exchange, mux Mux, backoff lneto.BackoffStrategy) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (exch *Exchange) handleError(err error) {
|
func (exch *Exchange) handleError(err error) {
|
||||||
|
if err == lneto.ErrUnsupported {
|
||||||
|
// httpraw refused a first line naming a version it does not speak, before
|
||||||
|
// spending the field loop on it. An empty protocol is a HTTP/0.9
|
||||||
|
// simple-request, RFC 9112 3: a malformed 1.x request-line rather than a
|
||||||
|
// version there is any point naming back.
|
||||||
|
if len(exch.reqHdr.Protocol()) == 0 {
|
||||||
|
exch.WriteHeader(int(StatusBadRequest))
|
||||||
|
} else {
|
||||||
|
exch.WriteHeader(int(StatusHTTPVersionNotSupported))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
if err == httpraw.ErrHeaderTooMany || err == httpraw.ErrBufferExhausted || 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.
|
||||||
@@ -221,6 +233,7 @@ type MuxSlice struct {
|
|||||||
path string
|
path string
|
||||||
handler HandlerFunc
|
handler HandlerFunc
|
||||||
pathVals int
|
pathVals int
|
||||||
|
spec int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,30 +243,50 @@ func (sm *MuxSlice) Reset(capacity int) {
|
|||||||
internal.SliceReuse(&sm._handlers, capacity)
|
internal.SliceReuse(&sm._handlers, capacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupHandler returns the handler registered for request path, or nil if none matches.
|
// LookupHandler returns the handler registered for request path, or nil if none
|
||||||
// The first registration matching both method and uri wins.
|
// matches. The most specific matching registration wins, not the first, so the
|
||||||
|
// catch-all "/" may be registered alongside the endpoints it backs without
|
||||||
|
// shadowing them, as in http.ServeMux, see [patternSpecificity]. Registrations
|
||||||
|
// of equal specificity are resolved in registration order.
|
||||||
//
|
//
|
||||||
// Every method this package does not name is [MethUnknown], so a request with an
|
// Every method this package does not name is [MethUnknown], so a request with an
|
||||||
// extension method matches a bare-path registration and any registration naming
|
// extension method matches a bare-path registration and any registration naming
|
||||||
// an extension method, whichever it names. Tell PROPFIND from MKCOL inside the
|
// an extension method, whichever it names. Tell PROPFIND from MKCOL inside the
|
||||||
// handler with [Exchange.RequestMethodRaw].
|
// handler with [Exchange.RequestMethodRaw].
|
||||||
func (sm *MuxSlice) LookupHandler(method Method, path []byte, dstPathVals []PathValue) (matched string, _ HandlerFunc) {
|
func (sm *MuxSlice) LookupHandler(method Method, path []byte, dstPathVals []PathValue) (matched string, _ HandlerFunc) {
|
||||||
for _, endpoint := range sm._handlers {
|
best := -1
|
||||||
|
bestSpec := 0
|
||||||
|
for i, endpoint := range sm._handlers {
|
||||||
if endpoint.method != MethUndefined && endpoint.method != method {
|
if endpoint.method != MethUndefined && endpoint.method != method {
|
||||||
continue
|
continue
|
||||||
|
} else if best >= 0 && endpoint.spec <= bestSpec {
|
||||||
|
continue // Cannot beat the incumbent, so do not pay to match it.
|
||||||
}
|
}
|
||||||
// Method matches. A pattern ending in '/' is a wildcard despite binding no
|
// Method matches. A pattern ending in '/' is a wildcard despite binding no
|
||||||
// values: the trailing slash is an anonymous "{...}", so it must go
|
// values: the trailing slash is an anonymous "{...}", so it must go
|
||||||
// through the matcher and not a literal compare, see [SetPathValues].
|
// through the matcher and not a literal compare, see [SetPathValues].
|
||||||
|
var ok bool
|
||||||
if isWildcardPattern(endpoint.path) {
|
if isWildcardPattern(endpoint.path) {
|
||||||
if ok, _ := SetPathValues(dstPathVals, endpoint.path, path); ok {
|
// dstPathVals is scratch during the scan: a candidate that matches and
|
||||||
return endpoint.path, endpoint.handler
|
// is then beaten, or one that is beaten and clears on failure, would
|
||||||
}
|
// leave the winner's values wrong, so the winner is bound below.
|
||||||
} else if b2s(path) == endpoint.path {
|
ok, _ = SetPathValues(dstPathVals, endpoint.path, path)
|
||||||
return endpoint.path, endpoint.handler
|
} else {
|
||||||
|
ok = b2s(path) == endpoint.path
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
best, bestSpec = i, endpoint.spec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", nil
|
if best < 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
winner := sm._handlers[best]
|
||||||
|
if isWildcardPattern(winner.path) {
|
||||||
|
clear(dstPathVals) // The scan may have bound more values than the winner does.
|
||||||
|
SetPathValues(dstPathVals, winner.path, path)
|
||||||
|
}
|
||||||
|
return winner.path, winner.handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxPathValues returns the maximum number of path values any endpoint could have.
|
// MaxPathValues returns the maximum number of path values any endpoint could have.
|
||||||
@@ -303,11 +336,51 @@ func (sm *MuxSlice) Handle(optMethodAndPath string, handler HandlerFunc) {
|
|||||||
}
|
}
|
||||||
v := internal.SliceReclaim(&sm._handlers)
|
v := internal.SliceReclaim(&sm._handlers)
|
||||||
v.pathVals = countPathValues(url)
|
v.pathVals = countPathValues(url)
|
||||||
|
v.spec = patternSpecificity(url)
|
||||||
v.method = method
|
v.method = method
|
||||||
v.path = url
|
v.path = url
|
||||||
v.handler = handler
|
v.handler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// patternSpecificity scores how tightly pattern pins a path, letting
|
||||||
|
// [MuxSlice.LookupHandler] prefer the most specific match over the first one
|
||||||
|
// registered. A literal segment pins harder than a wildcard segment, and a
|
||||||
|
// pattern left open at the end ("/", "/files/", "/{p...}") pins less than one
|
||||||
|
// spent on the whole path, so "/cnt" outscores "/" and "/users/me" outscores
|
||||||
|
// "/users/{id}". Scoring at registration keeps lookup to an integer compare.
|
||||||
|
//
|
||||||
|
// The score is a total order over patterns, which the subset relation is not:
|
||||||
|
// neither of "/a/{x}/c" and "/a/b/{y}" is more specific than the other, and they
|
||||||
|
// tie here where http.ServeMux rejects the pair as conflicting. A tie is settled
|
||||||
|
// by registration order rather than by a panic.
|
||||||
|
func patternSpecificity(pattern string) (spec int) {
|
||||||
|
if len(pattern) == 0 || pattern[0] != '/' {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
pattern = pattern[1:]
|
||||||
|
for {
|
||||||
|
if len(pattern) == 0 {
|
||||||
|
return spec // Nothing after a slash: an anonymous "{...}" taking the rest.
|
||||||
|
}
|
||||||
|
segment, rest, more := strings.Cut(pattern, "/")
|
||||||
|
name, isMulti, isWildcard := pathWildcard(segment)
|
||||||
|
switch {
|
||||||
|
case isWildcard && name == "$":
|
||||||
|
return spec + 1 // Ends the path, so nothing is left open.
|
||||||
|
case isWildcard && isMulti:
|
||||||
|
return spec // Takes the remainder, pinning nothing more.
|
||||||
|
case isWildcard:
|
||||||
|
spec++
|
||||||
|
default:
|
||||||
|
spec += 2
|
||||||
|
}
|
||||||
|
if !more {
|
||||||
|
return spec + 1 // Spent on the last segment: the pattern is exact.
|
||||||
|
}
|
||||||
|
pattern = rest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// countPathValues is how many values pattern can bind, which is what sizes the
|
// countPathValues is how many values pattern can bind, which is what sizes the
|
||||||
// slice [SetPathValues] writes into. Only a named wildcard segment binds: "{$}"
|
// slice [SetPathValues] writes into. Only a named wildcard segment binds: "{$}"
|
||||||
// marks the path's end, an anonymous "{...}" has no name to bind under, and a
|
// marks the path's end, an anonymous "{...}" has no name to bind under, and a
|
||||||
|
|||||||
@@ -363,6 +363,85 @@ func TestMuxSliceHandleAllowsExtensionMethod(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A catch-all registered before the endpoints it sits above must not swallow
|
||||||
|
// them. "/" is a wildcard pattern: its trailing slash is an anonymous "{...}",
|
||||||
|
// so a purely first-match-wins scan hands every request to it and the specific
|
||||||
|
// registrations below become dead code, answered with the root page instead of
|
||||||
|
// their own body. Registering the site root first is the ordinary way to write
|
||||||
|
// a mux, so the more specific pattern has to win regardless of order, as in
|
||||||
|
// http.ServeMux.
|
||||||
|
func TestMuxSliceSpecificPatternBeatsEarlierCatchAll(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
register []string
|
||||||
|
path string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "root registered first",
|
||||||
|
register: []string{"/", "/hello", "/cnt", "/6"},
|
||||||
|
path: "/cnt",
|
||||||
|
want: "/cnt",
|
||||||
|
}, {
|
||||||
|
name: "root registered last",
|
||||||
|
register: []string{"/hello", "/cnt", "/6", "/"},
|
||||||
|
path: "/cnt",
|
||||||
|
want: "/cnt",
|
||||||
|
}, {
|
||||||
|
name: "root still serves the root path",
|
||||||
|
register: []string{"/", "/cnt"},
|
||||||
|
path: "/",
|
||||||
|
want: "/",
|
||||||
|
}, {
|
||||||
|
name: "root still catches the unregistered",
|
||||||
|
register: []string{"/", "/cnt"},
|
||||||
|
path: "/nowhere",
|
||||||
|
want: "/",
|
||||||
|
}, {
|
||||||
|
name: "subtree wildcard loses to its own literal",
|
||||||
|
register: []string{"/files/", "/files/index"},
|
||||||
|
path: "/files/index",
|
||||||
|
want: "/files/index",
|
||||||
|
}, {
|
||||||
|
name: "subtree wildcard keeps the rest",
|
||||||
|
register: []string{"/files/", "/files/index"},
|
||||||
|
path: "/files/a/b",
|
||||||
|
want: "/files/",
|
||||||
|
}, {
|
||||||
|
name: "longer literal prefix wins over shorter subtree",
|
||||||
|
register: []string{"/", "/files/", "/files/a/b"},
|
||||||
|
path: "/files/a/b",
|
||||||
|
want: "/files/a/b",
|
||||||
|
}, {
|
||||||
|
name: "named wildcard loses to the literal it covers",
|
||||||
|
register: []string{"/users/{id}", "/users/me"},
|
||||||
|
path: "/users/me",
|
||||||
|
want: "/users/me",
|
||||||
|
}, {
|
||||||
|
name: "named wildcard keeps everything else",
|
||||||
|
register: []string{"/users/{id}", "/users/me"},
|
||||||
|
path: "/users/42",
|
||||||
|
want: "/users/{id}",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
var sm MuxSlice
|
||||||
|
sm.Reset(len(test.register))
|
||||||
|
for _, pattern := range test.register {
|
||||||
|
sm.Handle(pattern, func(ex *Exchange) { ex.WriteHeader(200) })
|
||||||
|
}
|
||||||
|
pathVals := make([]PathValue, max(sm.MaxPathValues(), 0))
|
||||||
|
got, handler := sm.LookupHandler(MethGet, []byte(test.path), pathVals)
|
||||||
|
if handler == nil {
|
||||||
|
t.Fatalf("%s matched no handler, want %q", test.path, test.want)
|
||||||
|
}
|
||||||
|
if got != test.want {
|
||||||
|
t.Errorf("%s matched pattern %q, want %q", test.path, got, test.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// pathVals sizes the slice SetPathValues writes into, so it must count exactly
|
// pathVals sizes the slice SetPathValues writes into, so it must count exactly
|
||||||
// the wildcards that bind. Counting braces over-reports: "{$}" marks the path's
|
// the wildcards that bind. Counting braces over-reports: "{$}" marks the path's
|
||||||
// end, an anonymous "{...}" has no name, and a brace inside a literal segment is
|
// end, an anonymous "{...}" has no name, and a brace inside a literal segment is
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cookie implements cookie key-value parsing. Methods function similarly to eponymous [Header] methods.
|
// Cookie implements cookie key-value parsing. Methods function similarly to eponymous [HeaderV1] methods.
|
||||||
// Cookie represents a single-line Cookie header value in a HTTP header, much like the standard library Cookie.
|
// Cookie represents a single-line Cookie header value in a HTTP header, much like the standard library Cookie.
|
||||||
type Cookie struct {
|
type Cookie struct {
|
||||||
kv kvBuffer
|
kv kvBuffer
|
||||||
@@ -16,7 +16,7 @@ func (c *Cookie) EnableBufferGrowth(enableBufferGrowth bool) {
|
|||||||
c.kv.EnableBufferGrowth(enableBufferGrowth)
|
c.kv.EnableBufferGrowth(enableBufferGrowth)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset functions very similarly to [Header.Reset]. Can be used for in-place cookie parsing.
|
// Reset functions very similarly to [HeaderV1.Reset]. Can be used for in-place cookie parsing.
|
||||||
func (c *Cookie) Reset(buf []byte, capKV int) { c.kv.Reset(buf, capKV) }
|
func (c *Cookie) Reset(buf []byte, capKV int) { c.kv.Reset(buf, capKV) }
|
||||||
|
|
||||||
func (c *Cookie) valid() bool {
|
func (c *Cookie) valid() bool {
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
methodGet = "GET"
|
methodGet = "GET"
|
||||||
strHTTP11 = "HTTP/1.1"
|
strHTTP1 = "HTTP/1."
|
||||||
|
strHTTP11 = strHTTP1 + "1"
|
||||||
strCRLF = "\r\n"
|
strCRLF = "\r\n"
|
||||||
headerCookie = "Cookie"
|
headerCookie = "Cookie"
|
||||||
headerConnection = "Connection"
|
headerConnection = "Connection"
|
||||||
@@ -19,7 +20,7 @@ const (
|
|||||||
|
|
||||||
// Flags is a bitset of signals gathered while parsing or building a header,
|
// Flags is a bitset of signals gathered while parsing or building a header,
|
||||||
// such as a status code having been set or the peer requesting connection
|
// such as a status code having been set or the peer requesting connection
|
||||||
// close. See [Header.Flags].
|
// close. See [HeaderV1.Flags].
|
||||||
type Flags uint16
|
type Flags uint16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -40,15 +41,15 @@ func (f Flags) HasAny(checkThese Flags) bool {
|
|||||||
return f&checkThese != 0
|
return f&checkThese != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header implements "raw" HTTP header key-value parsing, validation and marshalling.
|
// HeaderV1 implements "raw" HTTP header key-value parsing, validation and marshalling.
|
||||||
//
|
//
|
||||||
// It does NOT implement:
|
// It does NOT implement:
|
||||||
// - Normalization.
|
// - Normalization.
|
||||||
// - Cookies (see [Cookie]).
|
// - Cookies (see [Cookie]).
|
||||||
// - Special header optimizations.
|
// - Special header optimizations.
|
||||||
// - Content-Length validation and other special header field value validation.
|
// - Content-Length validation and other special header field value validation.
|
||||||
type Header struct {
|
type HeaderV1 struct {
|
||||||
hbuf headerBuf
|
hbuf headerv1Buf
|
||||||
|
|
||||||
// Request fields.
|
// Request fields.
|
||||||
method view
|
method view
|
||||||
@@ -62,18 +63,18 @@ type Header struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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.hbuf.kv.flags }
|
func (h *HeaderV1) 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 [HeaderV1.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 *HeaderV1) ConfigBufferGrowth(enableBufferGrowth bool) {
|
||||||
h.hbuf.kv.EnableBufferGrowth(enableBufferGrowth)
|
h.hbuf.kv.EnableBufferGrowth(enableBufferGrowth)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 *HeaderV1) ParseBytes(asResponse bool, b []byte) error {
|
||||||
h.Reset(nil, 0)
|
h.Reset(nil, 0)
|
||||||
err := h.hbuf.kv.ReadFromBytes(b)
|
err := h.hbuf.kv.ReadFromBytes(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -82,9 +83,9 @@ func (h *Header) ParseBytes(asResponse bool, b []byte) error {
|
|||||||
return h.parse(asResponse)
|
return h.parse(asResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses accumulated data in-place with no copying. One can set HTTP header data buffer with [Header.Reset].
|
// Parse parses accumulated data in-place with no copying. One can set HTTP header data buffer with [HeaderV1.Reset].
|
||||||
// It fails if HTTP data is incomplete.
|
// It fails if HTTP data is incomplete.
|
||||||
func (h *Header) Parse(asResponse bool) error {
|
func (h *HeaderV1) Parse(asResponse bool) error {
|
||||||
debuglog("http:parse:reset")
|
debuglog("http:parse:reset")
|
||||||
h.Reset(h.hbuf.kv.buf, 0)
|
h.Reset(h.hbuf.kv.buf, 0)
|
||||||
debuglog("http:parse:start")
|
debuglog("http:parse:start")
|
||||||
@@ -93,7 +94,7 @@ func (h *Header) Parse(asResponse bool) error {
|
|||||||
|
|
||||||
// TryParse begins parsing or resumes parsing from a failed previous attempt from any of the Parse* methods.
|
// TryParse begins parsing or resumes parsing from a failed previous attempt from any of the Parse* methods.
|
||||||
// As long as needMoreData returns true future calls to TryParse may succeed and the header is not done parsing.
|
// As long as needMoreData returns true future calls to TryParse may succeed and the header is not done parsing.
|
||||||
// Users may call [Header.ForEach] in-between TryParse calls so as to validate values before header is completely parsed.
|
// Users may call [HeaderV1.ForEach] in-between TryParse calls so as to validate values before header is completely parsed.
|
||||||
//
|
//
|
||||||
// needMoreData := true
|
// needMoreData := true
|
||||||
// var err error
|
// var err error
|
||||||
@@ -107,7 +108,7 @@ func (h *Header) Parse(asResponse bool) error {
|
|||||||
// if err != nil {
|
// if err != nil {
|
||||||
// return err
|
// return err
|
||||||
// }
|
// }
|
||||||
func (h *Header) TryParse(asResponse bool) (needMoreData bool, err error) {
|
func (h *HeaderV1) TryParse(asResponse bool) (needMoreData bool, err error) {
|
||||||
flags := h.Flags()
|
flags := h.Flags()
|
||||||
if flags.HasAny(flagDoneParsingHeader) {
|
if flags.HasAny(flagDoneParsingHeader) {
|
||||||
return false, errAlreadyParsed
|
return false, errAlreadyParsed
|
||||||
@@ -125,26 +126,26 @@ func (h *Header) TryParse(asResponse bool) (needMoreData bool, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 *HeaderV1) 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 [HeaderV1.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 *HeaderV1) ReadFromLimited(r io.Reader, maxBytesToRead int) (int, error) {
|
||||||
return h.hbuf.kv.ReadLimited(r, maxBytesToRead)
|
return h.hbuf.kv.ReadLimited(r, maxBytesToRead)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 [HeaderV1.TryParse].
|
||||||
func (h *Header) ReadFromBytes(b []byte) error {
|
func (h *HeaderV1) ReadFromBytes(b []byte) error {
|
||||||
return h.hbuf.kv.ReadFromBytes(b)
|
return h.hbuf.kv.ReadFromBytes(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 *HeaderV1) BufferReceived() int {
|
||||||
if h.Flags().HasAny(flagMangledBuffer | flagOOMReached) {
|
if h.Flags().HasAny(flagMangledBuffer | flagOOMReached) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -154,7 +155,7 @@ func (h *Header) BufferReceived() int {
|
|||||||
// 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 *HeaderV1) BufferParsed() int {
|
||||||
if h.Flags().HasAny(flagMangledBuffer | flagOOMReached) {
|
if h.Flags().HasAny(flagMangledBuffer | flagOOMReached) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -162,56 +163,56 @@ 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 [HeaderV1.BufferCapacity].
|
||||||
func (h *Header) BufferRaw() []byte { return h.hbuf.kv.BufferRaw() }
|
func (h *HeaderV1) 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 *HeaderV1) BufferUsed() int {
|
||||||
return len(h.hbuf.kv.BufferRaw())
|
return len(h.hbuf.kv.BufferRaw())
|
||||||
}
|
}
|
||||||
|
|
||||||
// BufferFree returns amount of bytes free in underlying buffer.
|
// BufferFree returns amount of bytes free in underlying buffer.
|
||||||
//
|
//
|
||||||
// BufferUsed + BufferFree == BufferCapacity
|
// BufferUsed + BufferFree == BufferCapacity
|
||||||
func (h *Header) BufferFree() int {
|
func (h *HeaderV1) BufferFree() int {
|
||||||
return h.hbuf.free()
|
return h.hbuf.free()
|
||||||
}
|
}
|
||||||
|
|
||||||
// BufferCapacity returns the total capacity of the underlying buffer.
|
// BufferCapacity returns the total capacity of the underlying buffer.
|
||||||
//
|
//
|
||||||
// BufferUsed + BufferFree == BufferCapacity
|
// BufferUsed + BufferFree == BufferCapacity
|
||||||
func (h *Header) BufferCapacity() int {
|
func (h *HeaderV1) BufferCapacity() int {
|
||||||
return cap(h.hbuf.kv.BufferRaw())
|
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) bool) {
|
func (h *HeaderV1) ForEach(cb func(key, value []byte) bool) {
|
||||||
h.hbuf.kv.ForEach(cb)
|
h.hbuf.kv.ForEach(cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
// can be used to avoid copying and growing buffers. Call [Header.Parse] after setting buffer
|
// can be used to avoid copying and growing buffers. Call [HeaderV1.Parse] after setting buffer
|
||||||
// data with Reset to parse data in-place.
|
// data with Reset to parse data in-place.
|
||||||
// If buf is nil then the current buffer is reused. There are 3 ways to use Reset:
|
// If buf is nil then the current buffer is reused. There are 3 ways to use Reset:
|
||||||
//
|
//
|
||||||
// h.Reset(prealloc[:0], 16); h.ParseBytes(httpHeader) // Tell header to use a pre-allocated buffer capacity.
|
// h.Reset(prealloc[:0], 16); h.ParseBytes(httpHeader) // Tell header to use a pre-allocated buffer capacity.
|
||||||
// h.Reset(httpHeader, 16); h.Parse() // Parse bytes in place with no copying.
|
// h.Reset(httpHeader, 16); h.Parse() // Parse bytes in place with no copying.
|
||||||
// h.Reset(nil) // Reuse buffer previously set in a call to Reset.
|
// h.Reset(nil) // Reuse buffer previously set in a call to Reset.
|
||||||
func (h *Header) Reset(buf []byte, numHeaderCapacity int) {
|
func (h *HeaderV1) 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) && h.BufferCapacity() < 32 {
|
if h.Flags().HasAny(flagNoBufferGrow) && h.BufferCapacity() < 32 {
|
||||||
panic("small buffer and flagNoBufferGrow set")
|
panic("small buffer and flagNoBufferGrow set")
|
||||||
}
|
}
|
||||||
*h = Header{hbuf: h.hbuf}
|
*h = HeaderV1{hbuf: h.hbuf}
|
||||||
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 *HeaderV1) Body() ([]byte, error) {
|
||||||
debuglog("http:body")
|
debuglog("http:body")
|
||||||
flags := h.Flags()
|
flags := h.Flags()
|
||||||
if flags.HasAny(flagMangledBuffer) {
|
if flags.HasAny(flagMangledBuffer) {
|
||||||
@@ -222,16 +223,16 @@ func (h *Header) Body() ([]byte, error) {
|
|||||||
return nil, errUnparsed
|
return nil, errUnparsed
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBytes is equivalent to [Header.Set] but with a []byte value. Does not keep reference to value slice.
|
// SetBytes is equivalent to [HeaderV1.Set] but with a []byte value. Does not keep reference to value slice.
|
||||||
// Calling SetBytes Mangles the buffer.
|
// Calling SetBytes Mangles the buffer.
|
||||||
func (h *Header) SetBytes(key string, value []byte) {
|
func (h *HeaderV1) SetBytes(key string, value []byte) {
|
||||||
h.Set(key, b2s(value))
|
h.Set(key, b2s(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetInt is equivalent to [Header.Set] but with an integer value i.e: Content-Length header key.
|
// SetInt is equivalent to [HeaderV1.Set] but with an integer value i.e: Content-Length header key.
|
||||||
// base must be in the range 2..36 (as accepted by [strconv.AppendInt]); other bases are dropped.
|
// base must be in the range 2..36 (as accepted by [strconv.AppendInt]); other bases are dropped.
|
||||||
// SetInt formats the value directly into the header buffer without heap allocation.
|
// SetInt formats the value directly into the header buffer without heap allocation.
|
||||||
func (h *Header) SetInt(key string, value int64, base int) {
|
func (h *HeaderV1) 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.
|
||||||
}
|
}
|
||||||
@@ -240,25 +241,25 @@ 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) (enoughSpace bool) {
|
func (h *HeaderV1) Set(key, value string) (enoughSpace bool) {
|
||||||
return h.hbuf.kv.Set(key, value)
|
return h.hbuf.kv.Set(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 [HeaderV1.ForEach] to find multiple values corresponding to same key.
|
||||||
func (h *Header) Get(key string) []byte {
|
func (h *HeaderV1) Get(key string) []byte {
|
||||||
return h.hbuf.kv.Get(key)
|
return h.hbuf.kv.Get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
// comparison, i.e: "content-length" matches "Content-Length".
|
// comparison, i.e: "content-length" matches "Content-Length".
|
||||||
// Use [Header.Get] for exact match and [Header.ForEach] to find multiple values
|
// Use [HeaderV1.Get] for exact match and [HeaderV1.ForEach] to find multiple values
|
||||||
// corresponding to same key.
|
// corresponding to same key.
|
||||||
func (h *Header) GetFold(key string) []byte {
|
func (h *HeaderV1) GetFold(key string) []byte {
|
||||||
return h.hbuf.kv.GetFold(key)
|
return h.hbuf.kv.GetFold(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 *HeaderV1) NormalizeKeys() {
|
||||||
for i, kv := range h.hbuf.kv.kvs {
|
for i, kv := range h.hbuf.kv.kvs {
|
||||||
if kv.isValidHeader() {
|
if kv.isValidHeader() {
|
||||||
NormalizeHeaderKey(h.hbuf.kv.AtKey(i))
|
NormalizeHeaderKey(h.hbuf.kv.AtKey(i))
|
||||||
@@ -268,7 +269,7 @@ func (h *Header) NormalizeKeys() {
|
|||||||
|
|
||||||
// ContentLength returns the body length declared by the Content-Length field.
|
// ContentLength returns the body length declared by the Content-Length field.
|
||||||
// If the field is not present then the returned bool is false. Will return error for invalid or non-integer value.
|
// If the field is not present then the returned bool is false. Will return error for invalid or non-integer value.
|
||||||
func (h *Header) ContentLength() (_ int64, present bool, _ error) {
|
func (h *HeaderV1) ContentLength() (_ int64, present bool, _ error) {
|
||||||
value := h.GetFold(headerContentLength)
|
value := h.GetFold(headerContentLength)
|
||||||
if value == nil {
|
if value == nil {
|
||||||
return 0, false, nil
|
return 0, false, nil
|
||||||
@@ -283,34 +284,34 @@ 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 *HeaderV1) Add(key, value string) {
|
||||||
h.hbuf.kv.appendPair(key, value)
|
h.hbuf.kv.appendPair(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method returns HTTP request method.
|
// Method returns HTTP request method.
|
||||||
func (h *Header) Method() []byte {
|
func (h *HeaderV1) Method() []byte {
|
||||||
return h.getNonEmptyValue(h.method)
|
return h.getNonEmptyValue(h.method)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMethod sets the request header's method.
|
// SetMethod sets the request header's method.
|
||||||
func (h *Header) SetMethod(method string) {
|
func (h *HeaderV1) SetMethod(method string) {
|
||||||
h.method = h.hbuf.kv.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 *HeaderV1) SetRequestTarget(requestTarget string) {
|
||||||
h.requestTarget = h.hbuf.kv.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.
|
||||||
// Called Request-URI in the obsolete RFC 2616, renamed request-target by RFC 9112.
|
// Called Request-URI in the obsolete RFC 2616, renamed request-target by RFC 9112.
|
||||||
func (h *Header) RequestTarget() []byte {
|
func (h *HeaderV1) RequestTarget() []byte {
|
||||||
return h.getNonEmptyValue(h.requestTarget)
|
return h.getNonEmptyValue(h.requestTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestPath returns the request-target (URI) up to the query string, i.e: "/search"
|
// RequestPath returns the request-target (URI) up to the query string, i.e: "/search"
|
||||||
// for "/search?q=go". Returns the whole target if it contains no query string.
|
// for "/search?q=go". Returns the whole target if it contains no query string.
|
||||||
func (h *Header) RequestPath() []byte {
|
func (h *HeaderV1) RequestPath() []byte {
|
||||||
target := h.RequestTarget()
|
target := h.RequestTarget()
|
||||||
before, _, ok := bytes.Cut(target, []byte{'?'})
|
before, _, ok := bytes.Cut(target, []byte{'?'})
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -322,7 +323,7 @@ func (h *Header) RequestPath() []byte {
|
|||||||
// RequestQuery returns the request-target (URI) query string as it appears on the
|
// RequestQuery returns the request-target (URI) query string as it appears on the
|
||||||
// wire, percent-encoded and with '+' undecoded, i.e: "q=go" for "/search?q=go".
|
// wire, percent-encoded and with '+' undecoded, i.e: "q=go" for "/search?q=go".
|
||||||
// Returns nil if the target has no query string. Iterate it with [NextQueryPair].
|
// Returns nil if the target has no query string. Iterate it with [NextQueryPair].
|
||||||
func (h *Header) RequestQuery() []byte {
|
func (h *HeaderV1) RequestQuery() []byte {
|
||||||
target := h.RequestTarget()
|
target := h.RequestTarget()
|
||||||
_, after, ok := bytes.Cut(target, []byte{'?'})
|
_, after, ok := bytes.Cut(target, []byte{'?'})
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -332,17 +333,17 @@ func (h *Header) RequestQuery() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Protocol returns the request header's HTTP protocol. Usually "HTTP/1.1".
|
// Protocol returns the request header's HTTP protocol. Usually "HTTP/1.1".
|
||||||
func (h *Header) Protocol() []byte {
|
func (h *HeaderV1) Protocol() []byte {
|
||||||
return h.getNonEmptyValue(h.proto)
|
return h.getNonEmptyValue(h.proto)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 *HeaderV1) SetProtocol(protocol string) {
|
||||||
h.proto = h.hbuf.kv.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".
|
||||||
func (h *Header) Status() (code, statusText []byte) {
|
func (h *HeaderV1) Status() (code, statusText []byte) {
|
||||||
if h.statusCode.len == 0 {
|
if h.statusCode.len == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -350,20 +351,20 @@ 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 *HeaderV1) SetStatus(code, statusText string) {
|
||||||
h.hbuf.kv.flags |= FlagStatusSet
|
h.hbuf.kv.flags |= FlagStatusSet
|
||||||
h.statusCode = h.hbuf.kv.reuseOrAppend(h.statusCode, code)
|
h.statusCode = h.hbuf.kv.reuseOrAppend(h.statusCode, code)
|
||||||
h.statusText = h.hbuf.kv.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 [HeaderV1.SetStatus] but performs integer to text conversion for status code.
|
||||||
func (h *Header) SetStatusInt(code int64, statusText string) {
|
func (h *HeaderV1) SetStatusInt(code int64, statusText string) {
|
||||||
h.hbuf.kv.flags |= FlagStatusSet
|
h.hbuf.kv.flags |= FlagStatusSet
|
||||||
h.statusCode = h.hbuf.kv.reuseOrAppendInt(h.statusCode, code, 10)
|
h.statusCode = h.hbuf.kv.reuseOrAppendInt(h.statusCode, code, 10)
|
||||||
h.statusText = h.hbuf.kv.reuseOrAppend(h.statusText, statusText)
|
h.statusText = h.hbuf.kv.reuseOrAppend(h.statusText, statusText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Header) getNonEmptyValue(s view) []byte {
|
func (h *HeaderV1) getNonEmptyValue(s view) []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.
|
||||||
}
|
}
|
||||||
@@ -371,7 +372,7 @@ func (h *Header) getNonEmptyValue(s view) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 *HeaderV1) 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, ErrBufferExhausted
|
return dst, ErrBufferExhausted
|
||||||
@@ -401,7 +402,7 @@ func (h *Header) AppendRequest(dst []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AppendResponse appends the response header representation to the buffer and returns the result.
|
// AppendResponse appends the response header representation to the buffer and returns the result.
|
||||||
func (h *Header) AppendResponse(dst []byte) ([]byte, error) {
|
func (h *HeaderV1) AppendResponse(dst []byte) ([]byte, error) {
|
||||||
dst, err := h.AppendResponseNoHeaders(dst)
|
dst, err := h.AppendResponseNoHeaders(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dst, err
|
return dst, err
|
||||||
@@ -411,7 +412,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 *HeaderV1) 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, ErrBufferExhausted
|
return dst, ErrBufferExhausted
|
||||||
@@ -433,7 +434,7 @@ 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 *HeaderV1) AppendHeaders(dst []byte) []byte {
|
||||||
for i, kv := range h.hbuf.kv.kvs {
|
for i, kv := range h.hbuf.kv.kvs {
|
||||||
if kv.isValidHeader() {
|
if kv.isValidHeader() {
|
||||||
k, v := h.hbuf.kv.At(i)
|
k, v := h.hbuf.kv.At(i)
|
||||||
@@ -446,7 +447,7 @@ func (h *Header) AppendHeaders(dst []byte) []byte {
|
|||||||
// String returns the header's wire representation, as a request if it has a
|
// String returns the header's wire representation, as a request if it has a
|
||||||
// request line and as a response otherwise. Returns the error text if neither
|
// request line and as a response otherwise. Returns the error text if neither
|
||||||
// can be built. Allocates, so it is meant for debugging and logging only.
|
// can be built. Allocates, so it is meant for debugging and logging only.
|
||||||
func (h *Header) String() string {
|
func (h *HeaderV1) String() string {
|
||||||
buf, err := h.AppendRequest(nil)
|
buf, err := h.AppendRequest(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
buf, err = h.AppendResponse(nil)
|
buf, err = h.AppendResponse(nil)
|
||||||
@@ -44,7 +44,7 @@ func TestHeaderParseRequest(t *testing.T) {
|
|||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
req.Write(&buf)
|
req.Write(&buf)
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
msg := buf.Bytes()
|
msg := buf.Bytes()
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
@@ -128,7 +128,7 @@ func BenchmarkParseBytes(b *testing.B) {
|
|||||||
// allocating on every iteration. Declaring it inside the loop causes
|
// allocating on every iteration. Declaring it inside the loop causes
|
||||||
// two allocs per iteration: one for the headers slice (make in reset)
|
// two allocs per iteration: one for the headers slice (make in reset)
|
||||||
// and one for the data buffer (append in readFromBytes).
|
// and one for the data buffer (append in readFromBytes).
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
b.StartTimer()
|
b.StartTimer()
|
||||||
|
|
||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
@@ -169,7 +169,7 @@ func TestHeaderRequestPath(t *testing.T) {
|
|||||||
{uri: "/a/b/c?x=1&y=2", want: "/a/b/c"},
|
{uri: "/a/b/c?x=1&y=2", want: "/a/b/c"},
|
||||||
{uri: "/?q=go", want: "/"},
|
{uri: "/?q=go", want: "/"},
|
||||||
} {
|
} {
|
||||||
var h Header
|
var h HeaderV1
|
||||||
err := h.ParseBytes(false, []byte("GET "+test.uri+" HTTP/1.1\r\nHost: h\r\n\r\n"))
|
err := h.ParseBytes(false, []byte("GET "+test.uri+" HTTP/1.1\r\nHost: h\r\n\r\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -196,7 +196,7 @@ func TestHeaderContentLength(t *testing.T) {
|
|||||||
{field: "Content-Length: 1 2", wantErr: errBadContentLength}, // Not a list.
|
{field: "Content-Length: 1 2", wantErr: errBadContentLength}, // Not a list.
|
||||||
{field: "Content-Length: 9223372036854775808", wantErr: errBadContentLength},
|
{field: "Content-Length: 9223372036854775808", wantErr: errBadContentLength},
|
||||||
} {
|
} {
|
||||||
var h Header
|
var h HeaderV1
|
||||||
raw := "POST / HTTP/1.1\r\nHost: h\r\n"
|
raw := "POST / HTTP/1.1\r\nHost: h\r\n"
|
||||||
if test.field != "" {
|
if test.field != "" {
|
||||||
raw += test.field + "\r\n"
|
raw += test.field + "\r\n"
|
||||||
@@ -234,7 +234,7 @@ func TestNextQueryPair(t *testing.T) {
|
|||||||
{uri: "/x?a%20b=c%20d", want: "a%20b=c%20d"}, // Raw, undecoded.
|
{uri: "/x?a%20b=c%20d", want: "a%20b=c%20d"}, // Raw, undecoded.
|
||||||
{uri: "/x?a=b=c", want: "a=b=c"}, // Only first '=' splits.
|
{uri: "/x?a=b=c", want: "a=b=c"}, // Only first '=' splits.
|
||||||
} {
|
} {
|
||||||
var h Header
|
var h HeaderV1
|
||||||
err := h.ParseBytes(false, []byte("GET "+test.uri+" HTTP/1.1\r\nHost: h\r\n\r\n"))
|
err := h.ParseBytes(false, []byte("GET "+test.uri+" HTTP/1.1\r\nHost: h\r\n\r\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -397,7 +397,7 @@ func TestCopyDecodedPercentURLInPlace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHeaderSetOverwrite(t *testing.T) {
|
func TestHeaderSetOverwrite(t *testing.T) {
|
||||||
var h Header
|
var h HeaderV1
|
||||||
h.Reset(nil, defaultKVCap)
|
h.Reset(nil, defaultKVCap)
|
||||||
h.SetMethod("GET")
|
h.SetMethod("GET")
|
||||||
h.SetRequestTarget("/")
|
h.SetRequestTarget("/")
|
||||||
@@ -420,7 +420,7 @@ func TestHeaderSetOverwrite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHeaderSetBytesEmptyValue(t *testing.T) {
|
func TestHeaderSetBytesEmptyValue(t *testing.T) {
|
||||||
var h Header
|
var h HeaderV1
|
||||||
h.Reset(nil, defaultKVCap)
|
h.Reset(nil, defaultKVCap)
|
||||||
h.SetBytes("X-Empty", nil)
|
h.SetBytes("X-Empty", nil)
|
||||||
if got := h.Get("X-Empty"); len(got) != 0 {
|
if got := h.Get("X-Empty"); len(got) != 0 {
|
||||||
@@ -440,7 +440,7 @@ func TestHeader_LargeBufferOverflow(t *testing.T) {
|
|||||||
"X-Canary: " + wantVal + "\r\n" +
|
"X-Canary: " + wantVal + "\r\n" +
|
||||||
"\r\n"
|
"\r\n"
|
||||||
|
|
||||||
var h Header
|
var h HeaderV1
|
||||||
err := h.ParseBytes(false, []byte(raw))
|
err := h.ParseBytes(false, []byte(raw))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Clean rejection of the oversized header is the intended behavior:
|
// Clean rejection of the oversized header is the intended behavior:
|
||||||
@@ -458,7 +458,7 @@ func TestHeader_LargeBufferOverflow(t *testing.T) {
|
|||||||
// not ErrNeedMoreData (which makes a streaming parser wait forever).
|
// not ErrNeedMoreData (which makes a streaming parser wait forever).
|
||||||
func TestHeader_ColonlessLineIsHardError(t *testing.T) {
|
func TestHeader_ColonlessLineIsHardError(t *testing.T) {
|
||||||
raw := "GET / HTTP/1.1\r\nBadHeaderNoColon\r\n\r\n"
|
raw := "GET / HTTP/1.1\r\nBadHeaderNoColon\r\n\r\n"
|
||||||
var h Header
|
var h HeaderV1
|
||||||
err := h.ParseBytes(false, []byte(raw))
|
err := h.ParseBytes(false, []byte(raw))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("want error on colonless header line, got nil")
|
t.Fatal("want error on colonless header line, got nil")
|
||||||
@@ -475,7 +475,7 @@ func TestHeader_SplitBeforeColonStillParses(t *testing.T) {
|
|||||||
const part1 = "GET / HTTP/1.1\r\nHost" // split mid-key, before colon+newline
|
const part1 = "GET / HTTP/1.1\r\nHost" // split mid-key, before colon+newline
|
||||||
const part2 = ": example.com\r\n\r\n"
|
const part2 = ": example.com\r\n\r\n"
|
||||||
|
|
||||||
var h Header
|
var h HeaderV1
|
||||||
h.Reset(nil, defaultKVCap)
|
h.Reset(nil, defaultKVCap)
|
||||||
if err := h.ReadFromBytes([]byte(part1)); err != nil {
|
if err := h.ReadFromBytes([]byte(part1)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -508,7 +508,7 @@ func TestHeader_SplitBeforeColonStillParses(t *testing.T) {
|
|||||||
func TestHeader_AppendHeaderExactCapNoPanic(t *testing.T) {
|
func TestHeader_AppendHeaderExactCapNoPanic(t *testing.T) {
|
||||||
const key, value = "K", "V"
|
const key, value = "K", "V"
|
||||||
buf := make([]byte, 0, len(key)+len(value)) // exact cap, no slack.
|
buf := make([]byte, 0, len(key)+len(value)) // exact cap, no slack.
|
||||||
var h Header
|
var h HeaderV1
|
||||||
h.Reset(buf, defaultKVCap)
|
h.Reset(buf, defaultKVCap)
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@@ -525,7 +525,7 @@ func TestHeader_AppendHeaderExactCapNoPanic(t *testing.T) {
|
|||||||
// never panic. Panicking is unacceptable for this package.
|
// never panic. Panicking is unacceptable for this package.
|
||||||
func TestHeader_AddFullBufferNoPanic(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.
|
buf := make([]byte, 0, 40) // Small cap; enough for Reset (len 0) but not the field below.
|
||||||
var h Header
|
var h HeaderV1
|
||||||
h.Reset(buf, defaultKVCap)
|
h.Reset(buf, defaultKVCap)
|
||||||
h.ConfigBufferGrowth(false)
|
h.ConfigBufferGrowth(false)
|
||||||
h.SetMethod("GET")
|
h.SetMethod("GET")
|
||||||
@@ -560,7 +560,7 @@ func TestHeader_SetInt(t *testing.T) {
|
|||||||
{"hex", 255, 16, "ff"},
|
{"hex", 255, 16, "ff"},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var h Header
|
var h HeaderV1
|
||||||
h.Reset(nil, defaultKVCap)
|
h.Reset(nil, defaultKVCap)
|
||||||
h.SetInt("Content-Length", tc.value, tc.base)
|
h.SetInt("Content-Length", tc.value, tc.base)
|
||||||
if got := string(h.Get("Content-Length")); got != tc.want {
|
if got := string(h.Get("Content-Length")); got != tc.want {
|
||||||
@@ -572,7 +572,7 @@ func TestHeader_SetInt(t *testing.T) {
|
|||||||
|
|
||||||
// SetInt on an existing key must reuse the slot in place (single field, latest value).
|
// SetInt on an existing key must reuse the slot in place (single field, latest value).
|
||||||
func TestHeader_SetIntOverwrite(t *testing.T) {
|
func TestHeader_SetIntOverwrite(t *testing.T) {
|
||||||
var h Header
|
var h HeaderV1
|
||||||
h.Reset(nil, defaultKVCap)
|
h.Reset(nil, defaultKVCap)
|
||||||
h.SetMethod("GET")
|
h.SetMethod("GET")
|
||||||
h.SetRequestTarget("/")
|
h.SetRequestTarget("/")
|
||||||
@@ -596,7 +596,7 @@ func TestHeader_SetIntOverwrite(t *testing.T) {
|
|||||||
// SetInt must not heap-allocate: it must format directly into the header buffer.
|
// SetInt must not heap-allocate: it must format directly into the header buffer.
|
||||||
func TestHeader_SetIntNoAlloc(t *testing.T) {
|
func TestHeader_SetIntNoAlloc(t *testing.T) {
|
||||||
buf := make([]byte, 0, 256)
|
buf := make([]byte, 0, 256)
|
||||||
var h Header
|
var h HeaderV1
|
||||||
h.Reset(buf, defaultKVCap)
|
h.Reset(buf, defaultKVCap)
|
||||||
h.ConfigBufferGrowth(false)
|
h.ConfigBufferGrowth(false)
|
||||||
h.Add("Content-Length", "0000000000000000000000") // pre-size a reusable slot.
|
h.Add("Content-Length", "0000000000000000000000") // pre-size a reusable slot.
|
||||||
@@ -626,7 +626,7 @@ func TestHeader_FieldTableSizedFromBuffer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
raw.WriteString("X-Canary: " + wantVal + "\r\n\r\n")
|
raw.WriteString("X-Canary: " + wantVal + "\r\n\r\n")
|
||||||
|
|
||||||
var h Header
|
var h HeaderV1
|
||||||
h.Reset(make([]byte, 0, 8192), defaultKVCap) // 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()))
|
err := h.ParseBytes(false, []byte(raw.String()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -649,7 +649,7 @@ func TestHeader_FieldTableFullIsReported(t *testing.T) {
|
|||||||
raw.WriteString(":v\r\n")
|
raw.WriteString(":v\r\n")
|
||||||
}
|
}
|
||||||
raw.WriteString("\r\n")
|
raw.WriteString("\r\n")
|
||||||
var h Header
|
var h HeaderV1
|
||||||
h.Reset(make([]byte, 0, 512), defaultKVCap)
|
h.Reset(make([]byte, 0, 512), defaultKVCap)
|
||||||
h.ConfigBufferGrowth(false)
|
h.ConfigBufferGrowth(false)
|
||||||
err := h.ParseBytes(false, []byte(raw.String()))
|
err := h.ParseBytes(false, []byte(raw.String()))
|
||||||
@@ -468,7 +468,7 @@ func (pair pairKV) isValid() bool {
|
|||||||
return pair.key.len > 0 || pair.value.len > 0
|
return pair.key.len > 0 || pair.value.len > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// isValidHeader is for the append-built [Header] store, where mustAppendSlice
|
// isValidHeader is for the append-built [HeaderV1] store, where mustAppendSlice
|
||||||
// burns byte 0 so a zero offset means absent. Drops offset-0 pairs otherwise.
|
// burns byte 0 so a zero offset means absent. Drops offset-0 pairs otherwise.
|
||||||
func (pair pairKV) isValidHeader() bool { return pair.key.start > 0 }
|
func (pair pairKV) isValidHeader() bool { return pair.key.start > 0 }
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/soypat/lneto"
|
||||||
"github.com/soypat/lneto/internal"
|
"github.com/soypat/lneto/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,7 +49,11 @@ var (
|
|||||||
// returning the wrong bytes or panicking on a wrapped slice bound.
|
// returning the wrong bytes or panicking on a wrapped slice bound.
|
||||||
const maxBufLen = 0xffff
|
const maxBufLen = 0xffff
|
||||||
|
|
||||||
type headerBuf struct {
|
func protoIsV1(s string) bool {
|
||||||
|
return s[:min(len(strHTTP1), len(s))] == strHTTP1
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerv1Buf struct {
|
||||||
kv kvBuffer
|
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
|
||||||
@@ -61,7 +66,7 @@ type headerBuf struct {
|
|||||||
// 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 *headerv1Buf) reset(buf []byte, numHeaderCapacity int) {
|
||||||
h.kv.Reset(buf, numHeaderCapacity)
|
h.kv.Reset(buf, numHeaderCapacity)
|
||||||
h.off = 0
|
h.off = 0
|
||||||
}
|
}
|
||||||
@@ -80,7 +85,7 @@ type scannerState struct {
|
|||||||
initialized bool
|
initialized bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Header) parse(asResponse bool) (err error) {
|
func (h *HeaderV1) parse(asResponse bool) (err error) {
|
||||||
debuglog("http:firstline:start")
|
debuglog("http:firstline:start")
|
||||||
err = h.parseFirstLine(asResponse)
|
err = h.parseFirstLine(asResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -93,7 +98,7 @@ func (h *Header) parse(asResponse bool) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Header) parseFirstLine(asResponse bool) (err error) {
|
func (h *HeaderV1) parseFirstLine(asResponse bool) (err error) {
|
||||||
if len(h.hbuf.kv.buf) > maxBufLen {
|
if len(h.hbuf.kv.buf) > maxBufLen {
|
||||||
return errBufferTooLarge // Offsets would overflow uint16 tokint.
|
return errBufferTooLarge // Offsets would overflow uint16 tokint.
|
||||||
}
|
}
|
||||||
@@ -107,7 +112,7 @@ func (h *Header) parseFirstLine(asResponse bool) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Header) parseNextHeaders(flags Flags) error {
|
func (h *HeaderV1) 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 {
|
||||||
@@ -118,9 +123,9 @@ func (h *Header) parseNextHeaders(flags Flags) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hb *headerBuf) free() int { return hb.kv.free() }
|
func (hb *headerv1Buf) free() int { return hb.kv.free() }
|
||||||
|
|
||||||
func (hb *headerBuf) parseNextHeaders(ss *scannerState, flags Flags) {
|
func (hb *headerv1Buf) parseNextHeaders(ss *scannerState, flags Flags) {
|
||||||
debuglog("http:nexthdr:loop")
|
debuglog("http:nexthdr:loop")
|
||||||
for kv := hb.next(ss); kv.isValidHeader(); kv = hb.next(ss) {
|
for kv := hb.next(ss); kv.isValidHeader(); kv = hb.next(ss) {
|
||||||
if !hb.kv.canAddOneKV() {
|
if !hb.kv.canAddOneKV() {
|
||||||
@@ -132,17 +137,17 @@ func (hb *headerBuf) parseNextHeaders(ss *scannerState, flags Flags) {
|
|||||||
debuglog("http:nexthdr:done")
|
debuglog("http:nexthdr:done")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hb *headerBuf) offBuf() []byte {
|
func (hb *headerv1Buf) offBuf() []byte {
|
||||||
return hb.kv.buf[hb.off:]
|
return hb.kv.buf[hb.off:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hb *headerBuf) skipLeadingCRLF() {
|
func (hb *headerv1Buf) skipLeadingCRLF() {
|
||||||
for hb.off < len(hb.kv.buf) && (hb.kv.buf[hb.off] == '\n' || hb.kv.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++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hb *headerBuf) scanLine() []byte {
|
func (hb *headerv1Buf) scanLine() []byte {
|
||||||
buf := hb.scanUntilByte('\n')
|
buf := hb.scanUntilByte('\n')
|
||||||
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.
|
||||||
@@ -153,7 +158,7 @@ func (hb *headerBuf) scanLine() []byte {
|
|||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hb *headerBuf) scanUntilByte(c byte) []byte {
|
func (hb *headerv1Buf) scanUntilByte(c byte) []byte {
|
||||||
buf := hb.offBuf()
|
buf := hb.offBuf()
|
||||||
idx := bytes.IndexByte(buf, c)
|
idx := bytes.IndexByte(buf, c)
|
||||||
if idx >= 0 {
|
if idx >= 0 {
|
||||||
@@ -163,7 +168,7 @@ func (hb *headerBuf) scanUntilByte(c byte) []byte {
|
|||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hb *headerBuf) parseFirstLineRequest(initFlags Flags) (method, uri, proto view, flags Flags, err error) {
|
func (hb *headerv1Buf) parseFirstLineRequest(initFlags Flags) (method, uri, proto view, flags Flags, err error) {
|
||||||
debuglog("http:req:scan")
|
debuglog("http:req:scan")
|
||||||
hb.off = 0 // Parsing first line resets offset.
|
hb.off = 0 // Parsing first line resets offset.
|
||||||
hb.skipLeadingCRLF()
|
hb.skipLeadingCRLF()
|
||||||
@@ -183,21 +188,30 @@ func (hb *headerBuf) parseFirstLineRequest(initFlags Flags) (method, uri, proto
|
|||||||
reqURIEnd += methodEnd + 1
|
reqURIEnd += methodEnd + 1
|
||||||
uri = hb.kv.view(b[methodEnd+1 : reqURIEnd])
|
uri = hb.kv.view(b[methodEnd+1 : reqURIEnd])
|
||||||
proto = hb.kv.view(b[reqURIEnd+1:]) // Skip space before protocol.
|
proto = hb.kv.view(b[reqURIEnd+1:]) // Skip space before protocol.
|
||||||
if b2s(b[reqURIEnd+1:]) != strHTTP11 {
|
protoText := b2s(b[reqURIEnd+1:])
|
||||||
flags |= flagNoHTTP11
|
if !protoIsV1(protoText) {
|
||||||
|
// Refused here rather than after the fields: the field loop is nearly
|
||||||
|
// all of the parse cost and none of it serves a version this type
|
||||||
|
// does not speak. proto is set so the caller can name it, i.e: 505.
|
||||||
|
method = hb.kv.view(b[:methodEnd])
|
||||||
|
return method, uri, proto, flags | flagNoHTTP11, lneto.ErrUnsupported
|
||||||
|
} else if protoText != strHTTP11 {
|
||||||
|
flags |= flagNoHTTP11 // HTTP/1.0, which defaults to closing the connection.
|
||||||
}
|
}
|
||||||
} else if reqURIEnd == 0 {
|
} else if reqURIEnd == 0 {
|
||||||
return method, uri, proto, flags, errEmptyURI
|
return method, uri, proto, flags, errEmptyURI
|
||||||
} else {
|
} else {
|
||||||
// No version provided.
|
// No version at all is a HTTP/0.9 simple-request, not a 1.x request-line,
|
||||||
flags |= flagNoHTTP11
|
// RFC 9112 3. proto stays empty, telling it apart from a named version.
|
||||||
uri = hb.kv.view(b[methodEnd+1:])
|
uri = hb.kv.view(b[methodEnd+1:])
|
||||||
|
method = hb.kv.view(b[:methodEnd])
|
||||||
|
return method, uri, proto, flags | flagNoHTTP11, lneto.ErrUnsupported
|
||||||
}
|
}
|
||||||
method = hb.kv.view(b[:methodEnd])
|
method = hb.kv.view(b[:methodEnd])
|
||||||
return method, uri, proto, flags, nil
|
return method, uri, proto, flags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hb *headerBuf) parseFirstLineResponse(initFlags Flags) (statusCode, statusText view, flags Flags, err error) {
|
func (hb *headerv1Buf) parseFirstLineResponse(initFlags Flags) (statusCode, statusText view, flags Flags, err error) {
|
||||||
debuglog("http:resp:scan")
|
debuglog("http:resp:scan")
|
||||||
hb.off = 0 // Parsing first line resets offset.
|
hb.off = 0 // Parsing first line resets offset.
|
||||||
hb.skipLeadingCRLF()
|
hb.skipLeadingCRLF()
|
||||||
@@ -216,7 +230,11 @@ func (hb *headerBuf) parseFirstLineResponse(initFlags Flags) (statusCode, status
|
|||||||
if protoEnd < 0 {
|
if protoEnd < 0 {
|
||||||
return statusCode, statusText, flags, ErrNeedMoreData
|
return statusCode, statusText, flags, ErrNeedMoreData
|
||||||
}
|
}
|
||||||
if b2s(b[:protoEnd]) != strHTTP11 {
|
if !protoIsV1(b2s(b[:protoEnd])) {
|
||||||
|
// Refused before the fields, as on the request side: a response naming
|
||||||
|
// another version is not one this type can read.
|
||||||
|
return statusCode, statusText, flags | flagNoHTTP11, lneto.ErrUnsupported
|
||||||
|
} else if b2s(b[:protoEnd]) != strHTTP11 {
|
||||||
flags |= flagNoHTTP11
|
flags |= flagNoHTTP11
|
||||||
}
|
}
|
||||||
b = b[protoEnd+1:] // Advance past protocol and space.
|
b = b[protoEnd+1:] // Advance past protocol and space.
|
||||||
@@ -243,7 +261,7 @@ func (hb *headerBuf) parseFirstLineResponse(initFlags Flags) (statusCode, status
|
|||||||
return statusCode, statusText, flags, nil
|
return statusCode, statusText, flags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hb *headerBuf) next(ss *scannerState) pairKV {
|
func (hb *headerv1Buf) next(ss *scannerState) pairKV {
|
||||||
if !ss.initialized {
|
if !ss.initialized {
|
||||||
ss.nextColon = -1
|
ss.nextColon = -1
|
||||||
ss.nextNewLine = -1
|
ss.nextNewLine = -1
|
||||||
@@ -328,7 +346,7 @@ func (hb *headerBuf) next(ss *scannerState) pairKV {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 *HeaderV1) ConnectionClose() bool {
|
||||||
flags := h.Flags()
|
flags := h.Flags()
|
||||||
closed := flags.HasAny(flagConnClose) ||
|
closed := flags.HasAny(flagConnClose) ||
|
||||||
h.hasConnectionToken(strClose) ||
|
h.hasConnectionToken(strClose) ||
|
||||||
@@ -342,7 +360,7 @@ func (h *Header) ConnectionClose() bool {
|
|||||||
// hasConnectionToken reports whether the Connection field lists token, which
|
// hasConnectionToken reports whether the Connection field lists token, which
|
||||||
// must be lowercase. The field name, its comma list and each token all compare
|
// must be lowercase. The field name, its comma list and each token all compare
|
||||||
// case insensitively, RFC 9110 5.1 and 7.6.1.
|
// case insensitively, RFC 9110 5.1 and 7.6.1.
|
||||||
func (h *Header) hasConnectionToken(token string) bool {
|
func (h *HeaderV1) hasConnectionToken(token string) bool {
|
||||||
value := h.GetFold(headerConnection)
|
value := h.GetFold(headerConnection)
|
||||||
for len(value) > 0 {
|
for len(value) > 0 {
|
||||||
item := value
|
item := value
|
||||||
@@ -2,14 +2,17 @@ package httpraw
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/soypat/lneto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTryParse_IncrementalRequest(t *testing.T) {
|
func TestTryParse_IncrementalRequest(t *testing.T) {
|
||||||
// Full HTTP request split across multiple ReadFromBytes calls.
|
// 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"
|
full := "GET /index.html HTTP/1.1\r\nHost: example.com\r\nContent-Type: text/html\r\n\r\nbody here"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
|
|
||||||
// Feed data in small chunks to exercise incremental parsing.
|
// Feed data in small chunks to exercise incremental parsing.
|
||||||
@@ -78,7 +81,7 @@ func TestTryParse_IncrementalRequest(t *testing.T) {
|
|||||||
|
|
||||||
func TestTryParse_IncrementalResponse(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"
|
full := "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nServer: lneto\r\n\r\nhello"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
|
|
||||||
chunks := splitInto(full, 8)
|
chunks := splitInto(full, 8)
|
||||||
@@ -130,7 +133,7 @@ func TestReadFromLimited(t *testing.T) {
|
|||||||
data := "GET / HTTP/1.1\r\nHost: test\r\n\r\n"
|
data := "GET / HTTP/1.1\r\nHost: test\r\n\r\n"
|
||||||
r := strings.NewReader(data)
|
r := strings.NewReader(data)
|
||||||
|
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
|
|
||||||
// Read in one shot.
|
// Read in one shot.
|
||||||
@@ -156,7 +159,7 @@ func TestReadFromLimited(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadFromLimited_MaxBytes(t *testing.T) {
|
func TestReadFromLimited_MaxBytes(t *testing.T) {
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
|
|
||||||
// Zero maxBytesToRead should error.
|
// Zero maxBytesToRead should error.
|
||||||
@@ -167,7 +170,7 @@ func TestReadFromLimited_MaxBytes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadFromBytes_Empty(t *testing.T) {
|
func TestReadFromBytes_Empty(t *testing.T) {
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
|
|
||||||
err := hdr.ReadFromBytes(nil)
|
err := hdr.ReadFromBytes(nil)
|
||||||
@@ -177,7 +180,7 @@ func TestReadFromBytes_Empty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBufferFreeAndCapacity(t *testing.T) {
|
func TestBufferFreeAndCapacity(t *testing.T) {
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
hdr.Reset(make([]byte, 0, 100), defaultKVCap)
|
hdr.Reset(make([]byte, 0, 100), defaultKVCap)
|
||||||
|
|
||||||
if hdr.BufferCapacity() != 100 {
|
if hdr.BufferCapacity() != 100 {
|
||||||
@@ -194,7 +197,7 @@ func TestBufferFreeAndCapacity(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEnableBufferGrowth(t *testing.T) {
|
func TestEnableBufferGrowth(t *testing.T) {
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
buf := make([]byte, 0, 64)
|
buf := make([]byte, 0, 64)
|
||||||
hdr.Reset(buf, defaultKVCap)
|
hdr.Reset(buf, defaultKVCap)
|
||||||
hdr.ConfigBufferGrowth(false)
|
hdr.ConfigBufferGrowth(false)
|
||||||
@@ -211,7 +214,7 @@ func TestEnableBufferGrowth(t *testing.T) {
|
|||||||
|
|
||||||
func TestHeader_Add(t *testing.T) {
|
func TestHeader_Add(t *testing.T) {
|
||||||
full := "GET / HTTP/1.1\r\nHost: test\r\n\r\n"
|
full := "GET / HTTP/1.1\r\nHost: test\r\n\r\n"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
err := hdr.ParseBytes(false, []byte(full))
|
err := hdr.ParseBytes(false, []byte(full))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -238,7 +241,7 @@ func TestHeader_Add(t *testing.T) {
|
|||||||
|
|
||||||
func TestHeader_SetBytes(t *testing.T) {
|
func TestHeader_SetBytes(t *testing.T) {
|
||||||
full := "GET / HTTP/1.1\r\nHost: test\r\n\r\n"
|
full := "GET / HTTP/1.1\r\nHost: test\r\n\r\n"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
err := hdr.ParseBytes(false, []byte(full))
|
err := hdr.ParseBytes(false, []byte(full))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -254,7 +257,7 @@ func TestHeader_SetBytes(t *testing.T) {
|
|||||||
func TestConnectionClose(t *testing.T) {
|
func TestConnectionClose(t *testing.T) {
|
||||||
t.Run("HTTP11_NoConnectionHeader", func(t *testing.T) {
|
t.Run("HTTP11_NoConnectionHeader", func(t *testing.T) {
|
||||||
full := "GET / HTTP/1.1\r\nHost: test\r\n\r\n"
|
full := "GET / HTTP/1.1\r\nHost: test\r\n\r\n"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
hdr.ParseBytes(false, []byte(full))
|
hdr.ParseBytes(false, []byte(full))
|
||||||
if hdr.ConnectionClose() {
|
if hdr.ConnectionClose() {
|
||||||
t.Error("HTTP/1.1 without Connection:close should not close")
|
t.Error("HTTP/1.1 without Connection:close should not close")
|
||||||
@@ -263,7 +266,7 @@ func TestConnectionClose(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("ExplicitClose", func(t *testing.T) {
|
t.Run("ExplicitClose", func(t *testing.T) {
|
||||||
full := "GET / HTTP/1.1\r\nConnection: close\r\nHost: test\r\n\r\n"
|
full := "GET / HTTP/1.1\r\nConnection: close\r\nHost: test\r\n\r\n"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
hdr.ParseBytes(false, []byte(full))
|
hdr.ParseBytes(false, []byte(full))
|
||||||
if !hdr.ConnectionClose() {
|
if !hdr.ConnectionClose() {
|
||||||
t.Error("Connection:close header should trigger close")
|
t.Error("Connection:close header should trigger close")
|
||||||
@@ -272,7 +275,7 @@ func TestConnectionClose(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("HTTP10_NoKeepAlive", func(t *testing.T) {
|
t.Run("HTTP10_NoKeepAlive", func(t *testing.T) {
|
||||||
full := "GET / HTTP/1.0\r\nHost: test\r\n\r\n"
|
full := "GET / HTTP/1.0\r\nHost: test\r\n\r\n"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
hdr.ParseBytes(false, []byte(full))
|
hdr.ParseBytes(false, []byte(full))
|
||||||
if !hdr.ConnectionClose() {
|
if !hdr.ConnectionClose() {
|
||||||
t.Error("HTTP/1.0 without keep-alive should close")
|
t.Error("HTTP/1.0 without keep-alive should close")
|
||||||
@@ -281,7 +284,7 @@ func TestConnectionClose(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("HTTP10_KeepAlive", func(t *testing.T) {
|
t.Run("HTTP10_KeepAlive", func(t *testing.T) {
|
||||||
full := "GET / HTTP/1.0\r\nConnection: keep-alive\r\nHost: test\r\n\r\n"
|
full := "GET / HTTP/1.0\r\nConnection: keep-alive\r\nHost: test\r\n\r\n"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
hdr.ParseBytes(false, []byte(full))
|
hdr.ParseBytes(false, []byte(full))
|
||||||
if hdr.ConnectionClose() {
|
if hdr.ConnectionClose() {
|
||||||
t.Error("HTTP/1.0 with keep-alive should not close")
|
t.Error("HTTP/1.0 with keep-alive should not close")
|
||||||
@@ -291,7 +294,7 @@ func TestConnectionClose(t *testing.T) {
|
|||||||
|
|
||||||
func TestTryParse_AlreadyParsed(t *testing.T) {
|
func TestTryParse_AlreadyParsed(t *testing.T) {
|
||||||
full := "GET / HTTP/1.1\r\nHost: test\r\n\r\n"
|
full := "GET / HTTP/1.1\r\nHost: test\r\n\r\n"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
hdr.ParseBytes(false, []byte(full))
|
hdr.ParseBytes(false, []byte(full))
|
||||||
|
|
||||||
// Calling TryParse again should return error.
|
// Calling TryParse again should return error.
|
||||||
@@ -303,7 +306,7 @@ func TestTryParse_AlreadyParsed(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseResponse_BadStatusCode(t *testing.T) {
|
func TestParseResponse_BadStatusCode(t *testing.T) {
|
||||||
full := "HTTP/1.1 abc Bad\r\n\r\n"
|
full := "HTTP/1.1 abc Bad\r\n\r\n"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
err := hdr.ParseBytes(true, []byte(full))
|
err := hdr.ParseBytes(true, []byte(full))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error for non-numeric status code")
|
t.Fatal("expected error for non-numeric status code")
|
||||||
@@ -370,7 +373,7 @@ func TestCookie_ForEach(t *testing.T) {
|
|||||||
func TestHeader_MultilineValue(t *testing.T) {
|
func TestHeader_MultilineValue(t *testing.T) {
|
||||||
// RFC 7230: obsolete line folding with \r\n followed by space/tab.
|
// RFC 7230: obsolete line folding with \r\n followed by space/tab.
|
||||||
full := "GET / HTTP/1.1\r\nX-Multi: line1\r\n\tline2\r\nHost: test\r\n\r\n"
|
full := "GET / HTTP/1.1\r\nX-Multi: line1\r\n\tline2\r\nHost: test\r\n\r\n"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
err := hdr.ParseBytes(false, []byte(full))
|
err := hdr.ParseBytes(false, []byte(full))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -386,7 +389,7 @@ func TestHeader_MultilineValue(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHeader_ResponseRoundTrip(t *testing.T) {
|
func TestHeader_ResponseRoundTrip(t *testing.T) {
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
hdr.SetProtocol("HTTP/1.1")
|
hdr.SetProtocol("HTTP/1.1")
|
||||||
hdr.SetStatus("404", "Not Found")
|
hdr.SetStatus("404", "Not Found")
|
||||||
@@ -411,7 +414,7 @@ func TestHeader_ResponseRoundTrip(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse back the generated response.
|
// Parse back the generated response.
|
||||||
var hdr2 Header
|
var hdr2 HeaderV1
|
||||||
err = hdr2.ParseBytes(true, buf)
|
err = hdr2.ParseBytes(true, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("re-parse response: %v", err)
|
t.Fatalf("re-parse response: %v", err)
|
||||||
@@ -426,7 +429,7 @@ func TestHeader_ResponseRoundTrip(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHeader_RequestRoundTrip(t *testing.T) {
|
func TestHeader_RequestRoundTrip(t *testing.T) {
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
hdr.SetProtocol("HTTP/1.1")
|
hdr.SetProtocol("HTTP/1.1")
|
||||||
hdr.SetMethod("POST")
|
hdr.SetMethod("POST")
|
||||||
@@ -444,7 +447,7 @@ func TestHeader_RequestRoundTrip(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse back the generated request.
|
// Parse back the generated request.
|
||||||
var hdr2 Header
|
var hdr2 HeaderV1
|
||||||
err = hdr2.ParseBytes(false, buf)
|
err = hdr2.ParseBytes(false, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("re-parse request: %v", err)
|
t.Fatalf("re-parse request: %v", err)
|
||||||
@@ -463,7 +466,7 @@ func TestHeader_RequestRoundTrip(t *testing.T) {
|
|||||||
func TestParseResponse_StatusCodeOnly(t *testing.T) {
|
func TestParseResponse_StatusCodeOnly(t *testing.T) {
|
||||||
// Response with status code but no status text.
|
// Response with status code but no status text.
|
||||||
full := "HTTP/1.1 204\r\n\r\n"
|
full := "HTTP/1.1 204\r\n\r\n"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
err := hdr.ParseBytes(true, []byte(full))
|
err := hdr.ParseBytes(true, []byte(full))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -477,7 +480,7 @@ func TestParseResponse_StatusCodeOnly(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseResponse_HTTP10(t *testing.T) {
|
func TestParseResponse_HTTP10(t *testing.T) {
|
||||||
full := "HTTP/1.0 200 OK\r\nServer: old\r\n\r\n"
|
full := "HTTP/1.0 200 OK\r\nServer: old\r\n\r\n"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
err := hdr.ParseBytes(true, []byte(full))
|
err := hdr.ParseBytes(true, []byte(full))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -492,12 +495,14 @@ func TestParseResponse_HTTP10(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseRequest_NoProtocol(t *testing.T) {
|
func TestParseRequest_NoProtocol(t *testing.T) {
|
||||||
// HTTP/0.9 style: just method and URI, no version.
|
// HTTP/0.9 style: just method and URI, no version. Refused, but the
|
||||||
|
// request-line it did read stays readable so a caller can answer 400 and say
|
||||||
|
// what it saw.
|
||||||
full := "GET /simple\r\nHost: test\r\n\r\n"
|
full := "GET /simple\r\nHost: test\r\n\r\n"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
err := hdr.ParseBytes(false, []byte(full))
|
err := hdr.ParseBytes(false, []byte(full))
|
||||||
if err != nil {
|
if !errors.Is(err, lneto.ErrUnsupported) {
|
||||||
t.Fatal(err)
|
t.Fatalf("want lneto.ErrUnsupported for a version-less request, got %v", err)
|
||||||
}
|
}
|
||||||
if string(hdr.Method()) != "GET" {
|
if string(hdr.Method()) != "GET" {
|
||||||
t.Errorf("method = %q; want GET", hdr.Method())
|
t.Errorf("method = %q; want GET", hdr.Method())
|
||||||
@@ -505,6 +510,8 @@ func TestParseRequest_NoProtocol(t *testing.T) {
|
|||||||
if string(hdr.RequestTarget()) != "/simple" {
|
if string(hdr.RequestTarget()) != "/simple" {
|
||||||
t.Errorf("URI = %q; want /simple", hdr.RequestTarget())
|
t.Errorf("URI = %q; want /simple", hdr.RequestTarget())
|
||||||
}
|
}
|
||||||
|
// An empty protocol is what tells HTTP/0.9 apart from a named version, which
|
||||||
|
// is how [httphi] picks 400 over 505.
|
||||||
if hdr.Protocol() != nil {
|
if hdr.Protocol() != nil {
|
||||||
t.Errorf("protocol should be nil for version-less request, got %q", hdr.Protocol())
|
t.Errorf("protocol should be nil for version-less request, got %q", hdr.Protocol())
|
||||||
}
|
}
|
||||||
@@ -521,7 +528,7 @@ func TestCookie_QuotedValue(t *testing.T) {
|
|||||||
func TestParseRequest_InvalidHeaderSpaceBeforeColon(t *testing.T) {
|
func TestParseRequest_InvalidHeaderSpaceBeforeColon(t *testing.T) {
|
||||||
// RFC 7230 §3.2.4: No whitespace allowed between header name and colon.
|
// RFC 7230 §3.2.4: No whitespace allowed between header name and colon.
|
||||||
full := "GET / HTTP/1.1\r\nBad Header : value\r\n\r\n"
|
full := "GET / HTTP/1.1\r\nBad Header : value\r\n\r\n"
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
err := hdr.ParseBytes(false, []byte(full))
|
err := hdr.ParseBytes(false, []byte(full))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error for space before colon in header name")
|
t.Fatal("expected error for space before colon in header name")
|
||||||
@@ -567,7 +574,7 @@ func TestConnectionCloseFolded(t *testing.T) {
|
|||||||
{proto: "HTTP/1.0", field: "Host: h", wantClose: true},
|
{proto: "HTTP/1.0", field: "Host: h", wantClose: true},
|
||||||
} {
|
} {
|
||||||
t.Run(test.proto+" "+test.field, func(t *testing.T) {
|
t.Run(test.proto+" "+test.field, func(t *testing.T) {
|
||||||
var hdr Header
|
var hdr HeaderV1
|
||||||
full := "GET / " + test.proto + "\r\nHost: h\r\n" + test.field + "\r\n\r\n"
|
full := "GET / " + test.proto + "\r\nHost: h\r\n" + test.field + "\r\n\r\n"
|
||||||
if err := hdr.ParseBytes(false, []byte(full)); err != nil {
|
if err := hdr.ParseBytes(false, []byte(full)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -578,3 +585,63 @@ func TestConnectionCloseFolded(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HeaderV1 speaks HTTP/1.x and nothing else, so a request or response naming
|
||||||
|
// another version is refused on the first line. That skips the field loop, which
|
||||||
|
// is where nearly all the parse cost is, and refuses the h2c preface a modern
|
||||||
|
// client opens with before it is mistaken for a request.
|
||||||
|
func TestHeaderV1RejectsUnsupportedVersion(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
raw string
|
||||||
|
asResponse bool
|
||||||
|
}{
|
||||||
|
{name: "request http2", raw: "GET / HTTP/2.0\r\nHost: h\r\n\r\n"},
|
||||||
|
{name: "request http3", raw: "GET / HTTP/3.0\r\nHost: h\r\n\r\n"},
|
||||||
|
{name: "h2c preface", raw: "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"},
|
||||||
|
{name: "request http09 no version", raw: "GET /index.html\r\nHost: h\r\n\r\n"},
|
||||||
|
{name: "request bogus proto", raw: "GET / BANANA\r\nHost: h\r\n\r\n"},
|
||||||
|
{name: "response http2", raw: "HTTP/2.0 200 OK\r\nServer: s\r\n\r\n", asResponse: true},
|
||||||
|
{name: "response http09", raw: "HTTP/0.9 200 OK\r\nServer: s\r\n\r\n", asResponse: true},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
var h HeaderV1
|
||||||
|
err := h.ParseBytes(test.asResponse, []byte(test.raw))
|
||||||
|
if !errors.Is(err, lneto.ErrUnsupported) {
|
||||||
|
t.Fatalf("want lneto.ErrUnsupported, got %v", err)
|
||||||
|
}
|
||||||
|
// The field loop must not have run: refusing early is the point.
|
||||||
|
fields := 0
|
||||||
|
h.ForEach(func(key, value []byte) bool { fields++; return true })
|
||||||
|
if fields != 0 {
|
||||||
|
t.Errorf("want no fields parsed, got %d", fields)
|
||||||
|
}
|
||||||
|
if h.ParsingSuccess() {
|
||||||
|
t.Error("a refused header must not report a successful parse")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both HTTP/1 versions stay supported: only non-1.x is refused.
|
||||||
|
func TestHeaderV1AcceptsV1Versions(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
raw string
|
||||||
|
asResponse bool
|
||||||
|
}{
|
||||||
|
{raw: "GET / HTTP/1.1\r\nHost: h\r\n\r\n"},
|
||||||
|
{raw: "GET / HTTP/1.0\r\nHost: h\r\n\r\n"},
|
||||||
|
{raw: "HTTP/1.1 200 OK\r\nServer: s\r\n\r\n", asResponse: true},
|
||||||
|
{raw: "HTTP/1.0 200 OK\r\nServer: s\r\n\r\n", asResponse: true},
|
||||||
|
} {
|
||||||
|
t.Run(test.raw[:12], func(t *testing.T) {
|
||||||
|
var h HeaderV1
|
||||||
|
if err := h.ParseBytes(test.asResponse, []byte(test.raw)); err != nil {
|
||||||
|
t.Fatalf("want parsed, got %v", err)
|
||||||
|
}
|
||||||
|
if !h.ParsingSuccess() {
|
||||||
|
t.Error("want a successful parse")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type PacketBreakdown struct {
|
type PacketBreakdown struct {
|
||||||
hdr httpraw.Header
|
hdr httpraw.HeaderV1
|
||||||
dmsg dns.Message
|
dmsg dns.Message
|
||||||
vld lneto.Validator
|
vld lneto.Validator
|
||||||
// SubfieldLimit will limit the number of captured subfields to the value it has.
|
// SubfieldLimit will limit the number of captured subfields to the value it has.
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
const httpProtocol = "HTTP/1.1"
|
const httpProtocol = "HTTP/1.1"
|
||||||
|
|
||||||
func makeHttpPayload(body string) ([]byte, error) {
|
func makeHttpPayload(body string) ([]byte, error) {
|
||||||
var hdr httpraw.Header
|
var hdr httpraw.HeaderV1
|
||||||
hdr.SetProtocol(httpProtocol)
|
hdr.SetProtocol(httpProtocol)
|
||||||
hdr.SetStatus("200", "OK")
|
hdr.SetStatus("200", "OK")
|
||||||
hdr.Set("Cookie", "ABC=123")
|
hdr.Set("Cookie", "ABC=123")
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func FuzzStackPacketHTTP(f *testing.F) {
|
|||||||
const seed = 1
|
const seed = 1
|
||||||
var buf [ethernet.MaxFrameLength]byte
|
var buf [ethernet.MaxFrameLength]byte
|
||||||
s1, s2, c1, c2 := newTCPStacks(f, seed, MTU)
|
s1, s2, c1, c2 := newTCPStacks(f, seed, MTU)
|
||||||
var hdr httpraw.Header
|
var hdr httpraw.HeaderV1
|
||||||
err := s1.ListenTCP4(c1, 80)
|
err := s1.ListenTCP4(c1, 80)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.Fatal(err)
|
f.Fatal(err)
|
||||||
@@ -129,7 +129,7 @@ func FuzzStackPacketHTTP(f *testing.F) {
|
|||||||
if n1 == 0 && n2 == 0 {
|
if n1 == 0 && n2 == 0 {
|
||||||
if !closed {
|
if !closed {
|
||||||
if c1.BufferedInput() > 0 {
|
if c1.BufferedInput() > 0 {
|
||||||
var hdr httpraw.Header
|
var hdr httpraw.HeaderV1
|
||||||
n, _ := c1.Read(buf[:])
|
n, _ := c1.Read(buf[:])
|
||||||
hdr.ReadFromBytes(buf[:n])
|
hdr.ReadFromBytes(buf[:n])
|
||||||
hdr.TryParse(false)
|
hdr.TryParse(false)
|
||||||
|
|||||||
Reference in New Issue
Block a user