cut the commentary down to what the code cannot say

Half of net.go was me explaining the design at length. A reader needs two facts:
these are net/http's own types, and the net/http shapes allocate where fasthttp
does not. Everything else was restating the code above it.

63 lines to 39, same behaviour, same tests.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-08-10 17:12:51 -07:00
co-authored by Hanzo Dev
parent ecf51b7bf3
commit 9ae2c5831f
2 changed files with 12 additions and 40 deletions
+8 -32
View File
@@ -2,24 +2,8 @@
package http
// net.go — the net/http face of this package.
//
// A caller with net/http code changes its import and nothing else: the types
// below ARE net/http's, by alias, so an existing Handler, Request or
// ResponseWriter satisfies them exactly. Only the transport is different — the
// bytes leave as ZAP frames rather than as HTTP/1.1.
//
// WHY ALIASES AND NOT LOOKALIKES. A declared struct with the same fields is a
// DIFFERENT type: every caller would need a conversion, every middleware would
// stop compiling, and "drop-in" would be a claim rather than a fact. An alias
// is the same type, so it composes with the whole net/http ecosystem —
// httptest, middleware, mux — for free.
//
// WHAT IT COSTS, stated rather than hidden. net/http's Request and
// ResponseWriter allocate where fasthttp's do not, and this package guards a
// zero-alloc encode path. So the fast path stays fasthttp: Server.Handler is
// still a fasthttp.RequestHandler and nothing here is in its way. This file is
// the door for callers who want the standard shapes and will pay for them.
// The net/http face of this package: an existing handler runs unchanged, over
// ZAP frames instead of HTTP/1.1.
import (
"net/http"
@@ -28,7 +12,8 @@ import (
"github.com/valyala/fasthttp/fasthttpadaptor"
)
// The standard shapes, unchanged. These are net/http's own types.
// Aliases, not lookalikes — a struct with the same fields would be a different
// type, and every caller would need a conversion.
type (
Request = http.Request
Response = http.Response
@@ -39,25 +24,16 @@ type (
ServeMux = http.ServeMux
)
// NewServeMux returns net/http's mux, so routing composes as usual.
func NewServeMux() *ServeMux { return http.NewServeMux() }
// ListenAndServe serves a net/http Handler over ZAP, exactly as
// net/http.ListenAndServe serves one over HTTP/1.1.
//
// There is ONE of these, and it takes the standard Handler. A second one taking
// fasthttp.RequestHandler used to sit in server.go, which made two ways to start
// a server that differed only in which handler shape you happened to have. A
// caller holding a fasthttp handler composes &Server{Addr, Handler} directly —
// one line, and it says what it is.
// ListenAndServe serves h over ZAP. A caller holding a fasthttp handler builds
// &Server{Addr, Handler} instead.
func ListenAndServe(addr string, h Handler) error {
return (&Server{Addr: addr, Handler: Adapt(h)}).ListenAndServe()
}
// Adapt turns a net/http Handler into the fasthttp handler Server takes. It is
// exported because a caller composing a Server itself needs the same bridge,
// and because naming it makes the cost visible at the call site: this is where
// a net/http Request is materialised.
// Adapt bridges to the fasthttp handler Server takes. Exported so the cost is
// visible where it lands: a net/http Request allocates, fasthttp's does not.
func Adapt(h Handler) fasthttp.RequestHandler {
return fasthttpadaptor.NewFastHTTPHandler(h)
}
+4 -8
View File
@@ -7,10 +7,8 @@ import (
zap "github.com/zap-proto/http"
)
// A NET/HTTP HANDLER IS THIS PACKAGE'S HANDLER — the same type, not a lookalike.
// If these were separate declarations with matching fields, every caller would
// need a conversion and every middleware would stop compiling, and "drop-in"
// would be a claim instead of a fact. Assigning across proves the alias.
// The alias holds: a net/http Handler IS this package's Handler. Assigning
// across is the proof; matching fields would not be.
func TestStandardShapesAreTheStandardTypes(t *testing.T) {
var h zap.Handler = nethttp.HandlerFunc(func(w nethttp.ResponseWriter, r *nethttp.Request) {})
var back nethttp.Handler = h
@@ -18,17 +16,15 @@ func TestStandardShapesAreTheStandardTypes(t *testing.T) {
t.Fatal("a net/http Handler must BE this package's Handler")
}
// The mux composes, so routing written against net/http keeps working.
// So the mux composes.
var mux *nethttp.ServeMux = zap.NewServeMux()
mux.Handle("/x", h)
// And the request/response shapes are net/http's own.
var _ *nethttp.Request = (*zap.Request)(nil)
var _ nethttp.Header = zap.Header(nil)
}
// The bridge to the fast path exists and is named, so the cost is visible where
// a net/http Request is materialised rather than hidden inside the server.
// The bridge is named, so the allocation has a visible home.
func TestAdaptProducesTheFastHandler(t *testing.T) {
if zap.Adapt(nethttp.HandlerFunc(func(nethttp.ResponseWriter, *nethttp.Request) {})) == nil {
t.Fatal("Adapt returned no handler")