diff --git a/http/httphi/exchange.go b/http/httphi/exchange.go index 22ceba9..0dd2fda 100644 --- a/http/httphi/exchange.go +++ b/http/httphi/exchange.go @@ -38,6 +38,8 @@ type Exchange struct { hijacked bool rw conn + matchedPattern string + respRemains int respErr error // Sticky: response is unrecoverable once a write fails. headerWritten bool @@ -97,6 +99,7 @@ func (exch *Exchange) Acquire(conn conn) bool { if !exch.used.CompareAndSwap(false, true) { return false } + exch.matchedPattern = "" exch.gen.Add(1) exch.readErr = nil exch.respErr = nil @@ -366,6 +369,11 @@ func (exch *Exchange) remainingSurplusBody() ([]byte, error) { return toRead, nil } +// MuxPattern returns the pattern [Mux] matched to the request. +func (exch *Exchange) MuxPattern() string { + 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. diff --git a/http/httphi/exchange_test.go b/http/httphi/exchange_test.go index 81913dc..8889fe3 100644 --- a/http/httphi/exchange_test.go +++ b/http/httphi/exchange_test.go @@ -4,7 +4,9 @@ import ( "context" "errors" "io" + "net/http" "strings" + "unsafe" "testing" "time" @@ -792,6 +794,7 @@ func serveMultipart(t *testing.T, request string, bufSize int, skip string, segm } parts, gotErr = exch.ReadMultiparts(parts, make([]byte, bufSize), newSink) }) + const x = unsafe.Sizeof(http.Request{}) exch := newExchange(t, conn, 1024, false) if err := Handle(exch, &sm, nopBackoff); err != nil { t.Fatal(err) diff --git a/http/httphi/mux.go b/http/httphi/mux.go index 8f4409d..0f1a09d 100644 --- a/http/httphi/mux.go +++ b/http/httphi/mux.go @@ -49,8 +49,9 @@ func Handle(exch *Exchange, mux Mux, backoff lneto.BackoffStrategy) error { // Mux on the request path: the query string is the handler's business. path := reqhdr.RequestPath() meth := reqhdr.Method() - handler := mux.LookupHandler(MethodFromBytes(meth), b2s(path)) + matchedPattern, handler := mux.LookupHandler(MethodFromBytes(meth), b2s(path)) if handler != nil { + exch.matchedPattern = matchedPattern handler(exch) exch.FlushHeader() } else { @@ -70,7 +71,9 @@ type HandlerFunc func(ex *Exchange) // LookupHandler with the request-target's path, not the whole target, and // replies 404 when it returns nil. type Mux interface { - LookupHandler(get Method, requestPath string) HandlerFunc + // LookupHandler matches the requestPath and method to a handler and returns it and the + // pattern it matched. + LookupHandler(get Method, requestPath string) (matchedPattern string, handler HandlerFunc) } // MuxSlice is a [Mux] backed by a slice of registered endpoints, matched by @@ -92,17 +95,17 @@ func (sm *MuxSlice) Reset(capacity int) { // LookupHandler returns the handler registered for request path, or nil if none matches. // The first registration matching both method and uri wins. -func (sm *MuxSlice) LookupHandler(method Method, path string) HandlerFunc { +func (sm *MuxSlice) LookupHandler(method Method, path string) (matched string, _ HandlerFunc) { for _, endpoint := range sm._handlers { if endpoint.method != MethUndefined && endpoint.method != method { continue } // Method matches. if path == endpoint.path { - return endpoint.handler + return endpoint.path, endpoint.handler } } - return nil + return "", nil } // Handle registers handler for reg, either a bare path matching any method or a