zaphttp

package module
v0.0.0-...-0c8b134 Latest Latest
Warning

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

Go to latest
Published: Jun 18, 2026 License: Apache-2.0, MIT Imports: 17 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/zap_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  |   (zap_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

make schema    # regenerates internal/wire/zap_http.go from schema/zap_http.zap

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
)

Frame type IDs. The ZAP message header carries the type in the upper byte of the 16-bit flags field; FinishWithFlags(t<<8) tags the message and Message.Flags()>>8 recovers it. A request frame and a response frame are the two shapes the wire carries today.

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 Get

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

Get is a convenience for transports that don't need an http.Client wrapper (e.g. service-to-service calls inside a cluster).

func ListenAndServe

func ListenAndServe(addr string, handler http.Handler) error

ListenAndServe is the convenience equivalent of net/http.ListenAndServe.

func MarshalRequest

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

MarshalRequest serializes an *http.Request into a ZAP frame. The returned bytes are framed and ready for transport.Write — the transport layer prepends the length prefix.

The body is fully read and consumed; callers that need to reuse the request must replace req.Body with a fresh reader after this call.

func MarshalResponse

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

MarshalResponse serializes an *http.Response into a ZAP frame.

func UnmarshalRequest

func UnmarshalRequest(b []byte) (*http.Request, error)

UnmarshalRequest reconstructs an *http.Request from a ZAP frame. The returned request has Body set to a bytes.Reader over the frame's body bytes; the request URL is parsed from target. Host is taken from the request's Host header (RFC 9110 §7.2).

func UnmarshalResponse

func UnmarshalResponse(b []byte) (*http.Response, error)

UnmarshalResponse reconstructs an *http.Response from a ZAP frame.

Types

type Server

type Server struct {
	Addr           string        // ":9999" if empty
	Handler        http.Handler  // http.DefaultServeMux if nil
	ReadTimeout    time.Duration // 0 means no timeout
	WriteTimeout   time.Duration
	IdleTimeout    time.Duration
	MaxHeaderBytes int // not enforced today; placeholder for parity
	// contains filtered or unexported fields
}

Server is a ZAP-HTTP server. Zero-value is usable; common knobs (Addr, Handler, ReadTimeout, …) mirror net/http.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 implements http.RoundTripper over a ZAP-HTTP TCP connection. Field-zero is invalid; use NewTransport.

func NewTransport

func NewTransport(addr string) *Transport

NewTransport returns a Transport connecting to the given host:port.

func (*Transport) CloseIdleConnections

func (t *Transport) CloseIdleConnections()

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

func (*Transport) RoundTrip

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip executes a single request/response exchange.

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