mirror of
https://github.com/soypat/lneto.git
synced 2026-08-21 23:19:03 +00:00
Mux.MaxPathValues and other improvements
This commit is contained in:
@@ -28,7 +28,6 @@ err := router.Configure(httphi.RouterConfig{
|
|||||||
RequestHeaderBufferSize: 1024,
|
RequestHeaderBufferSize: 1024,
|
||||||
ResponseHeaderMinBufferSize: 32, // Shares the request buffer.
|
ResponseHeaderMinBufferSize: 32, // Shares the request buffer.
|
||||||
RequestNumHeaderKVCap: 32,
|
RequestNumHeaderKVCap: 32,
|
||||||
Backoff: func(uint) time.Duration { return time.Millisecond },
|
|
||||||
Mux: &mux,
|
Mux: &mux,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+17
-6
@@ -35,7 +35,7 @@ type Exchange struct {
|
|||||||
respHeaderOff uint16
|
respHeaderOff uint16
|
||||||
respHeaderLen uint16
|
respHeaderLen uint16
|
||||||
reqHdr httpraw.Header
|
reqHdr httpraw.Header
|
||||||
pathValues []pathValue
|
pathValues []PathValue
|
||||||
|
|
||||||
hijacked bool
|
hijacked bool
|
||||||
rw conn
|
rw conn
|
||||||
@@ -253,11 +253,18 @@ func (exch *Exchange) StageStatus(code int) {
|
|||||||
|
|
||||||
// WriteHeader sends the status line for code along with the staged header
|
// WriteHeader sends the status line for code along with the staged header
|
||||||
// fields. Only the first call reaches the wire, as in http.ResponseWriter.
|
// fields. Only the first call reaches the wire, as in http.ResponseWriter.
|
||||||
func (exch *Exchange) WriteHeader(code int) {
|
func (exch *Exchange) WriteHeader(code int) (n int, err error) {
|
||||||
if !exch.headerWritten {
|
if !exch.headerWritten {
|
||||||
exch.StageStatus(code)
|
exch.StageStatus(code)
|
||||||
exch.FlushHeader()
|
n, err = exch.FlushHeader()
|
||||||
}
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseError returns any error encountered during staging of headers or during writing of response.
|
||||||
|
// Provides an ergonomic way of checking if one ran out of buffer space after staging all headers with [Exchange.StageHeader].
|
||||||
|
func (exch *Exchange) ResponseError() error {
|
||||||
|
return exch.respErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlushHeader writes the status line and staged header fields to the connection
|
// FlushHeader writes the status line and staged header fields to the connection
|
||||||
@@ -709,9 +716,13 @@ func (exch *Exchange) PathValueAppend(dst []byte, key string, decoded bool) ([]b
|
|||||||
return dst[:base+n], nil
|
return dst[:base+n], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestMethod returns the request line's method, i.e: "GET". See
|
// RequestMethod returns the request's [Method] enum.
|
||||||
// [MethodFromBytes] to compare it against a [Method].
|
func (exch *Exchange) RequestMethod() Method {
|
||||||
func (exch *Exchange) RequestMethod() []byte {
|
return MethodFromBytes(exch.RequestMethodRaw())
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestMethod returns the request line's method as a []byte view, i.e: "GET".
|
||||||
|
func (exch *Exchange) RequestMethodRaw() []byte {
|
||||||
return exch.RequestHeaderRaw().Method()
|
return exch.RequestHeaderRaw().Method()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ func TestHandleRequestFields(t *testing.T) {
|
|||||||
var sm MuxSlice
|
var sm MuxSlice
|
||||||
route, _, _ := strings.Cut(test.wantURI, "?") // Mux matches on path.
|
route, _, _ := strings.Cut(test.wantURI, "?") // Mux matches on path.
|
||||||
sm.Handle(route, func(ex *Exchange) {
|
sm.Handle(route, func(ex *Exchange) {
|
||||||
gotMethod = string(ex.RequestMethod())
|
gotMethod = string(ex.RequestMethodRaw())
|
||||||
gotURI = string(ex.RequestTarget())
|
gotURI = string(ex.RequestTarget())
|
||||||
gotHost = string(ex.RequestHeader("Host"))
|
gotHost = string(ex.RequestHeader("Host"))
|
||||||
ex.WriteHeader(200)
|
ex.WriteHeader(200)
|
||||||
|
|||||||
+31
-19
@@ -101,22 +101,13 @@ type Mux interface {
|
|||||||
// LookupHandler matches the requestPath and method to a handler and returns it and the
|
// LookupHandler matches the requestPath and method to a handler and returns it and the
|
||||||
// pattern it matched. dstPathVals are set to non-zero values by Mux and can later be accessed by [Exchange.PathValue]
|
// pattern it matched. dstPathVals are set to non-zero values by Mux and can later be accessed by [Exchange.PathValue]
|
||||||
// requestPath is a buffer owned by the [Exchange] usually and should not be held after LookupHandler returns.
|
// requestPath is a buffer owned by the [Exchange] usually and should not be held after LookupHandler returns.
|
||||||
LookupHandler(get Method, requestPath []byte, dstPathVals []pathValue) (matchedPattern string, handler HandlerFunc)
|
LookupHandler(get Method, requestPath []byte, dstPathVals []PathValue) (matchedPattern string, handler HandlerFunc)
|
||||||
|
// MaxPathValues specifies the required size of dstPathVals in a call to [Mux.LookupHandler].
|
||||||
|
MaxPathValues() int
|
||||||
}
|
}
|
||||||
|
|
||||||
// MuxSlice is a [Mux] backed by a slice of registered endpoints, matched by
|
// PathValue used to implement [Mux] interface. Stores http.Request.PathValue-like values.
|
||||||
// exact path. Lookup is linear in the number of registrations.
|
type PathValue struct {
|
||||||
type MuxSlice struct {
|
|
||||||
// TODO: binary search worth it?
|
|
||||||
_handlers []struct {
|
|
||||||
method Method
|
|
||||||
path string
|
|
||||||
handler HandlerFunc
|
|
||||||
setPathVal bool
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type pathValue struct {
|
|
||||||
Key string // owned by mux.
|
Key string // owned by mux.
|
||||||
Value []byte // points to raw exchange buffer.
|
Value []byte // points to raw exchange buffer.
|
||||||
}
|
}
|
||||||
@@ -133,7 +124,7 @@ var pathSeparator = []byte{'/'}
|
|||||||
// Unlike ServeMux, segments are compared and bound raw, so "/users/{id}" binds
|
// Unlike ServeMux, segments are compared and bound raw, so "/users/{id}" binds
|
||||||
// "x%2Fy" and not "x/y". Which paths match is unaffected. Bound values alias
|
// "x%2Fy" and not "x/y". Which paths match is unaffected. Bound values alias
|
||||||
// requestPath rather than copy it.
|
// requestPath rather than copy it.
|
||||||
func SetPathValues(dstPathVals []pathValue, pattern string, requestPath []byte) (matched, pathValSliceTooShort bool) {
|
func SetPathValues(dstPathVals []PathValue, pattern string, requestPath []byte) (matched, pathValSliceTooShort bool) {
|
||||||
if len(pattern) == 0 || pattern[0] != '/' || len(requestPath) == 0 || requestPath[0] != '/' {
|
if len(pattern) == 0 || pattern[0] != '/' || len(requestPath) == 0 || requestPath[0] != '/' {
|
||||||
return false, false
|
return false, false
|
||||||
}
|
}
|
||||||
@@ -160,7 +151,7 @@ func SetPathValues(dstPathVals []pathValue, pattern string, requestPath []byte)
|
|||||||
if n >= len(dstPathVals) {
|
if n >= len(dstPathVals) {
|
||||||
return false, true
|
return false, true
|
||||||
}
|
}
|
||||||
dstPathVals[n] = pathValue{Key: name, Value: requestPath}
|
dstPathVals[n] = PathValue{Key: name, Value: requestPath}
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
return true, false
|
return true, false
|
||||||
@@ -172,7 +163,7 @@ func SetPathValues(dstPathVals []pathValue, pattern string, requestPath []byte)
|
|||||||
if n >= len(dstPathVals) {
|
if n >= len(dstPathVals) {
|
||||||
return false, true
|
return false, true
|
||||||
}
|
}
|
||||||
dstPathVals[n] = pathValue{Key: name, Value: reqSeg}
|
dstPathVals[n] = PathValue{Key: name, Value: reqSeg}
|
||||||
n++
|
n++
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -205,6 +196,18 @@ func pathWildcard(segment string) (name string, isMulti, ok bool) {
|
|||||||
return name, false, true
|
return name, false, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MuxSlice is a [Mux] implementation backed by a slice of registered endpoints, matched by
|
||||||
|
// exact path. Lookup is linear in the number of registrations.
|
||||||
|
type MuxSlice struct {
|
||||||
|
// TODO: binary search worth it?
|
||||||
|
_handlers []struct {
|
||||||
|
method Method
|
||||||
|
path string
|
||||||
|
handler HandlerFunc
|
||||||
|
pathVals int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reset discards all registered handlers, reusing the backing array and growing
|
// Reset discards all registered handlers, reusing the backing array and growing
|
||||||
// it to fit capacity registrations.
|
// it to fit capacity registrations.
|
||||||
func (sm *MuxSlice) Reset(capacity int) {
|
func (sm *MuxSlice) Reset(capacity int) {
|
||||||
@@ -213,13 +216,13 @@ func (sm *MuxSlice) Reset(capacity int) {
|
|||||||
|
|
||||||
// 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 matches.
|
||||||
// The first registration matching both method and uri wins.
|
// The first registration matching both method and uri wins.
|
||||||
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 {
|
for _, endpoint := range sm._handlers {
|
||||||
if endpoint.method != MethUndefined && endpoint.method != method {
|
if endpoint.method != MethUndefined && endpoint.method != method {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Method matches.
|
// Method matches.
|
||||||
if endpoint.setPathVal {
|
if endpoint.pathVals > 0 {
|
||||||
if ok, _ := SetPathValues(dstPathVals, endpoint.path, path); ok {
|
if ok, _ := SetPathValues(dstPathVals, endpoint.path, path); ok {
|
||||||
return endpoint.path, endpoint.handler
|
return endpoint.path, endpoint.handler
|
||||||
}
|
}
|
||||||
@@ -230,6 +233,14 @@ func (sm *MuxSlice) LookupHandler(method Method, path []byte, dstPathVals []path
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MaxPathValues returns the maximum number of path values any endpoint could have.
|
||||||
|
func (sm *MuxSlice) MaxPathValues() (maxPathValues int) {
|
||||||
|
for _, endpoint := range sm._handlers {
|
||||||
|
maxPathValues = max(maxPathValues, endpoint.pathVals)
|
||||||
|
}
|
||||||
|
return maxPathValues
|
||||||
|
}
|
||||||
|
|
||||||
// Handle registers handler for reg, either a bare path matching any method or a
|
// Handle registers handler for reg, either a bare path matching any method or a
|
||||||
// method and path separated by a space, i.e: "/health" or "GET /health".
|
// method and path separated by a space, i.e: "/health" or "GET /health".
|
||||||
// Handle does not check for duplicate registrations: the first one added wins.
|
// Handle does not check for duplicate registrations: the first one added wins.
|
||||||
@@ -242,6 +253,7 @@ func (sm *MuxSlice) Handle(optMethodAndPath string, handler HandlerFunc) {
|
|||||||
} else {
|
} else {
|
||||||
url = methodOrURL
|
url = methodOrURL
|
||||||
}
|
}
|
||||||
|
v.pathVals = strings.Count(optMethodAndPath, "{")
|
||||||
v.method = method
|
v.method = method
|
||||||
v.path = url
|
v.path = url
|
||||||
v.handler = handler
|
v.handler = handler
|
||||||
|
|||||||
+12
-8
@@ -54,7 +54,7 @@ func TestSetPathValues(t *testing.T) {
|
|||||||
{pattern: "/a/{x}/b", path: "/a//b", match: false},
|
{pattern: "/a/{x}/b", path: "/a//b", match: false},
|
||||||
} {
|
} {
|
||||||
t.Run(test.pattern+"__"+test.path, func(t *testing.T) {
|
t.Run(test.pattern+"__"+test.path, func(t *testing.T) {
|
||||||
vals := make([]pathValue, 8)
|
vals := make([]PathValue, 8)
|
||||||
match, tooShort := SetPathValues(vals, test.pattern, []byte(test.path))
|
match, tooShort := SetPathValues(vals, test.pattern, []byte(test.path))
|
||||||
if tooShort {
|
if tooShort {
|
||||||
t.Fatal("8 slots must be enough for these patterns")
|
t.Fatal("8 slots must be enough for these patterns")
|
||||||
@@ -76,7 +76,7 @@ func TestSetPathValues(t *testing.T) {
|
|||||||
// exchange owns that memory and a copy would allocate per request.
|
// exchange owns that memory and a copy would allocate per request.
|
||||||
func TestSetPathValuesAliasesRequestBuffer(t *testing.T) {
|
func TestSetPathValuesAliasesRequestBuffer(t *testing.T) {
|
||||||
path := []byte("/users/42/edit")
|
path := []byte("/users/42/edit")
|
||||||
vals := make([]pathValue, 4)
|
vals := make([]PathValue, 4)
|
||||||
match, _ := SetPathValues(vals, "/users/{id}/edit", path)
|
match, _ := SetPathValues(vals, "/users/{id}/edit", path)
|
||||||
if !match {
|
if !match {
|
||||||
t.Fatal("want match")
|
t.Fatal("want match")
|
||||||
@@ -94,7 +94,7 @@ func TestSetPathValuesAliasesRequestBuffer(t *testing.T) {
|
|||||||
// A destination too small to hold every wildcard must say so rather than bind a
|
// A destination too small to hold every wildcard must say so rather than bind a
|
||||||
// partial set or write out of range.
|
// partial set or write out of range.
|
||||||
func TestSetPathValuesSliceTooShort(t *testing.T) {
|
func TestSetPathValuesSliceTooShort(t *testing.T) {
|
||||||
match, tooShort := SetPathValues(make([]pathValue, 1), "/{a}/{b}", []byte("/x/y"))
|
match, tooShort := SetPathValues(make([]PathValue, 1), "/{a}/{b}", []byte("/x/y"))
|
||||||
if !tooShort {
|
if !tooShort {
|
||||||
t.Error("want pathValSliceTooShort for 2 wildcards in 1 slot")
|
t.Error("want pathValSliceTooShort for 2 wildcards in 1 slot")
|
||||||
}
|
}
|
||||||
@@ -112,11 +112,11 @@ func TestSetPathValuesSliceTooShort(t *testing.T) {
|
|||||||
// segment, so "/users/x%2Fy" binds id="x/y" there and id="x%2Fy" here. Matching
|
// segment, so "/users/x%2Fy" binds id="x/y" there and id="x%2Fy" here. Matching
|
||||||
// agrees either way; only the bound bytes differ.
|
// agrees either way; only the bound bytes differ.
|
||||||
func TestSetPathValuesEscaping(t *testing.T) {
|
func TestSetPathValuesEscaping(t *testing.T) {
|
||||||
vals := make([]pathValue, 4)
|
vals := make([]PathValue, 4)
|
||||||
if match, _ := SetPathValues(vals, "/a%2Fb/{x}", []byte("/a%2Fb/v")); !match {
|
if match, _ := SetPathValues(vals, "/a%2Fb/{x}", []byte("/a%2Fb/v")); !match {
|
||||||
t.Error("want literal escape in pattern to match the same bytes in path")
|
t.Error("want literal escape in pattern to match the same bytes in path")
|
||||||
}
|
}
|
||||||
vals = make([]pathValue, 4)
|
vals = make([]PathValue, 4)
|
||||||
match, _ := SetPathValues(vals, "/users/{id}", []byte("/users/x%2Fy"))
|
match, _ := SetPathValues(vals, "/users/{id}", []byte("/users/x%2Fy"))
|
||||||
if !match {
|
if !match {
|
||||||
t.Fatal("want match")
|
t.Fatal("want match")
|
||||||
@@ -128,7 +128,7 @@ func TestSetPathValuesEscaping(t *testing.T) {
|
|||||||
|
|
||||||
// renderPathValues joins the bound pairs for comparison, stopping at the first
|
// renderPathValues joins the bound pairs for comparison, stopping at the first
|
||||||
// unused slot.
|
// unused slot.
|
||||||
func renderPathValues(vals []pathValue) string {
|
func renderPathValues(vals []PathValue) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
for _, v := range vals {
|
for _, v := range vals {
|
||||||
if v.Key == "" {
|
if v.Key == "" {
|
||||||
@@ -147,7 +147,7 @@ func renderPathValues(vals []pathValue) string {
|
|||||||
// Matching a request must not allocate: keys alias the mux's pattern and values
|
// Matching a request must not allocate: keys alias the mux's pattern and values
|
||||||
// alias the request buffer, so nothing is copied per request.
|
// alias the request buffer, so nothing is copied per request.
|
||||||
func TestSetPathValuesNoAlloc(t *testing.T) {
|
func TestSetPathValuesNoAlloc(t *testing.T) {
|
||||||
vals := make([]pathValue, 8)
|
vals := make([]PathValue, 8)
|
||||||
path := []byte("/b/bk/o/a/b/c")
|
path := []byte("/b/bk/o/a/b/c")
|
||||||
allocs := testing.AllocsPerRun(100, func() {
|
allocs := testing.AllocsPerRun(100, func() {
|
||||||
SetPathValues(vals, "/b/{bucket}/o/{obj...}", path)
|
SetPathValues(vals, "/b/{bucket}/o/{obj...}", path)
|
||||||
@@ -164,7 +164,11 @@ type pathValueMux struct {
|
|||||||
handler HandlerFunc
|
handler HandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *pathValueMux) LookupHandler(method Method, path []byte, dst []pathValue) (string, HandlerFunc) {
|
func (m *pathValueMux) MaxPathValues() int {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *pathValueMux) LookupHandler(method Method, path []byte, dst []PathValue) (string, HandlerFunc) {
|
||||||
if ok, _ := SetPathValues(dst, m.pattern, path); ok {
|
if ok, _ := SetPathValues(dst, m.pattern, path); ok {
|
||||||
return m.pattern, m.handler
|
return m.pattern, m.handler
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,8 +84,6 @@ type RouterConfig struct {
|
|||||||
ResponseHeaderMinBufferSize int
|
ResponseHeaderMinBufferSize int
|
||||||
// Number of request header key/value pairs to parse before failing and returning [StatusRequestHeaderFieldsTooLarge].
|
// Number of request header key/value pairs to parse before failing and returning [StatusRequestHeaderFieldsTooLarge].
|
||||||
RequestNumHeaderKVCap int
|
RequestNumHeaderKVCap int
|
||||||
// Sets maximum number of PathValue pairs that can be set on an exchange. Accessed via [Exchange.PathValue].
|
|
||||||
MaxPathValues int
|
|
||||||
|
|
||||||
// NormalizeOutgoingKeys normalizes response header field keys as they are
|
// NormalizeOutgoingKeys normalizes response header field keys as they are
|
||||||
// staged, i.e: "content-type" becomes "Content-Type".
|
// staged, i.e: "content-type" becomes "Content-Type".
|
||||||
@@ -186,7 +184,7 @@ func (r *Router) Configure(cfg RouterConfig) error {
|
|||||||
r.respBuf = cfg.ResponseHeaderMinBufferSize
|
r.respBuf = cfg.ResponseHeaderMinBufferSize
|
||||||
r.mux = cfg.Mux
|
r.mux = cfg.Mux
|
||||||
r.log = cfg.Logger
|
r.log = cfg.Logger
|
||||||
r.maxPathValues = cfg.MaxPathValues
|
r.maxPathValues = cfg.Mux.MaxPathValues()
|
||||||
r.normalizeKeys = cfg.NormalizeOutgoingKeys
|
r.normalizeKeys = cfg.NormalizeOutgoingKeys
|
||||||
// Freelist entries were sized by the outgoing configuration: recycling one
|
// Freelist entries were sized by the outgoing configuration: recycling one
|
||||||
// would serve a request with buffer limits cfg never asked for.
|
// would serve a request with buffer limits cfg never asked for.
|
||||||
@@ -210,6 +208,7 @@ func (r *Router) Configure(cfg RouterConfig) error {
|
|||||||
r.exchs = r.exchs[:numgoro]
|
r.exchs = r.exchs[:numgoro]
|
||||||
rawBuflen := cfg.RequestHeaderBufferSize + cfg.ResponseHeaderMinBufferSize
|
rawBuflen := cfg.RequestHeaderBufferSize + cfg.ResponseHeaderMinBufferSize
|
||||||
internal.SliceReuse(&r.globbuf, numgoro*rawBuflen)
|
internal.SliceReuse(&r.globbuf, numgoro*rawBuflen)
|
||||||
|
maxPathValues := cfg.Mux.MaxPathValues()
|
||||||
for i := range numgoro {
|
for i := range numgoro {
|
||||||
// TODO exchange buffer alloc
|
// TODO exchange buffer alloc
|
||||||
goff := i * rawBuflen
|
goff := i * rawBuflen
|
||||||
@@ -220,7 +219,7 @@ func (r *Router) Configure(cfg RouterConfig) error {
|
|||||||
NumHeaderKVCap: cfg.RequestNumHeaderKVCap,
|
NumHeaderKVCap: cfg.RequestNumHeaderKVCap,
|
||||||
NormalizeOutgoingKeys: cfg.NormalizeOutgoingKeys,
|
NormalizeOutgoingKeys: cfg.NormalizeOutgoingKeys,
|
||||||
NoRequestBufferGrowth: true, // Hard memory limit.
|
NoRequestBufferGrowth: true, // Hard memory limit.
|
||||||
MaxPathValues: cfg.MaxPathValues,
|
MaxPathValues: maxPathValues,
|
||||||
})
|
})
|
||||||
go r.goroWorker(gen, jobqueue, cfg.Mux)
|
go r.goroWorker(gen, jobqueue, cfg.Mux)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,8 +196,10 @@ func TestRouterRequestVisibleToHandler(t *testing.T) {
|
|||||||
router Router
|
router Router
|
||||||
)
|
)
|
||||||
var gotMethod, gotURI, gotHost string
|
var gotMethod, gotURI, gotHost string
|
||||||
|
var gotMethodEnum Method
|
||||||
sm.Handle("GET /index.html", func(ex *Exchange) {
|
sm.Handle("GET /index.html", func(ex *Exchange) {
|
||||||
gotMethod = string(ex.RequestMethod())
|
gotMethod = string(ex.RequestMethodRaw())
|
||||||
|
gotMethodEnum = ex.RequestMethod()
|
||||||
gotURI = string(ex.RequestTarget())
|
gotURI = string(ex.RequestTarget())
|
||||||
gotHost = string(ex.RequestHeader("Host"))
|
gotHost = string(ex.RequestHeader("Host"))
|
||||||
ex.WriteHeader(200)
|
ex.WriteHeader(200)
|
||||||
@@ -213,6 +215,9 @@ func TestRouterRequestVisibleToHandler(t *testing.T) {
|
|||||||
if gotMethod != "GET" {
|
if gotMethod != "GET" {
|
||||||
t.Errorf("want method %q, got %q", "GET", gotMethod)
|
t.Errorf("want method %q, got %q", "GET", gotMethod)
|
||||||
}
|
}
|
||||||
|
if gotMethodEnum != MethGet {
|
||||||
|
t.Errorf("want method enum %q, got %q", MethGet, gotMethodEnum)
|
||||||
|
}
|
||||||
if gotURI != "/index.html" {
|
if gotURI != "/index.html" {
|
||||||
t.Errorf("want URI %q, got %q", "/index.html", gotURI)
|
t.Errorf("want URI %q, got %q", "/index.html", gotURI)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,17 +285,17 @@ func (kvb *kvBuffer) getIdx(key string) int {
|
|||||||
|
|
||||||
func (kvb *kvBuffer) getFoldIdx(key string) int {
|
func (kvb *kvBuffer) getFoldIdx(key string) int {
|
||||||
for i, pair := range kvb.kvs {
|
for i, pair := range kvb.kvs {
|
||||||
if pair.isValid() && asciiEqualFold(key, b2s(kvb.AtKey(i))) {
|
if pair.isValid() && EqualFoldASCII(key, b2s(kvb.AtKey(i))) {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// asciiEqualFold reports whether a and b are equal under ASCII case folding.
|
// EqualFoldASCII reports whether a and b are equal under ASCII case folding.
|
||||||
// Unlike strings.EqualFold it does not fold non-ASCII runes, so no multi-byte
|
// Unlike strings.EqualFold it does not fold non-ASCII runes, so no multi-byte
|
||||||
// rune such as U+212A KELVIN SIGN can alias a header key.
|
// rune such as U+212A KELVIN SIGN can alias a header key.
|
||||||
func asciiEqualFold(a, b string) bool {
|
func EqualFoldASCII(a, b string) bool {
|
||||||
if len(a) != len(b) {
|
if len(a) != len(b) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user