Skip to content

Compatibility

cl-http-kit focuses on reusable HTTP/1.x client and server framing with optional HTTP/2 connection/session support, HTTP/3 request-stream framing, WebSocket sessions, and native TCP/DNS integration. This page describes the behavior implemented by the registered systems; it is not a claim of complete RFC coverage.

The registered systems in this checkout are version 0.4.1. The current integration source has native default client wiring, proxy-environment parsing, lock-backed pool/cookie state, and four-kit dependency declarations. The URL-and-method convenience form is available through http-client-send. The TLS wrapper requires cl-tls-kit 0.1.2 or later for the native TLS 1.3 client path; server wrapping remains an explicit unsupported boundary because the kit does not expose a server driver.

HTTP/1.1

The core serializer returns an octet vector containing HTTP/1.1 request lines and headers. The application transport writes that vector to a binary stream. The serializer validates method and header syntax, derives Host from the URI, checks Content-Length against the body, and rejects request transfer encoding that the core cannot safely construct.

The response parser supports:

  • informational responses and the terminal 101 protocol-switch response, leaving upgraded bytes unread;
  • fixed-length bodies with exact byte accounting;
  • chunked bodies and trailers;
  • close-delimited bodies;
  • no-body rules for HEAD responses and the relevant status codes;
  • header and body byte limits.

HEAD responses preserve Content-Length metadata without reading a body, including for status 205. Other 205 responses are bodyless and reject a nonzero Content-Length. Chunked payload bytes count toward the body limit; chunk-size lines, chunk extensions, delimiters, and trailers count toward the header limit separately.

Conflicting transfer framing, malformed chunk data, invalid status lines, and truncated fixed-length bodies raise public conditions.

serve-http1-session serves requests on an already-open binary stream. It supports request-body limits and streaming callbacks, Expect: 100-continue, trailers, response streams, protocol upgrades, and bounded request counts. The listener and any TLS or protocol-selection layer remain application-owned, except for the optional native HTTP/1 listener service.

HTTP/2

The optional cl-http-kit/http2 system accepts an injected frame exchange or binary stream. It validates frame size and header and body limits, then exposes the same request and response model as the core system where the boundary supports it.

The HTTP/2 connection API supports reusable, owner-thread connections, stream-id allocation, request multiplexing within a batch, flow-controlled request and response bodies, GOAWAY/draining state, and an optional http2-connection-manager with origin-keyed reuse, an LRU connection cap, and optional idle expiry and maximum connection lifetime. The manager can retry once on a new connection when a peer explicitly reports that an unstreamed request was not processed through GOAWAY or REFUSED_STREAM; body producers are never replayed automatically. The manager is cooperative and callback-driven: the application still provides the stream, socket, TLS, ALPN, proxy negotiation, and broader retry policy. The peer must send a non-ACK SETTINGS frame first. RFC 8441 Extended CONNECT is supported when the peer advertises SETTINGS_ENABLE_CONNECT_PROTOCOL; HTTP/1.1-style 101 Switching Protocols is not valid on HTTP/2. The client advertises SETTINGS_ENABLE_PUSH=0; a subsequent PUSH_PROMISE is rejected as PROTOCOL_ERROR. Unknown extension frames are ignored as permitted by HTTP/2. Unsupported features are reported with HTTP-UNSUPPORTED-FEATURE.

serve-http2-session provides the corresponding server-side session boundary over a caller-supplied binary stream. It handles the HTTP/2 preface, SETTINGS, HPACK, stream state, flow control, request bodies, response framing, and session limits; it does not accept sockets or provide TLS/ALPN negotiation. HPACK Huffman strings use the RFC 7541 Appendix B code table for encoding and decoding.

HTTP/3

The optional cl-http-kit/http3 system implements HTTP/3 control and request-stream framing over callbacks supplied by a QUIC implementation. It requires SBCL at load time: its cl-quic-kit dependency loads the UDP implementation even when the caller supplies QUIC callbacks. It supports a client and serve-http3-request-stream for one injected server request stream, including request/response headers and trailers, DATA frames, content-length checks, static and literal QPACK field sections, HPACK Huffman strings, caller-owned QPACK dynamic-table references, and Extended CONNECT :protocol pseudo-headers for transports that negotiate the capability. The client connection manager reuses injected QUIC clients by origin and supports LRU capacity, idle expiry, and maximum connection lifetime policies. Its high-level transport adapter supports in-memory and streaming request bodies plus response-body and ordered informational-response callbacks; proxy routing currently raises an explicit unsupported error. Per-request response header-byte, field-count, and body limits can be stricter than the retained connection defaults.

The HTTP/3 client uses finite defaults of 65,536 bytes for each QPACK instruction, 262,144 bytes for each retained QPACK instruction buffer, 100 request streams, 16 peer unidirectional streams, and 4 MiB for aggregate retained connection state. The injected QUIC backend still owns packet processing, flow control, and connection shutdown. max-body-bytes defaults to the core 16 MiB body limit at the client, server-stream, push, and connection-manager entry points. Passing NIL explicitly disables that body limit for that call.

The boundary does not provide QUIC packets, loss recovery, congestion control, TLS 1.3, ALPN, native sockets, a connection-level accept loop, or a native HTTP/3 server. The injected transport can pass accepted peer unidirectional streams to accept-http3-peer-unidirectional-stream for fragmented-prefix classification and control/QPACK attachment, or to process-http3-peer-unidirectional-stream to classify and apply the first control or QPACK input. It remains responsible for accepting streams, invoking the matching reader on later readiness notifications, and discarding unknown extension streams. The client can advertise MAX_PUSH_ID, receive promised responses on injected push streams, and cancel pushes. The server boundary can emit a push promise and its matching push stream when the surrounding QUIC transport supplies the required callbacks. The on-push-promise callback receives its own copy of the field names and values. Changing those strings does not change the client's retained promise. The cl-quic-kit client adapter can drive a caller-selected QUIC client, including its TLS handshake and HTTP/3 stream lifecycle; it does not replace the caller's QUIC, socket, certificate, or server-accept policy. Invalid or deliberately unsupported protocol features raise public protocol or unsupported-feature conditions.

High-level client

The optional cl-http-kit/client system supplies reusable URI, authentication, cookie, cache, content-coding selection, ALPN protocol-name, proxy, redirect, retry, and thread-safe HTTP/1.1 connection-pool policies around core messages. The client creates native TCP, TLS 1.3, and pooling boundaries when no custom transport is supplied. The cl-http-kit/tls system uses cl-tls-kit for client certificate verification, trust-store loading, and handshake deadlines. Verification defaults to :required; :optional applies the same policy. Only nil skips certificate-chain, hostname, and trust checks, and it does not disable the TLS CertificateVerify signature check. The TLS stream requires a peer close_notify before treating transport EOF as clean closure. Server-side TLS wrapping is explicitly unsupported by the current kit boundary. For an HTTPS server, terminate TLS in a reverse proxy or load balancer and forward plain HTTP to cl-http-kit/network. Configure the proxy to preserve the original host and scheme according to the application's trusted-forwarding policy; cl-http-kit does not validate forwarded headers automatically. Origin 401 and proxy 407 challenges are parsed with the RFC 9110 challenge grammar and can be answered by separate provider callbacks. Each provider receives at most one safe replay opportunity; streamed responses and non-replayable request bodies suppress automatic origin or forward-proxy replay. A rejected CONNECT tunnel is closed and reopened at most once when the proxy provider supplies credentials for its parsed challenge. The client enables an in-memory RFC 6797 HSTS store by default. Valid secure responses can add, replace, expire, or remove host policies; matching HTTP requests and redirect targets are upgraded before transport selection. IP literals and HTTP URIs cannot establish policy. The store checks the HTTPS scheme, not the transport's certificate-verification result. Applications using custom transports must ensure successful TLS verification before learning policies, or pass :strict-transport-store nil to disable HSTS. Applications can inject a store to retain policy across client instances or clear it explicitly. The client also enables an in-memory RFC 7838 alternative-service store by default. It learns valid Alt-Svc response fields, accounts for Age, applies the default ma, handles clear, expiry, and persist=1, and can discard non-persistent alternatives when the network changes. When :http3-transport-function is supplied, the client selects the first unexpired h3 alternative and attempts it before TCP. Applications must remove a rejected service after a 421 response; the client does not do so automatically. A failed attempt falls back to the primary transport only for http-connection-error with :request-sent-p nil, before any request-body producer or response callback has run. Other errors propagate. If no HTTP/3 transport is configured, discovered alternatives do not prevent TCP use. The native primary transport uses HTTP/1.1; a custom primary transport can provide HTTP/2. The HTTP/3 transport must authenticate the origin certificate, send the origin as TLS SNI, and preserve proxy policy. The cl-quic-kit adapter requires the origin hostname and leaves trust policy with the caller. Its default TLS driver uses the default trust store when trust anchors are omitted. The HTTP/3 manager rejects proxies rather than silently bypassing them. An explicit HTTP/3 request does not fall back. Pass :alternative-service-store nil to disable discovery. Collected client responses automatically decode gzip and deflate Content-Encoding values, including stacked codings, and remove stale Content-Encoding and Content-Length fields. Decoding can be disabled with :automatic-decompression-p nil. Additional codings such as br and zstd can be supplied as decoder functions without imposing compression-library dependencies on the core system. Eligible requests advertise the registered codings with Accept-Encoding unless the caller supplies that field. The configured body limit is checked against both transport bytes and each decoded representation, and bounded decoding stops before allocating an unbounded expanded body. Streaming body callbacks receive the encoded transport bytes and remain application-owned. The private response cache honors RFC 5861 stale-if-error on requests and responses after retries are exhausted for 500, 502, 503, 504, timeout, and connection errors. Cache fallback eligibility is independent of whether the retry policy retries those conditions. This fallback is limited to collected responses because a streaming callback might already have observed bytes from the failed response. RFC 5861 stale-while-revalidate is supported when a :stale-while-revalidate-scheduler is configured. The scheduler receives a revalidation function and owns asynchronous execution and duplicate-request coalescing; returning false makes the request perform normal synchronous validation. Serving stale updates Age, honors the response window, and is suppressed by explicit request revalidation directives. Fresh HTTPS responses also honor RFC 8246 immutable for ordinary max-age=0 reloads. The extension is ignored for plain HTTP and for explicit no-cache force reloads.

The cookie layer enforces host-only, prefix, SameSite, and partitioned-cookie constraints and lets the request path provide SameSite context and a top-level partition key. The cache layer evaluates request directives such as no-store, no-cache, max-age=0, min-fresh, and max-stale, can return stale entries when policy allows, and merges cache metadata from a 304 revalidation response without discarding the stored body.

Client transport functions must accept the keywords used by the request. The client API distinguishes always-passed keywords from optional body-producer and response-callback keywords. Use the provided stream or connection-pool constructors to compose those boundaries.

WebSocket

The http-kit/websocket package is loaded by cl-http-kit/client. It provides RFC 6455 frame serialization and parsing, masking and payload-size validation, fragmented-message assembly, close-code and UTF-8 reason handling, the Sec-WebSocket-Accept handshake calculation, and validated HTTP/1.1 upgrade request/response helpers. make-websocket-upgrade-request and websocket-client-handshake also cover the client-side HTTP 101 exchange on an already-open stream and leave that stream available for frame I/O. The frame API exposes masking explicitly: callers must supply masking keys or a per-frame key function when producing masked frames. It does not open sockets, perform TLS or ALPN, or negotiate extensions. Extension fields use strict RFC 6455 token and quoted-string parsing, and RFC 7692 permessage-deflate offer/response parameters are validated. The client handshake returns normalized negotiated extensions as its third value. Message reads and writes accept application-provided raw DEFLATE codec functions; only this explicit opt-in permits RSV1, and expanded messages remain subject to the configured size limit. The codec owns context takeover, window settings, and the RFC 7692 DEFLATE tail transformation. make-websocket-client-key validates and Base64-encodes 16 octets from an application-supplied cryptographically secure random source; make-websocket-upgrade-request can invoke that source directly. The entropy source itself and connection lifecycle remain application-owned.

serve-websocket-session serves an already-upgraded stream: it requires masked client frames by default, automatically answers Ping frames, echoes a valid peer Close frame, dispatches complete messages to a handler, and closes with an appropriate protocol or size code on errors. It does not open sockets, perform TLS or ALPN, or choose extension and codec policy.

Native network boundary

cl-http-kit/network provides a native SBCL TCP stream opener with IPv4/IPv6 DNS resolution and deadline-aware connect/read operations. It also provides an IPv4/IPv6 listening socket boundary: open-http-tcp-listener binds and listens, accept-http-tcp-stream accepts one binary stream and returns peer address metadata, and close-http-tcp-listener closes the listener. It keeps TLS, ALPN, and proxy negotiation outside the socket opener. Applications can compose the separate cl-http-kit/tls cl-tls-kit client wrapper around connected streams while retaining control of certificate and protocol policy. Server-side wrapping is an explicit unsupported boundary.

serve-http1-listener combines accept and serve-http1-session dispatch for a caller-owned listener, with connection limits and accept/error callbacks.

The native client path uses cl-tls-kit. Client-side HTTP/3 connection setup is available through the cl-quic-kit adapter, while the HTTP/3 framing system remains an injected QUIC stream boundary and applications retain socket and certificate policy.

Application-owned behavior

The portable core does not choose a socket library, DNS resolver, or TLS implementation. Applications can adapt those concerns to their deployment, including the optional SBCL network boundary, and can supply callbacks to the library's pool without changing the message and wire layers. The optional high-level client supplies native TCP/TLS when its default boundary is used; custom transports retain ownership of their network and TLS setup. The standard proxy stream opener implements HTTP forward/CONNECT and SOCKS5 negotiation; injected transports must implement the supplied proxy plan themselves. Character encoding and logging policy remain application-owned.

Portability

The core is written in portable Common Lisp and uses binary streams and injected callbacks rather than a platform-specific socket API. The repository's Nix flake currently declares Darwin ARM64 and Linux x86-64 development targets; the HTTP model itself does not depend on those operating-system details.