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

example/lib/main.dart

/// The `nitro_http` demo app.
///
/// Six tabs, each a working screen rather than a placeholder, all pointed at an
/// in-process `shelf` server on `127.0.0.1`. That means the app is fully usable
/// with no network at all, and nothing you press here leaves the device.
library;

import 'package:flutter/material.dart';
import 'package:nitro_http/nitro_http.dart';

import 'benchmark/release_runner.dart';
import 'core/app_state.dart' as app_state;
import 'screens/benchmark/benchmark_screen.dart';
import 'screens/console/request_console_screen.dart';
import 'theme/app_theme.dart';
import 'server/local_server.dart';
import 'tabs/cache_tab.dart';
import 'tabs/cookies_tab.dart';
import 'tabs/streaming_tab.dart';
import 'tabs/websocket_tab.dart';
import 'widgets/panels.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  // Built with `--dart-define=NITRO_HTTP_BENCHMARK=1`: run the benchmark and
  // print it to the platform log instead of showing the UI. This exists because
  // `flutter drive` cannot run in release mode, and release is the only build
  // whose numbers are worth quoting. `kBenchmarkMode` is a const, so an ordinary
  // build tree-shakes all of this away.
  if (kBenchmarkMode) {
    // A frame still has to be pumped: the engine needs a live view to keep the
    // isolate scheduled, and on some targets the process exits without one.
    runApp(const _BenchmarkHost());
    return;
  }

  // Nothing here recovers from a hot restart: the library reconciles native
  // state itself on the first touch of a new isolate incarnation.
  //
  // This probe exists only to fail EARLY and legibly. Reading a capability is
  // the cheapest way to reach native, so a build with no engine library linked
  // says so on the first frame instead of throwing from somewhere inside the
  // first request — and an app that dies in `main` cannot explain why.
  Object? startupError;
  try {
    NitroHttp.engineVersion;
  } on Object catch (error) {
    startupError = error;
  }

  runApp(NitroHttpDemoApp(startupError: startupError));
}

/// Shows one line and runs the benchmark. Only ever built when
/// [kBenchmarkMode] is set; see `benchmark/release_runner.dart`.
class _BenchmarkHost extends StatefulWidget {
  const _BenchmarkHost();

  @override
  State<_BenchmarkHost> createState() => _BenchmarkHostState();
}

class _BenchmarkHostState extends State<_BenchmarkHost> {
  @override
  void initState() {
    super.initState();
    // After the first frame, so the run is never measuring startup contention
    // with the engine bringing up its first view.
    WidgetsBinding.instance.addPostFrameCallback((_) => runReleaseBenchmark());
  }

  @override
  Widget build(BuildContext context) => const MaterialApp(
    home: Scaffold(
      body: Center(child: Text('Running benchmark — see the platform log.')),
    ),
  );
}

/// A snapshot of the native engine's identity and feature flags.
final class EngineInfo {
  const EngineInfo._({
    required this.version,
    required this.supportsHttp3,
    required this.supportsWebSockets,
    required this.supportsBrotli,
    required this.supportsZstd,
    required this.error,
  });

  /// Reads the engine's version and capabilities, capturing a native failure
  /// rather than propagating it.
  factory EngineInfo.probe() {
    try {
      return EngineInfo._(
        version: NitroHttp.engineVersion,
        supportsHttp3: NitroHttp.supportsHttp3,
        supportsWebSockets: NitroHttp.supportsWebSockets,
        supportsBrotli: NitroHttp.supportsBrotli,
        supportsZstd: NitroHttp.supportsZstd,
        error: null,
      );
    } on Object catch (error) {
      return EngineInfo._(
        version: 'unavailable',
        supportsHttp3: false,
        supportsWebSockets: false,
        supportsBrotli: false,
        supportsZstd: false,
        error: error,
      );
    }
  }

  /// The native stack string, e.g. `libcurl/8.21.0 OpenSSL/3.5.0`.
  final String version;

  /// Whether the linked libcurl has a QUIC backend.
  final bool supportsHttp3;

  /// Whether the engine can speak the WebSocket protocol.
  final bool supportsWebSockets;

  /// Whether brotli response bodies are decoded natively.
  final bool supportsBrotli;

  /// Whether zstd response bodies are decoded natively.
  final bool supportsZstd;

  /// The failure raised while probing, or `null` when the probe succeeded.
  final Object? error;

  /// Whether the engine answered at all.
  bool get isAvailable => error == null;
}

/// Root widget: owns the local server and the tab scaffold.
class NitroHttpDemoApp extends StatefulWidget {
  /// Creates the app. [startupError] is whatever `NitroHttp.reset()` threw.
  const NitroHttpDemoApp({this.startupError, super.key});

  /// The failure from the hot-restart reset in `main`, or `null`.
  final Object? startupError;

  @override
  State<NitroHttpDemoApp> createState() => _NitroHttpDemoAppState();
}

class _NitroHttpDemoAppState extends State<NitroHttpDemoApp> {
  LocalServer? _server;
  Object? _serverError;

  @override
  void initState() {
    super.initState();
    _startServer();
  }

  Future<void> _startServer() async {
    try {
      final server = await LocalServer.start();
      if (!mounted) {
        await server.stop();
        return;
      }
      app_state.baseUrl.value = server.baseUrl;
      setState(() {
        _server = server;
        _serverError = null;
      });
    } on Object catch (error) {
      if (!mounted) return;
      setState(() => _serverError = error);
    }
  }

  @override
  void dispose() {
    app_state.baseUrl.value = '';
    _server?.stop();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'nitro_http',
      debugShowCheckedModeBanner: false,
      theme: buildAppTheme(Brightness.light),
      darkTheme: buildAppTheme(Brightness.dark),
      home: Builder(
        builder: (context) {
          final server = _server;
          final error = _serverError;
          if (error != null) {
            return Scaffold(
              appBar: AppBar(title: const Text('nitro_http')),
              body: EngineErrorCard(
                action: 'start the local test server',
                error: error,
                onRetry: () {
                  setState(() => _serverError = null);
                  _startServer();
                },
              ),
            );
          }
          if (server == null) {
            return const Scaffold(
              body: Center(child: CircularProgressIndicator()),
            );
          }
          return _HomePage(
            server: server,
            startupError: widget.startupError,
          );
        },
      ),
    );
  }
}

class _HomePage extends StatefulWidget {
  const _HomePage({required this.server, required this.startupError});

  final LocalServer server;
  final Object? startupError;

  @override
  State<_HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<_HomePage> {
  late EngineInfo _engine = EngineInfo.probe();

  static const List<Tab> _tabs = [
    Tab(text: 'Console', icon: Icon(Icons.terminal)),
    Tab(text: 'Streaming', icon: Icon(Icons.waves)),
    Tab(text: 'WebSocket', icon: Icon(Icons.cable)),
    Tab(text: 'Cache', icon: Icon(Icons.sd_storage)),
    Tab(text: 'Cookies', icon: Icon(Icons.cookie)),
    Tab(text: 'Benchmark', icon: Icon(Icons.speed)),
  ];

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: _tabs.length,
      child: Scaffold(
        appBar: AppBar(
          titleSpacing: 12,
          title: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text('nitro_http'),
              Text(
                _engine.version,
                style: const TextStyle(fontSize: 11, fontFamily: 'monospace'),
                overflow: TextOverflow.ellipsis,
              ),
              Text(
                widget.server.baseUrl,
                style: const TextStyle(fontSize: 11, fontFamily: 'monospace'),
              ),
            ],
          ),
          actions: [
            IconButton(
              tooltip: 'Re-probe the engine',
              onPressed: () => setState(() => _engine = EngineInfo.probe()),
              icon: const Icon(Icons.refresh),
            ),
          ],
          bottom: TabBar(isScrollable: true, tabs: _tabs),
        ),
        body: Column(
          children: [
            _CapabilityBar(engine: _engine),
            if (!_engine.isAvailable)
              EngineErrorCard(
                action: 'reach the native engine',
                error: widget.startupError ?? _engine.error!,
                onRetry: () => setState(() => _engine = EngineInfo.probe()),
              ),
            Expanded(
              child: TabBarView(
                children: [
                  const RequestConsoleScreen(),
                  StreamingTab(server: widget.server),
                  WebSocketTab(server: widget.server),
                  CacheTab(server: widget.server),
                  CookiesTab(server: widget.server),
                  const BenchmarkScreen(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _CapabilityBar extends StatelessWidget {
  const _CapabilityBar({required this.engine});

  final EngineInfo engine;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 44,
      child: ListView(
        scrollDirection: Axis.horizontal,
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
        children: [
          CapabilityChip(label: 'engine', enabled: engine.isAvailable),
          CapabilityChip(label: 'HTTP/3', enabled: engine.supportsHttp3),
          CapabilityChip(label: 'WebSocket', enabled: engine.supportsWebSockets),
          CapabilityChip(label: 'brotli', enabled: engine.supportsBrotli),
          CapabilityChip(label: 'zstd', enabled: engine.supportsZstd),
        ],
      ),
    );
  }
}
18
likes
140
points
107
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

flutter, http, nitro, web_socket

More

Packages that depend on nitro_http

Packages that implement nitro_http