poseidon-http-client

module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 12, 2026 License: MIT

README

poseidon-http-client

A low-level, zero-allocation HTTP/2 client for Go, designed for load generators. Implements RFC 7540 (HTTP/2) and RFC 7541 (HPACK) from scratch without net/http or golang.org/x/net/http2.

Status: Phase D.5 — HTTP request trailers. Request.Trailers / Request.TrailerFunc send trailer HEADERS+END_STREAM after body DATA frames (RFC 7540 §8.1.3); StreamResponse.WaitTrailers convenience method for response trailer polling. See C.1 design, C.2 design, C.3/C.4 design, D.1 design, D.2 design, D.3 design, D.4 design, D.5 design.

Phases

  • A — Frame layer + HPACK (released): codec only, no networking.
  • B.1 — Connection layer (single stream) (released): TLS+ALPN dial, SETTINGS handshake, one in-flight stream end-to-end against net/http2.Server.
  • B.2.1 — Multi-stream foundation (released): configurable MaxConcurrentStreams, deferred stream-id allocation under the writer mutex (RFC 7540 §5.1.1 monotonic ordering).
  • B.2.2 — Flow control IN (released): per-stream + connection recv windows, batched WINDOW_UPDATE refunds at 32 KiB threshold, typed FLOW_CONTROL_ERROR on peer overrun (RFC 7540 §6.9.1).
  • B.2.3 — Flow control OUT (released): chunked DATA writes at min(peer MAX_FRAME_SIZE, our advertised MAX_FRAME_SIZE), blocking acquireSendCredits until per-stream + connection send-window credit, OnWindowUpdate replenishment, 2^31-1 overflow as typed StreamError / ConnError.
  • B.2.4 — Dynamic SETTINGS (released): connHandler.OnSettings merges non-ACK frames into c.peerSettings, applies side effects (HPACK encoder resize, retroactive INITIAL_WINDOW_SIZE delta on every open stream — RFC §6.9.2), and emits a SETTINGS ACK (RFC §6.5.3).
  • B.2.5 — Peer-advertised MAX_CONCURRENT_STREAMS (released): NewStream gates inflight on min(local advertised, peer-advertised); dynamic shrinks refuse new streams without disturbing open ones (RFC §6.5.2).
  • B.2.6 — GOAWAY drain + PING ACK (released): peer GOAWAY records state on *Conn, drains streams above lastStreamID with EventReset(REFUSED_STREAM), blocks new NewStream with ErrGoAway, wakes writers stuck on send credit (RFC §6.8); inbound non-ACK PING echoes back with ACK=1 and the same 8-byte payload (RFC §6.7).
  • C.1 — Public client API (released): client.Client, Request, Response, sync Do and streaming DoStream, single-connection transport with conn-level auto-redial, (*conn.Conn).IsAlive helper for transport reuse decisions.
  • C.2 — connection pool (released): per-host Pool with lazy-grow, least-loaded stream selection, idle-timeout eviction, GOAWAY-aware drain, dial backoff, and MAX_CONCURRENT_STREAMS enforcement (RFC 7540 §5.1.2, §6.8). Enable via ClientOptions{Transport: client.TransportPool}.
  • C.3 — service discovery (released): per-address sub-pool fan-out via Resolver + Selector. Built-in StaticResolver, DNSResolver with TTL cache + Watch, and RoundRobin / Random / Hash selectors. DrainGraceful / DrainHard / DrainLazy modes.
  • C.4 — metrics & hooks (released): lifecycle Hooks (OnRequestStart, OnRequestComplete, OnRetry, OnDial, OnConnClose, OnResolverUpdate); lock-free counters and log-bucket latency histograms exposed via (*Client).Metrics() and (*Client).MetricsSnapshot(). Zero hot-path overhead when ClientOptions.Hooks == nil.
  • D.1 — zero-alloc request path (released): caller-provided *Response/*StreamResponse eliminates per-call heap alloc; conn.HeaderSlabPool slab allocator for HPACK header bytes; per-Conn stream sync.Pool; encBufPool for HPACK encode buffers; hdrSlicePool + const name bytes in buildHeaders. 33 allocs/op, down from 49 (−33%).
  • D.2 — request/response body streaming (released): Request.ContentLength int64 emits content-length header; Request.StreamBody bool + Response.BodyReader io.ReadCloser stream response bodies without buffering (caller drains + Close()); uploadBufPool recycles upload read buffers; Response.Reset() closes BodyReader automatically. RFC 7540 §8.1 conformance test added.
  • D.3 — H2C (plaintext HTTP/2) (released): conn.PlaintextDialer for unencrypted TCP; H2C prior-knowledge handshake (RFC 7540 §3.4). Set ClientOptions.DefaultScheme = "http" for H2C targets.
  • D.4 — PING / keepalive (released): (*conn.Conn).Ping(ctx) measures round-trip time; background keepalive loop with ConnOptions.KeepaliveInterval + KeepaliveTimeout closes dead connections automatically (RFC 7540 §6.7).
  • D.5 — HTTP request trailers (released): Request.Trailers []hpack.HeaderField and Request.TrailerFunc send trailer HEADERS+END_STREAM after body DATA frames (RFC 7540 §8.1.3); StreamResponse.WaitTrailers(ctx) pumps events until trailers arrive or stream ends; trailerAnnouncement emits Trailer: header for Go net/http server compatibility.

Quick start

package main

import (
	"context"
	"crypto/tls"
	"fmt"

	"github.com/lodgvideon/poseidon-http-client/client"
	"github.com/lodgvideon/poseidon-http-client/conn"
)

func get(ctx context.Context, addr string) error {
	c, err := client.NewClient(client.ClientOptions{
		Addr: addr,
		ConnOpts: conn.ConnOptions{
			Dialer: &conn.TLSDialer{Config: &tls.Config{ServerName: "example.com"}},
		},
	})
	if err != nil {
		return err
	}
	defer c.Close()

	var res client.Response
	if err := c.Do(ctx, &client.Request{
		Method:   "GET",
		Path:     "/",
		WantBody: true,
	}, &res); err != nil {
		return err
	}
	fmt.Println("status:", res.Status, "bytes:", res.BytesReceived)
	return nil
}
Pool transport
package main

import (
	"context"
	"crypto/tls"
	"fmt"
	"time"

	"github.com/lodgvideon/poseidon-http-client/client"
	"github.com/lodgvideon/poseidon-http-client/conn"
)

func poolGet(ctx context.Context, addr string) error {
	c, err := client.NewClient(client.ClientOptions{
		Addr: addr,
		ConnOpts: conn.ConnOptions{
			Dialer: &conn.TLSDialer{Config: &tls.Config{ServerName: "example.com"}},
		},
		Transport: client.TransportPool,
		Pool: &client.PoolOptions{
			MaxConnsPerHost:   4,
			MaxStreamsPerConn: 100,
			IdleTimeout:       30 * time.Second,
		},
	})
	if err != nil {
		return err
	}
	defer c.Close()

	var res client.Response
	if err := c.Do(ctx, &client.Request{
		Method:   "GET",
		Path:     "/",
		WantBody: true,
	}, &res); err != nil {
		return err
	}
	fmt.Println("status:", res.Status, "bytes:", res.BytesReceived)
	return nil
}

For codec-only usage (no networking), the frame and hpack packages remain importable directly:

import (
    "github.com/lodgvideon/poseidon-http-client/frame"
    "github.com/lodgvideon/poseidon-http-client/hpack"
)

Limits and contracts

  • conn.Conn is goroutine-safe across NewStream / Close. Stream methods may be called from one goroutine at a time.
  • B.2.1 enforces a locally advertised MaxConcurrentStreams cap per Conn (default 100; returns ErrTooManyStreams if exceeded). Peer-advertised limit enforcement is B.2.5.
  • frame.Framer, hpack.Encoder, hpack.Decoder are NOT goroutine-safe — conn.Conn owns one of each per connection and serializes access internally.
  • Decoder / StreamEvent slice fields alias internal scratch buffers and are valid only until the next Recv/Close on the same stream. Copy if you must retain.
  • Codec hot path: 0 B/op and 0 allocs/op (bench gate enforced in CI). client steady state: D.1 reduced to 33 allocs/op (−33%).

Local development

Requirements: Go 1.24, golangci-lint v1.62 (optional).

make tidy
make lint
make test-race
make bench

Optional pre-commit hook:

git config core.hooksPath .githooks

See docs/BENCH_BASELINE.md for reference numbers and docs/COVERAGE.md for coverage policy.

License

MIT — see LICENSE.

Directories

Path Synopsis
Package client provides a high-level HTTP/2 client API on top of the conn package.
Package client provides a high-level HTTP/2 client API on top of the conn package.
Package conn implements the Phase B HTTP/2 connection layer on top of the Phase A frame and HPACK codecs.
Package conn implements the Phase B HTTP/2 connection layer on top of the Phase A frame and HPACK codecs.
Package frame implements the HTTP/2 framing layer (RFC 7540) without any networking.
Package frame implements the HTTP/2 framing layer (RFC 7540) without any networking.
Package hpack implements HPACK (RFC 7541) header compression for HTTP/2.
Package hpack implements HPACK (RFC 7541) header compression for HTTP/2.
internal
bytesx
Package bytesx provides private byte-level helpers used by the frame and hpack packages.
Package bytesx provides private byte-level helpers used by the frame and hpack packages.

Jump to

Keyboard shortcuts

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