Catalog · How the catalog works
horse-provider-nghttp2
HTTP/2-native transport provider for the Horse web framework, on libnghttp2. Drop-in replacement for the default Indy transport — one compiler define, existing routes and middleware unchanged. h2c, TLS, mTLS, gRPC, streaming and WebSocket.
- ★ 2 on GitHub
Anyone may press Interesting, signed in or not. It helps others find this project.
README
horse-provider-nghttp2
Status: v1.12.1 — production-ready (h2c + TLS + mTLS + gRPC + streaming + WebSocket + Windows Service). Delphi 10.4+, FPC 3.2.2 and trunk 3.3.1 — gRPC needs trunk.
HTTP/2-native transport provider for Horse, built on Delphi-nghttp2 and the C library libnghttp2. Drop-in replacement for the default Indy transport — activate with one compiler define, keep your existing routes and middleware unchanged.
Companion to horse-provider-crosssocket (HTTP/1.1 async via Delphi-Cross-Socket) and horse-provider-mormot (mORMot2 stack, incl. http.sys).
What it enables
- HTTP/2 multiplexing — clients send many concurrent streams over one TCP connection; no Head-of-Line blocking
- HPACK header compression — reduces per-request overhead on high-frequency routes
- TLS with ALPN —
h2over HTTPS; OpenSSL 3.x / 1.1.x auto-detected at runtime, no recompile - mTLS — client certificate verification for zero-trust service-to-service calls
- TLS 1.2 cipher restriction —
SSLCipherList(OpenSSL rule syntax), applied since 1.10.0 and verified on the wire; TLS 1.3 suites (SSLCipherSuitesTLS13) and a minimum TLS version (SSLMinVersion, e.g. TLS 1.3 only) since 1.11.0, both read back and verified on the wire; and since 1.11.0Listenrefuses a TLS 1.2 list that leaves no cipher HTTP/2 permits (RFC 7540) — see doc/tls.md - gRPC — unary plus all three streaming shapes (server, client, bidirectional); protobuf codec with repeated fields; two registration styles (
RegisterMethod/RegisterService<T>) - Async worker pool — handlers run off the connection thread; 18.3× throughput on blocking routes
- Streaming & SSE —
Res.SendStreamfor Web Streams (NDJSON) and Server-Sent Events; no chunked framing needed on HTTP/2 - WebSocket over HTTP/2 — RFC 8441 extended CONNECT, sharing the connection with regular streams instead of monopolising a socket; opt-in, off by default
- Graceful shutdown — two-stage GOAWAY per RFC 9113 §6.8; in-flight requests complete before the server closes
- Event-loop I/O — epoll (Linux) and IOCP (Windows) engines, opt-in via
UseEventLoop - Cross-platform — Windows/Delphi 12, Linux/FPC 3.2.2 and trunk 3.3.1, Linux/Delphi (PAServer)
- Cross-product app shapes — Console, VCL, Daemon, Windows Service, FPC Daemon, LCL, HTTPApplication (Windows Service: the base
THorseNghttp2ServiceimplementsGetServiceControllerand has its own.dfmsince 1.12.0; seesamples/service/README.md)
Before you choose it
The server speaks HTTP/2 only — there is no HTTP/1.1 fallback. Your routes and middleware port over unchanged, but the wire does not:
- HTTP/1.1-only clients are refused, not downgraded.
- Browsers need TLS + ALPN to reach it; cleartext h2c is for native clients.
- A reverse proxy must speak HTTP/2 on its back leg. nginx
grpc_passand Apachemod_proxy_http2do;proxy_pass,mod_proxy_httpand IIS ARR do not. See doc/deployment.md. - WebSocket needs RFC 8441 (extended CONNECT), not the HTTP/1.1 handshake. Opt-in
and off by default. Browsers negotiate it transparently —
new WebSocket(...)is unchanged — but most non-browser clients and libraries speak RFC 6455 over HTTP/1.1 only, and there is no HTTP/1.1 here to fall back to, so for them it is "cannot connect" rather than "slower path". See doc/websocket.md.
Best fit: gRPC, service-to-service APIs, and clients you control. For a public HTTP/1.1 endpoint, use one of Horse's other transports.
Quick start
Requirements
- Delphi 10.4 Sydney or later / FPC 3.2.2 or trunk 3.3.1 — gRPC needs trunk; build 3.2.2 with
-dHORSE_NGHTTP2_NO_GRPC(see doc/fpc-lazarus.md) - Horse ≥ 3.3.12 — stock, unpatched. 3.3.12 carries
SSLCipherSuitesTLS13andSSLMinVersion(HashLoad/horse #597), which this provider reads from 1.11.0, so it does not compile against 3.3.11 or earlier. The older floors still explain why an older Horse misbehaved: on 3.3.4 and below, WebSocket and streaming fail silently; below 3.3.10,StopListenGracefulthroughTHorseskips the provider's drain (see Horse core requirements) - Delphi-nghttp2 ≥ 1.27.0 — the floor
boss.jsondeclares, since provider 1.12.1. 1.27.0 is a correctness floor (KEYPW-1): below it, an encrypted key withoutSSLKeyPasswordmade OpenSSL prompt on the terminal, soListenhung on Windows, and a non-ASCII key password failed on Delphi. 1.26.0 is the API floor:Listenrefuses anSSLCipherListwith no RFC 7540-permitted cipher throughRequireHttp2Tls12Cipher(H2CIPHER-1), which first shipped there;SSLMinVersionneeds 1.23.0'sSetMinProtocolVersionand the TLS 1.3 suites 1.22.0's setters. Underneath it is the older correctness floor of 1.20.0: below 1.20.0,PushStreamDatareset the read cursor in the streaming buffer to 0 after every append (CL3b), causingReadResponseBodyCallbackto re-read from the start of the buffer on every pump — the buffer grew without bound and the worker deadlocked permanently under slow-client conditions. Any streaming or SSE route is affected; the failure is silent on the server side and surfaces as a hung connection on the client. Boss resolves the newest version satisfying the floor, so there is nothing to bump when the library releases. - libnghttp2 ≥ 1.59 — required at run time, dynamic-loaded (
nghttp2.dll/libnghttp2.so.14/libnghttp2.dylib); see getting-nghttp2-windows.md / getting-nghttp2-linux.md - OpenSSL 3.x or 1.1 for TLS only (auto-detected at runtime)
Full per-platform shipping list: doc/deployment.md.
Install with Boss:
boss install github.com/freitasjca/horse-provider-nghttp2Horse core requirements
Nothing to do beyond using Horse ≥ 3.3.12. Everything this provider needs is upstream and released — no fork, no branch, no patched files.
boss install github.com/HashLoad/horseThis section used to tell you to clone a nghttp2-required branch carrying five
patched core files. All five are now in stock Horse, verified at the published
- .5 artefact by each fix's own identifier rather than by its PR being marked merged:
| Horse core file | Provides | Landed in |
|---|---|---|
Horse.pas |
the HORSE_PROVIDER_NGHTTP2 selector and its mutual-exclusion guards — without it the define does not select this provider |
PR #555 |
Horse.Response.pas |
adds HORSE_PROVIDER_NGHTTP2 to the stream-writer factory guard |
PR #552 |
Horse.Provider.Socket.WebSocket.pas |
epoll transport no longer treats EAGAIN as a disconnect |
PR #549 |
Horse.Request.pas |
SetWebSocketUpgrade, so RFC 8441 extended CONNECT is recognised as a WebSocket |
PR #550 |
Horse.Core.WebSocket.pas |
FPC-only FeedBytes interface-to-class cast |
PR #551 |
Why the floor is 3.3.12. From 1.11.0 the provider reads the TLS 1.3 suite and minimum-version fields that PR #597 added in 3.3.12, so an older Horse fails to compile — loudly. The earlier floors are kept below because on those releases the provider (before 1.11.0) still compiled and the failures were silent — which is the reason to state a version rather than let people discover it:
- Before #552, every streaming and SSE request returns nothing — no
headers, no body, no error, no log line.
FStreamWriterFactoryis a last-writer-wins class var set from two unitinitializationsections, so which one survives depends on the compiler's dependency walk. On FPC trunk it happened to resolve correctly; on FPC 3.2.2 it does not. - Before #549 / #551, the RFC 8441 WebSocket handshake completes and then the connection simply stops carrying frames.
- Before #566 (3.3.5), Windows/FPC enabled keep-alive while
TCP_NODELAYwas still{$IFDEF UNIX}-only, giving ~200 ms stalls on reused connections. Linux was unaffected, so this one hides from a Linux-only CI. - Before #590 (3.3.10),
THorseInstance.StopListenGracefulcalled its ownStopListen, so a graceful stop throughTHorseskipped this provider's drain.
Activation
{$DEFINE HORSE_PROVIDER_NGHTTP2}Legacy alias {$DEFINE HORSE_NGHTTP2} also accepted. Mutually exclusive with all other HORSE_PROVIDER_* and HORSE_HOST_* defines — enforced at compile time by FATAL guards in Horse.pas.
Minimal server
program MyHttp2Server;
{$APPTYPE CONSOLE}
{$DEFINE HORSE_PROVIDER_NGHTTP2}
uses Horse;
procedure GetPing(Req: THorseRequest; Res: THorseResponse);
begin
Res.Send('pong');
end;
begin
THorse.Get('/ping', GetPing);
THorse.Listen(9200);
end.curl --http2-prior-knowledge http://localhost:9200/ping--http2-prior-knowledge tells curl to speak HTTP/2 immediately (no HTTP/1.1 upgrade round-trip). This is the connection mode used in h2c configuration.
Minimal client
program MyHttp2Client;
{$APPTYPE CONSOLE}
uses
Nghttp2.Client, System.SysUtils;
var
C: TNghttp2Client;
R: TNghttp2Response;
begin
C := TNghttp2Client.Create;
try
C.Connect('127.0.0.1', 9200);
R := C.SubmitRequest('GET', '/ping', nil, nil);
WriteLn('Status: ', R.Status);
WriteLn(TEncoding.UTF8.GetString(R.Body));
finally
C.Free;
end;
end.TNghttp2Client is provided by Delphi-nghttp2. No Horse dependency on the client side — add Delphi-nghttp2/src/ to the search path and link nghttp2.dll / libnghttp2.so.14.
For TLS, pass the cert paths before Connect:
C.SSLEnabled := True;
C.SSLCertFile := 'tls/cert.pem'; // mTLS only — omit for plain TLS
C.SSLKeyFile := 'tls/key.pem';
C.Connect('127.0.0.1', 9443);Documentation
| Topic | Guide |
|---|---|
| Roadmap | doc/roadmap.md |
| Concurrency & worker pool | doc/concurrency.md |
| Deployment — proxies, app types, load balancers | doc/deployment.md |
| Streaming & SSE | doc/streaming.md |
| WebSocket (RFC 8441) | doc/websocket.md |
| Graceful shutdown | doc/graceful-shutdown.md |
| TLS and mTLS | doc/tls.md |
| gRPC | doc/grpc.md |
| Testing & benchmarking | doc/testing.md |
| Platform coverage | doc/platform-coverage.md |
| FPC / Lazarus | doc/fpc-lazarus.md |
| Architecture & contributing | doc/architecture.md |
| Limitations | doc/limitations.md |
License
MIT. See LICENSE.

