relic_native 2.0.0-rc.5
relic_native: ^2.0.0-rc.5 copied to clipboard
A Relic adapter backed by a Zig HTTP server on native I/O threads.
relic_native #
A Relic adapter backed by a Zig HTTP
server (zio and std.http.Server) that
each isolate drives from its own thread.
An acceptor thread hands each connection to the isolate with the fewest.
From there the parsing, the keep-alive handling and the timeouts run in
native code between the isolate's Dart work, and a request reaches the
handler as plain Dart data. The adapter is opt-in: app.serve() stays on
relic_io, and this package is used through serveNative.
import 'package:relic_core/relic_core.dart';
import 'package:relic_native/relic_native.dart';
Future<void> main() async {
final app = RelicApp()..get('/', (final req) => Response.ok());
await app.serveNative(port: 8080, noOfIsolates: 4);
}
HTTPS #
Pass a NativeTls with the certificate chain and private key as PEM, and
the server speaks HTTPS. tls.zig
runs the handshake and the records in native code, on the same thread
as the rest of the connection.
await app.serveNative(
port: 8443,
tls: NativeTls.fromFiles(
certificateChain: 'cert.pem',
privateKey: 'key.pem',
),
);
The chain holds the server's certificate first, then any intermediates.
The key is a PKCS#8 key, ECDSA, RSA or Ed25519, or a SEC 1 or PKCS#1 one,
the EC PRIVATE KEY and RSA PRIVATE KEY blocks cert-manager writes by
default. An encrypted key takes its password: PKCS#8 under PBES2 with
PBKDF2 and AES-128-CBC or AES-256-CBC, or a key block behind OpenSSL's
DEK-Info headers with the same ciphers, as openssl pkcs8,
openssl genpkey and openssl ec -aes256 write them. An empty password
counts as none. bind throws a TlsException for a key it cannot use,
for a missing or wrong password, and for a chain with no certificate or
one that is expired, unreadable or repeats a subject. The server speaks
TLS 1.3 only and selects http/1.1 through ALPN. A client whose ALPN
offer holds no http/1.1 is refused with a TLS alert. A client that sends
plain HTTP to the port gets a plain 400.
To require a client certificate, pass the CA certificates it must chain
to as clientAuthorities:
tls: NativeTls.fromFiles(
certificateChain: 'cert.pem',
privateKey: 'key.pem',
clientAuthorities: 'client_ca.pem',
),
A client without one, or with one that does not chain to them, is
refused with a TLS alert. A handler does not see the certificate, only
that the connection passed the check. bind throws a TlsException for
client authorities with no certificate, or one that is expired,
unreadable, not a CA, that is, without the v3 basicConstraints that
marks one, or repeats a subject.
Platforms #
Linux (x64, arm64), macOS (arm64, x64) and Windows (x64, arm64). On
another platform the package builds no native library, and
NativeAdapter.bind throws. Use relic_io there.
The prebuilt libraries are compiled for CPUs with hardware AES, which
TLS needs to run at speed. On x64 that is AES-NI, PCLMULQDQ and AVX,
which server and desktop CPUs of the last decade have, apart from some
low-end Atom, Celeron and Pentium models. On Linux and Windows arm64 it
is the ARMv8 crypto extensions, which the Raspberry Pi 4 lacks. On such
a CPU the library stops with an illegal instruction. Use relic_io
there too.
Building from source #
The published package ships prebuilt libraries. A source checkout compiles
with Zig through the build hook. The zig on PATH must be the version in
build.zig.zon, or anyzig, which
picks it from that file.
Isolates #
Each isolate runs its own reactor on its own thread: the sockets, the
parsing and the writes of the connections it was handed happen between
its Dart work, in ticks the adapter drives from the event loop. One
acceptor thread per server hands each new connection to the isolate
with the fewest. noOfIsolates is the one knob, and a trivial route
scales with it up to the machine's cores.
Choosing between relic_io and relic_native #
relic_io is the default. app.serve() uses it and the relic package
does not depend on relic_native. Both run the same handlers, middleware
and router, and both pass the same conformance suite. The differences:
- Throughput and tail latency. On a plaintext route the native adapter
answers several times the requests per second of
relic_ioon one isolate, and its p99 under load is lower, since parsing and keep-alive run on native threads while the isolate is busy. The numbers are indoc/design/relic_native/NOTES.md. - TLS. Both terminate it.
relic_iouses aSecurityContextand speaks TLS 1.2 and 1.3.relic_nativetakes PEM throughNativeTlsand speaks TLS 1.3 only. - Platforms.
relic_ioruns wherever Dart does.relic_nativeruns on Linux, macOS and Windows. - Dependencies.
relic_iois pure Dart.relic_nativeships prebuilt libraries and needs Zig only for a source checkout.
Stay with relic_io unless you have measured the dart:io adapter as the
bottleneck. relic_native is newer code with a native library in the
process, and the gain only matters once the server is the limit.
Limits #
HTTP/1.0 and HTTP/1.1 only, with Content-Length and chunked bodies
both ways. No HTTP/2 or HTTP/3. TLS 1.3 only, see above. A handler
can hijack the connection as a raw byte channel, and a WebSocket upgrade
is framed by the native side, so capabilities.hijack and
capabilities.webSocket are both true. A WebSocket message is at most
maxWebSocketMessage bytes, 16 MiB unless bind is told otherwise. A
request head is at most 16 KiB with at most 128 fields, and a body over
maxInlineBody streams to the handler instead of being read first. Under
TLS a connection holds about 33 KiB more, the buffers a record needs.