http

package module
v0.3.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: Apache-2.0, MIT Imports: 16 Imported by: 0

README

zap-http

Docs: HTTP over ZAP · part of the ZAP Protocol

HTTP request/response semantics over the ZAP transport.

zap-proto.io · Spec · Paper · Discord

Drop-in replacement for net/http server and client when both peers live in a trusted boundary (in-cluster service mesh, agent-to-tool, edge-to-edge). Existing handlers and http.Client code work unchanged — only the wire underneath changes.

Why

Property net/http over TCP+TLS zap-http
Confidentiality TLS (classical curves) X-Wing hybrid PQ (X25519 + ML-KEM-768)
Authentication bearer / JWT at app layer KEM keypair at transport layer
Wire encoding text headers + chunked body ZAP wire, zero-copy
Field access parse → allocate → copy pointer offset, O(1)
JWT mint per call typical not in the path

Install

go get github.com/zap-proto/http

Requires Go 1.23+.

Server

package main

import (
    "io"
    "net/http"

    zaphttp "github.com/zap-proto/http"
)

func main() {
    http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "ok")
    })
    if err := zaphttp.ListenAndServe(":9999", nil); err != nil {
        panic(err)
    }
}

Same http.Handler, same http.HandleFunc, same default mux. The wire is ZAP-HTTP.

Client

package main

import (
    "io"
    "net/http"
    "os"

    zaphttp "github.com/zap-proto/http"
)

func main() {
    client := &http.Client{Transport: zaphttp.NewTransport("server:9999")}
    resp, err := client.Get("http://server/healthz")
    if err != nil { panic(err) }
    defer resp.Body.Close()
    io.Copy(os.Stdout, resp.Body)
}

http.Client machinery — timeouts, redirects, cookies — keeps working unchanged.

Wire format

Each HTTP message is one ZAP frame, defined in schema/http.zap using the ZAP schema language. Today the wire layer is length-prefixed framing over TCP; the paper and the v0.2 release will swap that for the full ZAP transport with X-Wing PQ KEM handshake on connect.

zap-http v0.1 wire (transitional):
  +---------+-------------------+
  | u32 BE  |   ZAP Frame       |
  | length  |   (http.zap)  |
  +---------+-------------------+

zap-http v0.2 wire:
  +-----------------------------+--------+-----------+
  | ZAP transport (X-Wing KEM,  | AEAD   | ZAP Frame |
  | mutual auth, multi-stream)  | header |           |
  +-----------------------------+--------+-----------+

The .zap schema is the source of truth for what's on the wire. Marshal/unmarshal in any language follows from the schema; bindings:

What's in v0.1

Feature Status
Request method, target, headers, body
Response status, reason, headers, body
Multi-value headers preserved
Trailers (read & write)
http.Client / http.Handler compatibility
Length-prefixed framing over TCP
64 MiB max frame size (configurable)
Streaming bodies (chunked-style) v0.2
Connection pool / keep-alive on client v0.2
Real ZAP transport (PQ KEM handshake) v0.2
WebSocket-style upgrade see zap-proto/ws

Sub-protocol family

  • zap-http — this repo
  • zap-ws — multi-stream pubsub, per-stream FEC
  • zap-fix — FIX 4.4/5.0 trading channel
  • zap-rns — KEM-bound service naming
  • zap-mcp — Model Context Protocol over ZAP
  • zap-acp — Agent Communication Protocol
  • zap-a2a — Google Agent2Agent over ZAP

By the composability theorem, every sub-protocol that embeds onto ZAP-base inherits its post-quantum confidentiality and mutual authentication automatically.

Schema regeneration

Requires zapc (the ZAP schema compiler) on PATH. Build it once from zap-proto/spec:

cargo install --path .   # from a clone of zap-proto/spec

License

MIT OR Apache-2.0

Documentation

Index

Constants

View Source
const (
	FrameRequest  uint16 = 0x01
	FrameResponse uint16 = 0x02
	// Streaming response frames. A streamed response is a FrameResponseHead
	// (status + headers, no body) followed by zero or more FrameData chunks and
	// a terminating FrameEnd (optional trailers). This is how server→client
	// push (SSE, MCP notifications, chunked bodies) rides ZAP — the analogue of
	// HTTP/2 HEADERS + DATA + END_STREAM. The non-streaming FrameResponse path
	// is unchanged, so existing peers interoperate.
	FrameResponseHead uint16 = 0x03
	FrameData         uint16 = 0x04
	FrameEnd          uint16 = 0x05
)

Frame type IDs. The ZAP message header carries the type in the upper byte of the 16-bit flags field; the encoder tags the message with type<<8 and flags>>8 recovers it. A request frame and a response frame are the two shapes the non-streaming wire carries.

View Source
const MaxFrameSize = 64 << 20 // 64 MiB

MaxFrameSize bounds an inbound frame. The wire format leaves room for larger frames; the limit here defends a server against a malicious peer announcing a multi-gigabyte length prefix.

Variables

This section is empty.

Functions

func Adapt added in v0.3.7

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.

func AppendRequest added in v0.2.1

func AppendRequest(dst []byte, req *fasthttp.Request) ([]byte, error)

AppendRequest appends a request frame to dst and returns the extended slice. Passing a reused (len-0) buffer makes the steady-state marshal zero-alloc; MarshalRequest is the dst==nil convenience.

func AppendResponse added in v0.2.1

func AppendResponse(dst []byte, resp *fasthttp.Response) ([]byte, error)

AppendResponse appends a response frame to dst and returns the extended slice. See AppendRequest.

func DataChunkOf added in v0.2.0

func DataChunkOf(frame []byte) ([]byte, error)

DataChunkOf extracts the chunk bytes from a FrameData frame. The returned slice aliases the frame buffer; copy it to retain past the frame's lifetime.

func FrameTypeOf added in v0.2.0

func FrameTypeOf(frame []byte) (uint16, error)

FrameTypeOf peeks a frame's type from its ZAP header without decoding the body, so a reader can dispatch (response vs streamed head vs data vs end).

func Get

func Get(addr, path string) (*fasthttp.Response, error)

Get is a convenience for one-shot service-to-service calls. The caller owns resp and must fasthttp.ReleaseResponse it when done.

func ListenAndServe

func ListenAndServe(addr string, h Handler) error

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.

func MarshalData added in v0.2.0

func MarshalData(chunk []byte) []byte

MarshalData wraps one body chunk as a FrameData frame.

func MarshalEnd added in v0.2.0

func MarshalEnd(trailer []byte) []byte

MarshalEnd terminates a stream, carrying optional encoded trailers.

func MarshalRequest

func MarshalRequest(req *fasthttp.Request) ([]byte, error)

MarshalRequest serializes a *fasthttp.Request into a ZAP frame. The returned bytes are the ZAP message; the transport layer prepends the length prefix (see transport.go). It is the dst==nil convenience for AppendRequest — hot callers append into a reused buffer to stay allocation-free.

func MarshalResponse

func MarshalResponse(resp *fasthttp.Response) ([]byte, error)

MarshalResponse serializes a *fasthttp.Response into a ZAP frame. See MarshalRequest; AppendResponse is the reused-buffer form.

func MarshalResponseHead added in v0.2.0

func MarshalResponseHead(resp *fasthttp.Response) ([]byte, error)

MarshalResponseHead serializes a response's status + headers (NO body) as the opening frame of a stream. The body is delivered by subsequent FrameData frames.

func UnmarshalRequest

func UnmarshalRequest(frame []byte, dst *fasthttp.Request) error

UnmarshalRequest reconstructs a request into dst from a ZAP frame. dst is reset first, then populated: method, request-URI, protocol, headers, trailers, and body. Content-Length is derived from the body length by SetBody; Host comes from a Host header if the frame carried one.

func UnmarshalResponse

func UnmarshalResponse(frame []byte, dst *fasthttp.Response) error

UnmarshalResponse reconstructs a response into dst from a ZAP frame.

func UnmarshalResponseHead added in v0.2.0

func UnmarshalResponseHead(frame []byte, dst *fasthttp.Response) error

UnmarshalResponseHead applies a streamed response's status + headers to dst. The body is left empty; the caller attaches a streaming body that reads the following FrameData frames.

Types

type Handler added in v0.3.7

type Handler = http.Handler

The standard shapes, unchanged. These are net/http's own types.

type HandlerFunc added in v0.3.7

type HandlerFunc = http.HandlerFunc

The standard shapes, unchanged. These are net/http's own types.

type Header = http.Header

The standard shapes, unchanged. These are net/http's own types.

type Request added in v0.3.7

type Request = http.Request

The standard shapes, unchanged. These are net/http's own types.

type Response added in v0.3.7

type Response = http.Response

The standard shapes, unchanged. These are net/http's own types.

type ResponseWriter added in v0.3.7

type ResponseWriter = http.ResponseWriter

The standard shapes, unchanged. These are net/http's own types.

type ServeMux added in v0.3.7

type ServeMux = http.ServeMux

The standard shapes, unchanged. These are net/http's own types.

func NewServeMux added in v0.3.7

func NewServeMux() *ServeMux

NewServeMux returns net/http's mux, so routing composes as usual.

type Server

type Server struct {
	Addr string // ":9999" if empty
	// Network mirrors net.Listen's first argument: "tcp" when empty, or
	// "unix" to serve the same ZAP frames on a socket path given in Addr.
	// The wire is identical either way; only the plumbing differs.
	Network      string
	Handler      fasthttp.RequestHandler // required
	ReadTimeout  time.Duration           // 0 means no timeout
	WriteTimeout time.Duration
	IdleTimeout  time.Duration
	Logger       fasthttp.Logger // passed to each RequestCtx; nil is fine
	// contains filtered or unexported fields
}

Server is a ZAP-HTTP server. Zero-value is usable; common knobs (Addr, Handler, ReadTimeout, …) mirror fasthttp.Server.

func (*Server) Close

func (s *Server) Close() error

Close stops the listener; in-flight handlers are not interrupted.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe() error

ListenAndServe binds Addr and serves until Close is called or a fatal accept error occurs.

func (*Server) Serve

func (s *Server) Serve(ln net.Listener) error

Serve accepts connections on ln and serves each one in its own goroutine. Returns when the listener is closed.

type Transport

type Transport struct {
	// contains filtered or unexported fields
}

Transport speaks ZAP over a pooled connection. Field-zero is invalid; use Dial.

func Dial added in v0.2.2

func Dial(network, addr string) *Transport

Dial returns a Transport that speaks ZAP to addr over network, mirroring net.Dial: the network is a value ("tcp", "unix", …), not a family of functions. A unix address is a socket path and carries the same ZAP frames as tcp — the wire does not change with the network.

http.Dial("tcp", "billing.hanzo.svc:9653")
http.Dial("unix", "/run/hanzo/billing.sock")

Dialing is lazy; the first request opens the connection.

func (*Transport) CloseIdleConnections

func (t *Transport) CloseIdleConnections()

CloseIdleConnections closes every idle conn in the pool. Active requests are unaffected. Useful in tests and on shutdown.

func (*Transport) Do

func (t *Transport) Do(req *fasthttp.Request, resp *fasthttp.Response) error

Do executes a single request/response exchange, filling resp. It is safe for concurrent use: each call takes its own connection from the pool.

func (*Transport) SetDialTimeout

func (t *Transport) SetDialTimeout(d time.Duration)

SetDialTimeout overrides the default 10s dial timeout.

func (*Transport) SetMaxIdleConns

func (t *Transport) SetMaxIdleConns(n int)

SetMaxIdleConns caps the number of idle conns held in the pool. Surplus conns close on return.

func (*Transport) SetReadTimeout

func (t *Transport) SetReadTimeout(d time.Duration)

SetReadTimeout overrides the default 30s response-read timeout.

Directories

Path Synopsis
examples
hello command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL