nitro_http 0.0.4
nitro_http: ^0.0.4 copied to clipboard
A fast HTTP client for Flutter backed by a native libcurl C++ engine over Nitro FFI, with HTTP/1.1, HTTP/2, HTTP/3, streaming and WebSockets.
0.0.4 #
Fixed #
-
RootCaSource.noneverified nothing instead of trusting nothing — every chain was accepted. It is now refused unlesspinnedSpkiSha256is set; useTlsSettings.insecure()to skip verification deliberately. -
HTTPS failed on every iOS and macOS request against the vendored engine (
CURLcode 60). The TLS backend is detected at runtime and the bundled Mozilla roots are installed when the platform store is unreachable. Note thatRootCaSource.platformon Apple is then served by that bundle, so a root added to the Keychain by a user or MDM profile is not trusted — useRootCaSource.customwith your own PEM. Android was never affected. -
wss://segfaulted against any HTTP/2-capable server. The handshake pins HTTP/1.1. Plainws://was unaffected. -
StreamChunkSettings.minContentLength: 0was read as "unset" and replaced with the 1 MiB default, so nothing under 1 MiB batched. -
StreamChunkSettings.maxHoldnever fired while a connection was idle, which is the case it exists for. The engine loop polls the deadline.
Added #
-
LogInterceptor— one line per call at four levels (none,basic,headers,body), credential headers redacted, streamed bodies never drained, duration taken from the engine's timings. -
ParallelInterceptors— runs independent observers concurrently for when each awaits I/O. Members that modify the chain throw. -
Interceptor hooks return
FutureOr, and the chain stays synchronous until one returns a future: three synchronous interceptors cost 4.4 µs per request against 50.8 µs before.Interceptor.proceedRequestandInterceptor.proceedResponseareconstpass-through results. -
NitroHttpTlsExceptionfor a handshake that failed before any certificate, andNitroHttpConfigurationExceptionfor settings refused before a socket opens. Split out ofNitroHttpCertificateExceptionandNitroHttpUnknownException. -
NitroWebSocket.connectacceptstlsSettings, sowss://can use custom roots, SPKI pinning, mTLS and a version clamp.
Changed #
- Chunk-batching defaults are set where the engine config is constructed instead
of being inferred from
0where it is read, so0means zero.
Migration #
-
NitroHttpExceptionis sealed: exhaustive switches needNitroHttpTlsExceptionandNitroHttpConfigurationException. -
An
Interceptorsubclass that returnssuper.beforeRequest(…)from a method declaredFuture<…>must widen its own return type toFutureOr. Ordinaryasyncoverrides are unaffected. -
InterceptorChain.runOnErrorthrows synchronously for an unhandled failure rather than returning a rejected future.awaitbehaves as before.
Tests #
Behavioural coverage, added because the three TLS and WebSocket bugs above all shipped past configuration-only tests.
- 26 settings that had configuration tests but no end-to-end ones: redirect caps,
cookie suppression and persistence, the idle deadline, compression, pool
limits, SOCKS5, proxy credentials, upload progress, chunk batching, custom
verbs, alt-svc, DoH, h3 negotiation, and the TLS settings against a locally
generated CA.
TlsSettings.sniHostnameis a named skip, since it is still ignored. - WebSockets against a real peer: 7 engine tests (binary and 200 KB payloads,
empty frames, fragment reassembly, automatic pong,
maxFrameBytes, send after close) and 13 Dart tests (close codes, subprotocols, keepalive, burst ordering, concurrent sockets not crossing streams). - Resumable downloads and uploads.
RangeandContent-Rangealways worked; nothing proved it. See ADVANCED.md.
0.0.3 #
Documentation only. lib/ and src/ are still byte-identical to 0.0.1.
Fixed #
- The logo now renders on any background. It was a
<picture>with a near-black wordmark for light themes and a white one for dark, and the light variant did not appear on pub.dev. Both variants are replaced by one PNG whose wordmark is a single mid blue (#2a78d6), measured at 4.42:1 on white, 4.29:1 on GitHub dark and 3.90:1 on pub.dev dark — so one asset is legible everywhere and the theme switch is gone rather than fixed.
0.0.2 #
Documentation only. lib/ and src/ are byte-identical to 0.0.1, so upgrading
changes nothing at runtime.
Fixed #
- Asset links in the README, so the logo and benchmark charts actually render
on pub.dev. They now use absolute URLs rather than repository-relative ones.
pub.dev rewrites and proxies an
<img src>but leaves the<source srcset>of a<picture>alone, so every dark-theme variant resolved to nothing and readers on a dark theme saw blanks where the charts should be. A published README can only be corrected by publishing again, which is what this release is for.
0.0.1 #
First release.
A Flutter HTTP client whose transport is a C++ libcurl engine, reached over Nitro's FFI bridge rather than a platform channel. One engine serves iOS, Android, macOS, Windows and Linux, so proxies, TLS pinning, redirects, timeouts and cookies behave the same everywhere.
Added #
- HTTP/1.1, HTTP/2 and HTTP/3, negotiated automatically or forced per
client. Which are available depends on the libcurl actually linked, so the
engine asks it at runtime — see
NitroHttp.supportsHttp3. - Every verb, and seven body shapes — text, JSON, bytes, form, multipart,
stream and file.
HttpBody.fileuploads straight from disk without ever allocating a Dart buffer. - Streaming in both directions, with credit-based backpressure: a slow consumer throttles the socket instead of filling the Dart heap. Response chunks are handed over zero-copy.
- Cancellation with
CancelToken. The token lives in the engine, so a request bound to an already-cancelled one never opens a socket, and cancelling reaches every request sharing that token in a single call. - Timeouts for connect and total, plus a real idle deadline enforced by the
engine's own event loop.
TimeoutStagetells the three apart. - Progress callbacks and per-phase timings — DNS, connect, TLS and time-to-first-byte.
- A sealed Dart API.
HttpBody,HttpResponseandNitroHttpExceptionare sealed, so aswitchover them is exhaustive and the compiler catches a case you forgot. - Content decoding in the engine — gzip and deflate always, brotli and zstd when linked, bounded so a compression bomb cannot exhaust memory. It advertises exactly what it can decode, so results do not depend on how the local libcurl happened to be built.
- TLS — minimum and maximum version, custom or bundled root CAs, mutual TLS, and SPKI pinning per client or per request.
- Proxies and DNS — system, HTTP and SOCKS5 proxies, static host overrides, and DNS-over-HTTPS.
- Cookies — an in-memory or file-backed jar shared across a client's transfers.
- Interceptors and retry — async
beforeRequest/afterResponse/onError, so a token refresh is a normalawait, plus a retry interceptor with configurable backoff. - A disk cache implementing a subset of RFC 9111: freshness, revalidation
with
ETag/Last-Modified, LRU eviction against a byte budget, per-requestCacheMode, and an explicit prefetch API. - WebSockets on the same engine, inheriting its proxy and DNS behaviour,
and implementing
package:web_socket'sWebSocketinterface. (TLS settings did not reach them until 0.0.4.) - Adapters for
package:httpanddio, so existing call sites keep working. Thepackage:httpadapter passes the officialhttp_client_conformance_testssuite. - Hot restart needs nothing from you. The library reconciles native state
the first time a reloaded app touches the engine — aborting stragglers,
joining engine threads, flushing cookie jars — so there is no call to add to
main().
Built against nitro 0.6.1, which bounds the Android direct-buffer pool behind
@zeroCopy returns and settles batched completions on disposal rather than
dropping them.
Benchmarks against dart:io, package:http, dio and rhttp, measured in
release builds on real hardware, are in the README.
Known gaps #
- No web or WASM support. The engine is native code; this is a permanent
non-goal. Use
package:http'sBrowserClientbehindkIsWeb. - HTTP/3 is not guaranteed. It needs a libcurl with a QUIC backend, which a
system libcurl usually lacks. Without one,
HttpVersionPref.http3quietly negotiates HTTP/2 andhttp3Onlyfails the request. - WebSockets are HTTP/1.1 Upgrade only. RFC 8441 is not implemented, so a socket cannot share an HTTP/2 connection.
- No public-suffix validation for cookies. libpsl is not vendored yet, so
the jar cannot reject a
Domainnaming a public suffix — do not treat it as a security boundary against a hostile server. TlsSettings.sniHostnameis accepted but not applied. It round-trips through the configuration; the engine does not yet override SNI from it.- Binary size is 3.0–4.8 MB per ABI with the vendored stack (release APK:
2.96 MB armeabi-v7a, 4.53 MB arm64-v8a, 4.76 MB x86_64). Building the
dependencies with
--no-http3saves about 0.4 MB, not the 40 % this entry claimed at 0.0.1 — see 0.0.4. - Prebuilt binaries cover x64 and arm64 only. Windows arm64 has no slice and
needs a system libcurl, or one built locally with
tool/deps/build.ps1.