Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Catalog · How the catalog works
Anything · Lazarus or Delphi
Random projectRandom project

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.

View on GitHub

Member rating

No ratings yet.

Sign in to rate 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 — h2 over 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.0 Listen refuses 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.SendStream for 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 THorseNghttp2Service implements GetServiceController and has its own .dfm since 1.12.0; see samples/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_pass and Apache mod_proxy_http2 do; proxy_pass, mod_proxy_http and 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 SSLCipherSuitesTLS13 and SSLMinVersion (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, StopListenGraceful through THorse skips the provider's drain (see Horse core requirements)
  • Delphi-nghttp2 ≥ 1.27.0 — the floor boss.json declares, since provider 1.12.1. 1.27.0 is a correctness floor (KEYPW-1): below it, an encrypted key without SSLKeyPassword made OpenSSL prompt on the terminal, so Listen hung on Windows, and a non-ASCII key password failed on Delphi. 1.26.0 is the API floor: Listen refuses an SSLCipherList with no RFC 7540-permitted cipher through RequireHttp2Tls12Cipher (H2CIPHER-1), which first shipped there; SSLMinVersion needs 1.23.0's SetMinProtocolVersion and 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, PushStreamData reset the read cursor in the streaming buffer to 0 after every append (CL3b), causing ReadResponseBodyCallback to 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:

pascal
boss install github.com/freitasjca/horse-provider-nghttp2

Horse 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.

pascal
boss install github.com/HashLoad/horse

This 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 3.3.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. FStreamWriterFactory is a last-writer-wins class var set from two unit initialization sections, 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_NODELAY was 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.StopListenGraceful called its own StopListen, so a graceful stop through THorse skipped this provider's drain.

Activation

pascal
{$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

pascal
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.
pascal
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

pascal
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:

pascal
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.