flutterdev.in › Packages › native_proxy_resolver

native_proxy_resolver

Resolve the OS proxy per URL, including PAC and WPAD auto-config, and plug it into dart:io HttpClient, package:http and Dio. Android, iOS, macOS, Windows, Linux.

v0.1.0160/160 pub pointsMITAndroidiOSmacOSWindowsLinux
native_proxy_resolver: ^0.1.0

Resolve the operating system's proxy for each URL — including PAC scripts and WPAD auto-discovery used on corporate networks — and plug the answer into dart:io HttpClient, package:http and Dio.

Example app on the iOS simulator: https://example.com/ resolves to DIRECT (source none) and ProxyAwareHttpClient gets HTTP 200

Why

Dart's HttpClient ignores the OS proxy settings (flutter/flutter#26359). Existing packages only read one static host:port (system_proxy is unmaintained since 2021, native_flutter_proxy is static only). In a company network the proxy is usually chosen per URL by a PAC script, so a static value is wrong for half the hosts. This plugin asks the OS resolver itself, so your app routes exactly like the platform's native networking stack.

Install

dependencies:
  native_proxy_resolver: ^0.1.0

Usage

Resolve a URL

import 'package:native_proxy_resolver/native_proxy_resolver.dart';

final proxies = await SystemProxy.resolve(Uri.parse('https://example.com'));
// [PROXY proxy.corp:8080, DIRECT]   or   [DIRECT]

final detail = await SystemProxy.resolveDetailed(Uri.parse('https://example.com'));
print(detail.source);   // pac, autoDetect, manual, environment, none
print(detail.pacUrl);   // http://wpad.corp/wpad.dat
print(detail.error);    // e.g. "PAC evaluation timed out" (entries fall back to DIRECT)
print(detail.toFindProxyString()); // "PROXY proxy.corp:8080; DIRECT"

Resolution never throws for resolver failures: you get [DIRECT] plus error. Results are cached per origin (scheme://host:port, TTL 5 min, failures 30 s, 256 origins LRU) and concurrent lookups share one native call. Tune it with your own resolver:

SystemProxy.resolver = SystemProxyResolver(
  cacheTtl: const Duration(minutes: 2),
  timeout: const Duration(seconds: 5),
);
SystemProxy.onChange.listen((_) => print('network or proxy changed'));

dart:io HttpClient

HttpClient.findProxy is a synchronous callback, but asking the OS (and running a PAC script) is asynchronous. ProxyAwareHttpClient solves this with pre-resolution: every getUrl/openUrl/post/… first awaits the proxy decision for the URL's origin, then opens the request on the wrapped client, whose findProxy reads the now-warm cache.

final client = ProxyAwareHttpClient();            // wraps HttpClient()
final response = await (await client.getUrl(url)).close();

Or make every HttpClient() in the app proxy-aware (covers SDKs, NetworkImage, package:http's default client on the VM…):

void main() {
  SystemProxy.installHttpOverrides();
  runApp(const MyApp());
}

Trade-offs you should know:

Strategy How Cost
Pre-resolve (default) ProxyAwareHttpClient() First request to an origin waits for the OS (≈ instant for static config; PAC download/WPAD bounded by timeout).
Warmed cache ProxyAwareHttpClient(preResolve: false) + SystemProxy.warmUp([...]) No waiting; a not-yet-warmed origin uses fallback (DIRECT) once and is resolved in the background.
Plain callback client.findProxy = SystemProxy.resolver.findProxyCallback() Same as warmed cache, for a client you already own.

Redirects are followed inside dart:io and only hit the synchronous callback; a redirect to a never-seen origin uses fallback for that hop. Set request.followRedirects = false if every hop must use its PAC decision.

package:http

import 'package:native_proxy_resolver/http.dart';

final client = createSystemProxyHttpClient();
final response = await client.get(Uri.parse('https://example.com'));
client.close();

Dio

The package does not depend on Dio. Copy example/lib/dio_integration.dart:

import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:native_proxy_resolver/native_proxy_resolver.dart';

Dio createSystemProxyDio({SystemProxyResolver? resolver}) {
  final dio = Dio();
  dio.httpClientAdapter = IOHttpClientAdapter(
    createHttpClient: () => ProxyAwareHttpClient(resolver: resolver),
  );
  return dio;
}

Helpers

Platform support

Platform Mechanism PAC WPAD Change events
Android 7+ (API 24) ProxySelector.select(URI) + ConnectivityManager.getDefaultProxy() Yes — via Android's local PAC proxy (localhost:<port>) Where the OS supports it Yes
iOS 13+ CFNetworkCopyProxiesForURL; PAC run with CFNetworkExecuteProxyAutoConfigurationURL/Script on a background run loop with timeout Yes Yes Network path changes
macOS 10.15+ same as iOS Yes Yes Yes (SCDynamicStore proxies key + path)
Windows 10+ WinHttpGetIEProxyConfigForCurrentUser + WinHttpGetProxyForUrl (auto-detect DHCP/DNS, PAC URL, auto-logon retry), worker thread Yes Yes No — cache TTL
Linux http_proxy/https_proxy/all_proxy/no_proxy, else GNOME gsettings No (reported) No GNOME (gsettings monitor)
Web not supported (the browser applies its own proxy)

macOS apps need the com.apple.security.network.client entitlement to download PAC scripts from inside the sandbox. Android adds ACCESS_NETWORK_STATE to your manifest.

Limitations

Example

example/ resolves any URL, shows source/PAC/errors, fetches it through ProxyAwareHttpClient, package:http, Dio and a plain HttpClient() with the global overrides toggled, and lists change events.

More Flutter packages