The package is called http and now looks like it. Request, Response, Header,
Handler, HandlerFunc, ResponseWriter and ServeMux are ALIASES of net/http's own
types, so an existing handler satisfies them exactly and middleware, httptest
and mux compose for free. A declared struct with matching fields would be a
different type — every caller converting, every middleware failing — and drop-in
would be a claim rather than a fact.
ListenAndServe takes the standard Handler, and there is one of it. A second one
taking fasthttp.RequestHandler used to sit in server.go, so there were two ways
to start a server differing only in which handler shape you happened to hold. A
caller with a fasthttp handler composes &Server{Addr, Handler} — one line that
says what it is.
The cost is named rather than hidden: net/http's Request and ResponseWriter
allocate where fasthttp's do not, and this package guards a zero-alloc encode.
So the fast path is untouched — Server.Handler is still a fasthttp handler and
nothing in this file is in its way. Adapt is exported so the bridge is visible at
the call site, which is where a net/http Request gets materialised.
The example is an ordinary net/http handler now. Nothing in it knows about ZAP,
which is the whole demonstration.
Co-authored-by: Hanzo Dev <[email protected]>
Sixty error strings still opened with "zaphttp: " and the package comment still
declared Package zaphttp. An error names the package it came from, so a reader
grepping for the source of "zaphttp: truncated header block" would have found a
package that no longer exists under that name.
Co-authored-by: Hanzo Dev <[email protected]>
v0.3.3 renamed this to package zap, and that was wrong twice. Go resolves an
unaliased import to the package's own name, so github.com/zap-proto/http should
answer to http; and this package IMPORTS zap, the wire format, so naming itself
zap put two different things under one word in the same file. An importer that
also needs net/http aliases one of them, which is the ordinary convention rather
than a reason to invent a name.
zap.Version and zap.HeaderSize are that other package and are untouched.
Co-authored-by: Hanzo Dev <[email protected]>
zaphttp was a compound word invented to disambiguate a module whose path
already reads github.com/zap-proto/http. The package is the ZAP wire; the
module path says what it speaks. An importer that also needs net/http aliases
one of them, which is the ordinary Go convention rather than a reason to build
a compound name into every file.
The file that carried the name in its own filename is zap_test.go now, for the
same reason: a file is named for what it holds.
Co-authored-by: Hanzo Dev <[email protected]>
The wire was already ZAP binary on both sides; only the plumbing was pinned
to TCP (net.Listen("tcp"), net.DialTimeout("tcp")). Lifting that to a value
mirrors net.Dial/net.Listen — the network is an argument, not a family of
functions — and unix sockets fall out with no change to the frames:
zaphttp.Dial("unix", "/run/hanzo/billing.sock")
&zaphttp.Server{Network: "unix", Addr: "/run/hanzo/billing.sock"}
NewTransport is gone in favour of Dial: it was a compound name for what net
already calls dialing, and keeping both would be two ways to do one thing.
All callers migrated.
Serving on unix clears a stale socket first — a crashed predecessor leaves a
file that bind refuses, so restart-in-place needs it. A socket with something
live behind it still fails, loudly, rather than being unlinked out from under
a running process.
Co-authored-by: Hanzo Dev <[email protected]>
Hand-rolled frame encoder/decoder (codec.go) replaces the zap.Builder +
map[string][]string + json.Marshal path. Wire stays byte-identical (golden
tests + TestCodec_CrossCheck vs a zap.Builder oracle across a wide corpus,
incl. special-char headers). Headers are streamed straight into the frame tail
as compact sorted-key JSON, byte-identical to json.Marshal, with a stdlib
fallback for escape/UTF-8 values; decode scans the JSON in place into the
fasthttp store with a json.Unmarshal fallback.
Server: one RequestCtx per connection (Init2 + per-request Reset) instead of a
per-request heap alloc; reused frame read/write buffers; single-syscall
length-prefixed response write.
Client: per-conn reused request/response buffers, single frame-type peek + one
decode (was double zap.Parse), single-syscall request write.
Codec allocs/op (default GOGC):
MarshalRequest 11 -> 1 (Append: 0) MarshalResponse 19 -> 1 (Append: 0)
UnmarshalResponse 11 -> 0 hot Append/Unmarshal all 0 allocs/op
Adds multi-frame streamed responses so SSE / MCP notifications / progress /
chunked bodies ride ZAP as real server→client push, not a buffered blob. A
streamed response is a head frame (status+headers) + N data frames (one per
handler flush) + an end frame — the analogue of HTTP/2 HEADERS+DATA+END_STREAM.
- wire.go: FrameResponseHead/FrameData/FrameEnd + Marshal/Unmarshal + FrameTypeOf.
- server.go: when ctx.Response.IsBodyStream(), stream via BodyWriteTo→chunkWriter
(each flush = one FrameData frame on the wire immediately). Non-stream path
unchanged (single FrameResponse) — fully backward-compatible.
- client.go: Transport.Do dispatches on frame type; a head attaches a streaming
body (streamReader) that reads data frames until end, releasing the pooled conn
only when the stream drains (or closes it if abandoned mid-stream).
Verified: TestStream_SSEOverZAP flushes 3 events one-at-a-time and the client
receives each BEFORE the next is released (a buffering regression would deadlock/
time out — the guard proves real incremental push). All existing round-trip +
keep-alive tests pass; go test -race clean; gofmt clean.
Part 1 of the fasthttp unification (Fiber + ZAP both on fasthttp). Does NOT
yet build/verify; zap-proto/zip not yet created. Do not merge.
Co-authored-by: Hanzo Dev <[email protected]>
HTTP request/response semantics over the ZAP transport. Drop-in
net/http.Handler server and net/http.RoundTripper client; existing
handlers and http.Client code work unchanged.
Wire format: one Cap'n Proto frame per HTTP message
(schema/zap_http.capnp). v0.1 ships length-prefixed framing over TCP;
v0.2 will swap that for the full ZAP transport (X-Wing PQ KEM
handshake + AEAD framing) once zap-proto/go provides it. The
marshal/unmarshal API is stable across that change.
Includes:
- schema/zap_http.capnp (Cap'n Proto schema, source of truth)
- internal/capnp/ (generated Go bindings)
- wire.go (Marshal/Unmarshal Request/Response)
- transport.go (length-prefixed framing)
- server.go (Server + ListenAndServe)
- client.go (Transport + Get convenience)
- examples/hello (server + client in one binary)
- 7 tests covering GET, POST, large body (4 MiB), multi-value headers,
status code propagation, keep-alive, marshal idempotence
Tests pass; smoke test confirms end-to-end exchange works.