nitro_http 0.0.5 copy "nitro_http: ^0.0.5" to clipboard
nitro_http: ^0.0.5 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.5 #

Added #

  • Web support — runs under flutter build web (dart2js and --wasm), served by fetch. See the web section.
  • WebSockets on web, on the browser's own WebSocket; NitroWebSocket's API is unchanged. Request headers, pingInterval and TLS settings throw there.
  • Per-phase HttpTimings on web from Resource Timing, reported as time from the start of the request, as on native.
  • Per-request CacheMode on web, mapped onto fetch's cache modes.
  • Timeouts, download progress, cancellation and cookies now work on web.
  • Web capability queries: engineVersion is fetch (browser), supportsHttp3 is false, supportsWebSockets is true.
  • ClientSettings.maxResponseBytes — refuses a response body over this size, counted after decoding. A declared Content-Length fails before a byte is read; HEAD is exempt. Enforced on web by counting.
  • NitroHttpClient.downloadToFile(url, path) — the engine writes the body to disk; the response carries status, headers and timings with an empty bodyBytes. A 4xx or 5xx is not written and the file is removed. Throws on web.
  • ClientSettings.hstsCachePath — persists Strict-Transport-Security across launches, so a known-HTTPS host is not reached over plaintext first.
  • ClientSettings.unixSocketPath, networkInterface — send over a unix domain socket, or bind outgoing connections to an interface or address. Native.
  • ClientSettings.keepAlive, referrerPolicy — web only; a keepalive request outlives the page that started it.
  • ClientSettings.unsupportedSettingsignore accepts settings a browser cannot honour instead of throwing, for one configuration across native and web. Defaults to reject.
  • CURLOPT_PIPEWAIT — concurrent requests wait to discover multiplexing rather than opening a second connection. Over 73 transfers to an h2 origin, 12 concurrent per round on a cold pool: 37 new connections before, 7 after, with latency unchanged (82 ms vs 80 ms median). HTTP/1.1 is unaffected (40 ms either way).

Fixed #

  • CR, LF or NUL in a header name or value is rejected. User input in a header could previously split the request and inject another one; verified on a raw socket. HttpHeaders.add/set now throw ArgumentError.
  • TlsSettings.sniHostname is applied. It round-tripped and did nothing. It now rewrites the URL host and pins the connection to the real endpoint with CURLOPT_CONNECT_TO; a name the certificate does not carry is rejected.
  • The Alt-Svc cache file was never written, so HTTP/3 discovery could not survive a launch. curl saves the Alt-Svc and HSTS files only from curl_easy_cleanup, and pooled handles were recycled with curl_easy_reset. A handle carrying either cache is no longer pooled; connection reuse is unaffected.
  • NitroHttpRedirectException.redirectCount carried the CURLcode (always 47) instead of the hops followed.
  • Streamed uploads work on web. Streamed where the browser accepts a ReadableStream body, buffered elsewhere.
  • Streams delivered nothing on nitro 0.7.4 (#2) — 0.7.4 partitions stream ports by emitting instance, and the sink used a different instance from the one Dart subscribed on.

Changed #

  • The dio adapter ships inside nitro_http (#1) — import package:nitro_http/dio.dart instead of the never-published nitro_http_dio package. The API is unchanged; dio is now a dependency.
  • Engine settings a browser cannot honour — TLS pinning, mTLS, custom roots, proxies, DNS, HTTP version, the pool, timings, the cookie jar and the disk cache — throw NitroHttpConfigurationException on web rather than being ignored.
  • Sentinel fields on the generated Raw* records now default instead of being required. The wire format is unchanged.
  • Upgraded to nitro 0.7.5 from 0.7.0. Generated files carry a spec-sha256 stamp and a pinned formatter, so regenerating is reproducible across Flutter SDKs.

0.0.4 #

Fixed #

  • RootCaSource.none verified nothing instead of trusting nothing — every chain was accepted. It is now refused unless pinnedSpkiSha256 is set; use TlsSettings.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 that RootCaSource.platform on Apple is then served by that bundle, so a root added to the Keychain by a user or MDM profile is not trusted — use RootCaSource.custom with your own PEM. Android was never affected.

  • wss:// segfaulted against any HTTP/2-capable server. The handshake pins HTTP/1.1. Plain ws:// was unaffected.

  • StreamChunkSettings.minContentLength: 0 was read as "unset" and replaced with the 1 MiB default, so nothing under 1 MiB batched.

  • StreamChunkSettings.maxHold never 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.proceedRequest and Interceptor.proceedResponse are const pass-through results.

  • NitroHttpTlsException for a handshake that failed before any certificate, and NitroHttpConfigurationException for settings refused before a socket opens. Split out of NitroHttpCertificateException and NitroHttpUnknownException.

  • NitroWebSocket.connect accepts tlsSettings, so wss:// 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 0 where it is read, so 0 means zero.

Migration #

  • NitroHttpException is sealed: exhaustive switches need NitroHttpTlsException and NitroHttpConfigurationException.

  • An Interceptor subclass that returns super.beforeRequest(…) from a method declared Future<…> must widen its own return type to FutureOr. Ordinary async overrides are unaffected.

  • InterceptorChain.runOnError throws synchronously for an unhandled failure rather than returning a rejected future. await behaves 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.sniHostname is 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. Range and Content-Range always 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.file uploads 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. TimeoutStage tells the three apart.
  • Progress callbacks and per-phase timings — DNS, connect, TLS and time-to-first-byte.
  • A sealed Dart API. HttpBody, HttpResponse and NitroHttpException are sealed, so a switch over 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 normal await, 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-request CacheMode, and an explicit prefetch API.
  • WebSockets on the same engine, inheriting its proxy and DNS behaviour, and implementing package:web_socket's WebSocket interface. (TLS settings did not reach them until 0.0.4.)
  • Adapters for package:http and dio, so existing call sites keep working. The package:http adapter passes the official http_client_conformance_tests suite.
  • 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's BrowserClient behind kIsWeb.
  • HTTP/3 is not guaranteed. It needs a libcurl with a QUIC backend, which a system libcurl usually lacks. Without one, HttpVersionPref.http3 quietly negotiates HTTP/2 and http3Only fails 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 Domain naming a public suffix — do not treat it as a security boundary against a hostile server.
  • TlsSettings.sniHostname is 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-http3 saves 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.
19
likes
110
points
286
downloads

Documentation

Documentation
API reference

Publisher

verified publishershreeman.dev

Weekly Downloads

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.

Repository (GitHub)
View/report issues

Topics

#http #network #ffi #nitro #websocket

Funding

Consider supporting this project:

buymeacoffee.com

License

MIT (license)

Dependencies

dio, flutter, flutter_web_plugins, http, nitro, web, web_socket

More

Packages that depend on nitro_http

Packages that implement nitro_http