server_native 0.1.3+1
server_native: ^0.1.3+1 copied to clipboard
Rust-backed transport boot APIs for Dart HTTP servers.
server_native #
server_native provides a Rust-backed HTTP server runtime for Dart with a
dart:io-like programming model.
For most server code, it is intended to be a drop-in replacement for
HttpServer: keep the same request/response handling and swap only the bind
bootstrap.
Table Of Contents #
- Install
- Quick Start (
HttpServerStyle) - Drop-In
HttpServerReplacement - Protocol Support (HTTP/1.1, HTTP/2, HTTP/3)
- Address Semantics
- Multi-Server Binding (
NativeHttpServer.loopback) - Multi-Server Binding Shortcut (
localhost/any) - Callback Multi-Server Binding (
NativeMultiServer) - Explicit Multi-Bind List (
NativeServerBind) - TLS / HTTPS (Optional HTTP/3)
- Callback API (
HttpRequest) - Direct Handler API (
NativeDirectRequest) - Graceful Shutdown
- DevTools Profiling Example
- Framework Benchmarks
- Framework Compatibility Suites (Local + CI)
- Dart SDK
HttpServerCompatibility Tests - Native Bindings
- Prebuilt Native Artifacts
- Troubleshooting
Install #
dependencies:
server_native: ^0.1.0
Quick Start (HttpServer Style) #
import 'dart:io';
import 'package:server_native/server_native.dart';
Future<void> main() async {
final server = await NativeHttpServer.bind('127.0.0.1', 8080, http3: false);
await for (final request in server) {
if (request.uri.path == '/health') {
request.response
..statusCode = HttpStatus.ok
..headers.contentType = ContentType.json
..write('{"ok":true}');
await request.response.close();
continue;
}
request.response
..statusCode = HttpStatus.notFound
..headers.contentType = ContentType.text
..write('Not Found');
await request.response.close();
}
}
Drop-In HttpServer Replacement #
Existing HttpRequest/HttpResponse logic can remain unchanged.
Typical migration:
- before:
HttpServer.bind(...) - after:
NativeHttpServer.bind(...)
import 'dart:io';
import 'package:server_native/server_native.dart';
Future<void> main() async {
final HttpServer server = await NativeHttpServer.bind('127.0.0.1', 8080);
await for (final request in server) {
request.response
..statusCode = HttpStatus.ok
..write('drop-in ok');
await request.response.close();
}
}
Protocol Support (HTTP/1.1, HTTP/2, HTTP/3) #
- HTTP/1.1: supported for plaintext and TLS servers.
- HTTP/2: controlled explicitly with
http2(defaults totrue). - HTTP/3: supported only with TLS and QUIC.
Notes:
- TLS certificates do not implicitly force HTTP/2. If you want TLS + HTTP/1.1
only, set
http2: false. http3options default totrue, but HTTP/3 is automatically disabled for insecure (non-TLS) server boots.- If TLS cert/key are not configured, server boots run in HTTP/1.1 + optional
HTTP/2 mode only (based on
http2).
Address Semantics #
NativeHttpServer.bind() supports HttpServer-style address values:
'127.0.0.1','::1', or any explicit host/IP'localhost'(loopback convenience)'any'(bind all interfaces)
You can also use:
NativeHttpServer.loopback(...)NativeHttpServer.bindSecure(...)NativeHttpServer.loopbackSecure(...)
Multi-Server Binding (NativeHttpServer.loopback) #
Bind one logical server across all loopback interfaces (127.0.0.1 and ::1
when available) with a single HttpServer stream:
import 'dart:io';
import 'package:server_native/server_native.dart';
Future<void> main() async {
final server = await NativeHttpServer.loopback(8080, http3: false);
await for (final request in server) {
request.response
..statusCode = HttpStatus.ok
..headers.contentType = ContentType.text
..write('loopback multi-server ok');
await request.response.close();
}
}
Multi-Server Binding Shortcut (localhost / any) #
Use bind() with localhost (loopback interfaces) or any (all interfaces)
to get multi-interface binding through a single HttpServer:
import 'dart:io';
import 'package:server_native/server_native.dart';
Future<void> main() async {
final server = await NativeHttpServer.bind('localhost', 8080, http3: false);
// Equivalent patterns:
// final server = await NativeHttpServer.bind('any', 8080, http3: false);
// final server = await NativeHttpServer.loopback(8080, http3: false);
await for (final request in server) {
request.response
..statusCode = HttpStatus.ok
..headers.contentType = ContentType.text
..write('localhost multi-server ok');
await request.response.close();
}
}
Callback Multi-Server Binding (NativeMultiServer) #
Use NativeMultiServer when you want callback-style routing and
http_multi_server-style bind semantics:
import 'dart:io';
import 'package:server_native/server_native.dart';
Future<void> main() async {
await NativeMultiServer.bind(
(request) async {
request.response
..statusCode = HttpStatus.ok
..headers.contentType = ContentType.text
..write('native multi bind ok');
await request.response.close();
},
'localhost',
8080,
http3: false,
);
}
Explicit Multi-Bind List (NativeServerBind) #
Use NativeServerBind with serveNativeMulti/serveSecureNativeMulti for
fully explicit listener lists:
import 'dart:io';
import 'package:server_native/server_native.dart';
Future<void> main() async {
await serveNativeMulti(
(request) async {
request.response
..statusCode = HttpStatus.ok
..headers.contentType = ContentType.text
..write('explicit binds ok');
await request.response.close();
},
binds: const <NativeServerBind>[
NativeServerBind(host: '127.0.0.1', port: 8080),
NativeServerBind(host: '::1', port: 8080),
],
http3: false,
);
}
TLS / HTTPS (Optional HTTP/3) #
import 'dart:io';
import 'package:server_native/server_native.dart';
Future<void> main() async {
final server = await NativeHttpServer.bindSecure(
'127.0.0.1',
8443,
certificatePath: 'cert.pem',
keyPath: 'key.pem',
http2: true,
http3: true,
);
await for (final request in server) {
request.response
..statusCode = HttpStatus.ok
..headers.contentType = ContentType.text
..write('secure ok');
await request.response.close();
}
}
Callback API (HttpRequest) #
If you prefer a callback instead of a server stream:
import 'dart:io';
import 'package:server_native/server_native.dart';
Future<void> main() async {
await serveNativeHttp((request) async {
request.response
..statusCode = HttpStatus.ok
..headers.contentType = ContentType.text
..write('hello');
await request.response.close();
}, host: '127.0.0.1', port: 8080, http3: false);
}
nativeCallback defaults to true for NativeHttpServer and serveNative*
HttpRequest APIs, which means direct FFI callback transport is used by
default. Set nativeCallback: false to force bridge socket transport.
WebSocket upgrade is supported in either mode.
Direct Handler API (NativeDirectRequest) #
This mode gives direct method/path/header/body access without HttpRequest.
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:server_native/server_native.dart';
Future<void> main() async {
await serveNativeDirect((request) async {
if (request.method == 'GET' && request.path == '/health') {
return NativeDirectResponse.bytes(
headers: const [
MapEntry(HttpHeaders.contentTypeHeader, 'application/json'),
],
bodyBytes: Uint8List.fromList(utf8.encode('{"ok":true}')),
);
}
final body = await utf8.decoder.bind(request.body).join();
return NativeDirectResponse.bytes(
status: HttpStatus.ok,
headers: const [
MapEntry(HttpHeaders.contentTypeHeader, 'text/plain; charset=utf-8'),
],
bodyBytes: Uint8List.fromList(utf8.encode('echo: $body')),
);
}, host: '127.0.0.1', port: 8080, http3: false);
}
For lowest overhead callback routing, enable native callback mode:
import 'dart:io';
import 'dart:typed_data';
import 'package:server_native/server_native.dart';
Future<void> main() async {
await serveNativeDirect((request) async {
return NativeDirectResponse.bytes(
status: HttpStatus.ok,
headers: const [
MapEntry(HttpHeaders.contentTypeHeader, 'text/plain; charset=utf-8'),
],
bodyBytes: Uint8List.fromList('ok'.codeUnits),
);
}, host: '127.0.0.1', port: 8080, nativeDirect: true);
}
Graceful Shutdown #
All boot helpers accept shutdownSignal.
import 'dart:async';
import 'dart:io';
import 'package:server_native/server_native.dart';
Future<void> main() async {
final shutdown = Completer<void>();
final serveFuture = serveNativeHttp((request) async {
request.response
..statusCode = HttpStatus.ok
..write('bye');
await request.response.close();
}, host: '127.0.0.1', port: 8080, shutdownSignal: shutdown.future);
// Call this from your signal handler / lifecycle hook.
shutdown.complete();
await serveFuture;
}
DevTools Profiling Example #
Use the included profiling target:
dart --observe example/devtools_profile_server.dart --mode=direct --port=8080
Modes:
--mode=directfor direct handler path--mode=httpforHttpRequestpath
Framework Benchmarks #
Framework transport benchmarks live in benchmark/:
dart run benchmark/framework_transport_benchmark.dart --framework=all
Latest harness snapshot (February 19, 2026; requests=2500, concurrency=64,
warmup=300, iterations=25):
Note: these values were measured on a local development machine and are
intended for relative comparison. See benchmark/README.md for full test
machine specs and run context.
Mode meaning in this table:
nativeCallback=true:NativeHttpServeruses direct FFI callback handling (bridge socket bypassed).nativeCallback=false:NativeHttpServeruses the bridge socket/frame transport between Rust and Dart.
| Mode | Top result | req/s | p95 |
|---|---|---|---|
nativeCallback=true |
native_direct_rust |
12362 | 6.34 ms |
nativeCallback=false |
native_direct_rust |
12656 | 6.17 ms |
Framework pair highlights from the same harness:
| Framework | *_io |
*_native (nativeCallback=true) |
*_native (nativeCallback=false) |
|---|---|---|---|
dart:io |
7703 req/s, p95 9.72 ms | 8370 req/s, p95 9.05 ms | 7565 req/s, p95 10.60 ms |
routed |
5703 req/s, p95 12.51 ms | 7110 req/s, p95 10.88 ms | 6392 req/s, p95 11.93 ms |
relic |
5073 req/s, p95 14.46 ms | 6823 req/s, p95 11.31 ms | 5990 req/s, p95 12.73 ms |
shelf |
5181 req/s, p95 14.08 ms | 6524 req/s, p95 11.66 ms | 5843 req/s, p95 13.07 ms |
See benchmark/README.md for full result tables, options, and case labels.
Framework Compatibility Suites (Local + CI) #
Use the compatibility harness to clone/update external frameworks, apply
server_native integration patches, and run their full test suites in both
transport modes:
SERVER_NATIVE_COMPAT=false(io): framework binds withdart:ioHttpServerSERVER_NATIVE_COMPAT=true(native): framework binds withNativeHttpServer
Run locally from repo root:
dart run packages/server_native/tool/framework_compat.dart \
--framework=all \
--mode=both \
--fresh \
--json-output=.dart_tool/server_native/framework_compat/report.json
Run a single framework:
dart run packages/server_native/tool/framework_compat.dart --framework=shelf --mode=both --fresh
CI workflow:
.github/workflows/server_native_framework_compat.yml
The workflow runs the same harness command and uploads a JSON result artifact.
Dart SDK HttpServer Compatibility Tests #
server_native includes a Dart SDK-derived compatibility suite in:
test/sdk_http_server_compat_test.dart
Run it directly:
dart test test/sdk_http_server_compat_test.dart
The suite is ported from SDK standalone IO HttpServer tests and currently
contains:
- default response-header behavior
- content-type charset encoding behavior for
response.write - connection-header/persistent-connection behavior
- content-length mismatch error behavior (
response.done) - shared bind behavior (
shared: true)
Some native parity cases are explicitly skipped with a reason string until
the corresponding behavior matches dart:io. Those skip reasons serve as an
up-to-date checklist for remaining HttpServer parity work.
Native Bindings #
If you changed Rust FFI symbols/structs, regenerate bindings:
dart run tool/generate_ffi.dart
If you bump pubspec.yaml version, regenerate prebuilt release metadata:
dart run tool/generate_prebuilt_release.dart
Prebuilt Native Artifacts #
Cross-platform artifacts are built by:
.github/workflows/server_native_prebuilt.yml
CI build output is staged under:
packages/server_native/native/prebuilt/<platform>/
Artifact naming:
server_native-<platform>.tar.gz
Prebuilt binary release tags are separate from Dart package releases:
server-native-prebuilt-v*
Current platform labels:
linux-x64,linux-arm64macos-arm64,macos-x64windows-x64,windows-arm64android-arm64,android-armv7,android-x64ios-arm64,ios-sim-arm64,ios-sim-x64
Pull host prebuilts into your project:
dart run server_native:setup
setup is optional. Build hooks now auto-download the package-matched prebuilt
release into .dart_tool/server_native/prebuilt/<tag>/<platform>/ when no
local prebuilt is available.
This defaults to the prebuilt tag generated from the local
server_native package version (for example server-native-prebuilt-v0.1.2).
To auto-select the newest available prebuilt release instead, use:
dart run server_native:setup --tag latest
Pull a specific release tag and platform:
dart run server_native:setup --tag server-native-prebuilt-v0.1.2 --platform linux-x64
Downloaded files are extracted to:
.dart_tool/server_native/prebuilt/<tag>/<platform>/
Build hook prebuilt lookup order:
SERVER_NATIVE_PREBUILT(absolute path to a library file)<project-root>/.dart_tool/server_native/prebuilt/<tag>/<platform>/<library><project-root>/.dart_tool/server_native/prebuilt/<platform>/<library>(legacy fallback)<repo-root>/.dart_tool/server_native/prebuilt/<tag>/<platform>/<library><repo-root>/.dart_tool/server_native/prebuilt/<platform>/<library>(legacy fallback)<package-root>/native/prebuilt/<tag>/<platform>/<library>(packaged fallback)<package-root>/native/prebuilt/<platform>/<library>(legacy packaged fallback)<package-root>/native/<platform>/<library>(legacy packaged fallback)
If no prebuilt library is found, the hook falls back to Rust source build
through native_toolchain_rust.
Troubleshooting #
If you see:
File modified during build. Build must be rerun.
Run the same command again once. This can happen on first native asset build.