aliyun_httpdns 2.0.0 copy "aliyun_httpdns: ^2.0.0" to clipboard
aliyun_httpdns: ^2.0.0 copied to clipboard

Aliyun HTTPDNS Flutter SDK - Pure Dart implementation for DNS resolution service.

Aliyun HTTPDNS Flutter Plugin #

阿里云EMAS HTTPDNS Flutter SDK,纯 Dart 实现,提供域名解析服务。

一、快速入门 #

1.1 开通服务 #

请参考快速入门文档开通HTTPDNS。

1.2 获取配置 #

请参考开发配置文档在EMAS控制台开发配置中获取AccountId/SecretKey/AESSecretKey等信息,用于初始化SDK。

二、安装 #

pubspec.yaml中加入dependencies:

dependencies:
  aliyun_httpdns: ^2.0.0

添加依赖之后需要执行一次 flutter pub get

三、配置和使用 #

3.1 初始化配置 #

应用启动后,需要先初始化SDK,才能调用HTTPDNS能力。 初始化主要是配置AccountId/SecretKey等信息及功能开关。 示例代码如下:

import 'package:aliyun_httpdns/aliyun_httpdns.dart';

// 创建配置
final config = AliyunHttpdnsConfig(
  accountId: 您的AccountId,                 // 必填
  secretKey: '您的SecretKey',               // 可选
  httpsEnabled: true,                       // 默认 true
  persistentCacheEnabled: true,             // 持久化缓存
  reuseExpiredIPEnabled: true,              // 复用过期 IP
  preResolveAfterNetworkChanged: true,      // 网络变化后预解析
  ipRankingDatasource: {                    // IP 优选配置(可选)
    'www.aliyun.com': 443,
  },
);

// 初始化 SDK
await AliyunHttpdns.init(config);

// 开启日志(可选)
AliyunHttpdns.setLogEnabled(true);

// 设置预解析域名
await AliyunHttpdns.setPreResolveHosts(['www.aliyun.com']);

3.1.1 日志配置

应用开发过程中,如果要输出HTTPDNS的日志,可以调用日志输出控制方法,开启日志,示例代码如下:

AliyunHttpdns.setLogEnabled(true);

// 自定义日志处理器(可选)
AliyunHttpdns.setLogHandler((message) {
  print('[HTTPDNS] $message');
});

3.1.2 sessionId记录

应用在运行过程中,可以调用获取SessionId方法获取sessionId,记录到应用的数据采集系统中。 sessionId用于表示标识一次应用运行,线上排查时,可以用于查询应用一次运行过程中的解析日志,示例代码如下:

final sessionId = AliyunHttpdns.getSessionId();
print("SessionId = $sessionId");

3.2 域名解析 #

3.2.1 预解析

当需要提前解析域名时,可以调用预解析域名方法,示例代码如下:

await AliyunHttpdns.setPreResolveHosts(["www.aliyun.com", "www.example.com"]);

调用之后,SDK会发起域名解析,并把结果缓存到内存,用于后续请求时直接使用。

3.2.2 域名解析 #

当需要解析域名时,可以通过调用域名解析方法解析域名获取IP,示例代码如下:

Future<void> _resolve() async {
  final result = await AliyunHttpdns.resolveHostSyncNonBlocking(
    'www.aliyun.com',
    ipType: HttpdnsIpType.auto,
  );
  if (result != null) {
    print('IPv4: ${result.ips}');
    print('IPv6: ${result.ipv6s}');
  }
}

四、Flutter最佳实践 #

4.1 原理说明 #

本示例展示了一种更直接的集成方式,通过自定义HTTP客户端适配器来实现HTTPDNS集成:

  1. 创建自定义的HTTP客户端适配器,拦截网络请求
  2. 在适配器中调用HTTPDNS SDK解析域名为IP地址
  3. 使用解析得到的IP地址创建直接的Socket连接
  4. 对于HTTPS连接,确保正确设置SNI(Server Name Indication)为原始域名

这种方式避免了创建本地代理服务的复杂性,直接在HTTP客户端层面集成HTTPDNS功能。

4.2 示例说明 #

完整应用示例请参考demo应用。

4.2.1 自定义HTTP客户端适配器实现

自定义适配器的实现请参考demo/lib/net/httpdns_http_client_adapter.dart文件。本方案由EMAS团队设计实现,参考请注明出处。 适配器内部会拦截HTTP请求,调用HTTPDNS进行域名解析,并使用解析后的IP创建socket连接。

本示例支持三种网络库:Dio、HttpClient、http包。代码如下:

import 'dart:io';
import 'package:dio/io.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';
import 'package:flutter/foundation.dart';
import 'package:aliyun_httpdns/aliyun_httpdns.dart';

// Dio 适配器
IOHttpClientAdapter buildHttpdnsHttpClientAdapter() {
  final HttpClient client = HttpClient();
  _configureHttpClient(client);
  _configureConnectionFactory(client);

  final IOHttpClientAdapter adapter = IOHttpClientAdapter(createHttpClient: () => client)
    ..validateCertificate = (cert, host, port) => true;
  return adapter;
}

// 原生 HttpClient
HttpClient buildHttpdnsNativeHttpClient() {
  final HttpClient client = HttpClient();
  _configureHttpClient(client);
  _configureConnectionFactory(client);
  return client;
}

// http 包适配器
http.Client buildHttpdnsHttpPackageClient() {
  final HttpClient httpClient = buildHttpdnsNativeHttpClient();
  return IOClient(httpClient);
}

// HttpClient 基础配置
void _configureHttpClient(HttpClient client) {
  client.findProxy = (Uri _) => 'DIRECT';
  client.idleTimeout = const Duration(seconds: 30);
  client.maxConnectionsPerHost = 8;
}

// 配置基于 HTTPDNS 的连接工厂
// 本方案由EMAS团队设计实现,参考请注明出处。
void _configureConnectionFactory(HttpClient client) {
  client.connectionFactory = (Uri uri, String? proxyHost, int? proxyPort) async {
    final String domain = uri.host;
    final bool https = uri.scheme.toLowerCase() == 'https';
    final int port = uri.port == 0 ? (https ? 443 : 80) : uri.port;

    final List<InternetAddress> targets = await _resolveTargets(domain);
    final Object target = targets.isNotEmpty ? targets.first : domain;

    if (!https) {
      return Socket.startConnect(target, port);
    }

    // HTTPS:先 TCP,再 TLS(SNI=域名),并保持可取消
    bool cancelled = false;
    final Future<ConnectionTask<Socket>> rawStart = Socket.startConnect(target, port);
    final Future<Socket> upgraded = rawStart.then((task) async {
      final Socket raw = await task.socket;
      if (cancelled) {
        raw.destroy();
        throw const SocketException('Connection cancelled');
      }
      final SecureSocket secure = await SecureSocket.secure(
        raw,
        host: domain, // 重要:使用原始域名作为SNI
      );
      if (cancelled) {
        secure.destroy();
        throw const SocketException('Connection cancelled');
      }
      return secure;
    });
    return ConnectionTask.fromSocket(
      upgraded,
      () {
        cancelled = true;
        try {
          rawStart.then((t) => t.cancel());
        } catch (_) {}
      },
    );
  };
}

// 通过 HTTPDNS 解析目标 IP 列表
Future<List<InternetAddress>> _resolveTargets(String domain) async {
  try {
    final result = await AliyunHttpdns.resolveHostSyncNonBlocking(
      domain,
      ipType: HttpdnsIpType.auto,
    );
    final List<String> ipv4 = result?.ips ?? [];
    final List<String> ipv6 = result?.ipv6s ?? [];
    final List<InternetAddress> targets = [
      ...ipv4.map(InternetAddress.tryParse).whereType<InternetAddress>(),
      ...ipv6.map(InternetAddress.tryParse).whereType<InternetAddress>(),
    ];
    if (targets.isEmpty) {
      debugPrint('[dio] HTTPDNS no result for $domain, fallback to system DNS');
    } else {
      debugPrint('[dio] HTTPDNS resolved $domain -> ${targets.first.address}');
    }
    return targets;
  } catch (e) {
    debugPrint('[dio] HTTPDNS resolve failed: $e, fallback to system DNS');
    return const <InternetAddress>[];
  }
}

4.2.2 适配器集成和使用

适配器的集成请参考demo/lib/main.dart文件。 首先需要初始化HTTPDNS,然后配置网络库使用自定义适配器,示例代码如下:

class _MyHomePageState extends State<MyHomePage> {
  late final Dio _dio;
  late final HttpClient _httpClient;
  late final http.Client _httpPackageClient;

  @override
  void initState() {
    super.initState();
    
    // 初始化 HTTPDNS
    _initHttpDnsOnce();
    
    // 配置网络库使用 HTTPDNS 适配器
    _dio = Dio();
    _dio.httpClientAdapter = buildHttpdnsHttpClientAdapter();
    _dio.options.headers['Connection'] = 'keep-alive';
    
    _httpClient = buildHttpdnsNativeHttpClient();
    _httpPackageClient = buildHttpdnsHttpPackageClient();
  }

  Future<void> _initHttpDnsOnce() async {
    try {
      final config = AliyunHttpdnsConfig(
        accountId: 您的AccountId,
        secretKey: '您的SecretKey',
        httpsEnabled: true,
        reuseExpiredIPEnabled: true,
      );
      await AliyunHttpdns.init(config);
      AliyunHttpdns.setLogEnabled(true);
      
      // 设置预解析域名
      await AliyunHttpdns.setPreResolveHosts(['www.aliyun.com']);
    } catch (e) {
      debugPrint('[httpdns] init failed: $e');
    }
  }
}

使用配置好的网络库发起请求时,会自动使用HTTPDNS进行域名解析:

// 使用 Dio
final response = await _dio.get('https://www.aliyun.com');

// 使用 HttpClient
final request = await _httpClient.getUrl(Uri.parse('https://www.aliyun.com'));
final response = await request.close();

// 使用 http 包
final response = await _httpPackageClient.get(Uri.parse('https://www.aliyun.com'));

4.2.3 资源清理

在组件销毁时,记得清理相关资源:

@override
void dispose() {
  _urlController.dispose();
  _httpClient.close();
  _httpPackageClient.close();
  super.dispose();
}

五、API #

5.1 日志输出控制 #

控制是否打印Log。

AliyunHttpdns.setLogEnabled(true);

5.2 初始化 #

初始化配置, 在应用启动时调用。

import 'package:aliyun_httpdns/aliyun_httpdns.dart';

// 创建配置
final config = AliyunHttpdnsConfig(
  accountId: 您的AccountId,                 // 必填
  secretKey: 'your_secret_key',            // 可选
  aesSecretKey: 'your_aes_secret_key',     // 可选
  region: HttpdnsRegion.cn,                // 默认中国大陆
  httpsEnabled: true,                       // 默认 true
  persistentCacheEnabled: true,             // 持久化缓存
  reuseExpiredIPEnabled: true,              // 复用过期 IP
  preResolveAfterNetworkChanged: true,      // 网络变化后预解析
  ipRankingDatasource: {                    // IP 优选配置
    'www.aliyun.com': 443,
  },
  timeout: 2,                               // 请求超时(秒)
);

// 初始化 SDK
await AliyunHttpdns.init(config);

// 开启日志(可选)
AliyunHttpdns.setLogEnabled(true);

配置参数 (AliyunHttpdnsConfig):

参数名 类型 是否必须 默认值 功能
accountId int 必选参数 - Account ID
secretKey String? 可选参数 null 加签密钥
aesSecretKey String? 可选参数 null 加密密钥
region HttpdnsRegion 可选参数 cn 服务区域
httpsEnabled bool 可选参数 true 是否使用HTTPS解析链路
persistentCacheEnabled bool 可选参数 false 是否开启持久化缓存
reuseExpiredIPEnabled bool 可选参数 false 是否允许复用过期IP
preResolveAfterNetworkChanged bool 可选参数 false 网络切换时是否自动刷新解析
discardExpiredAfterSeconds int? 可选参数 null 过期缓存丢弃时间(秒)
ipRankingDatasource Map<String, int>? 可选参数 null IP优选配置
timeout int 可选参数 2 请求超时时间(秒)

HttpdnsRegion 可选值: cn(中国大陆), hk(香港), sg(新加坡), de(德国), us(美国)

5.3 域名解析 #

解析指定域名。SDK 提供三种解析方式:

// 1. 同步非阻塞解析(推荐)
// 立即返回缓存结果,未命中时返回 null 并后台请求
final result = await AliyunHttpdns.resolveHostSyncNonBlocking(
  'www.aliyun.com',
  ipType: HttpdnsIpType.auto,
);
if (result != null) {
  print('IPv4: ${result.ips}');
  print('IPv6: ${result.ipv6s}');
}

// 2. 同步阻塞解析
// 阻塞等待直到获取结果或超时
final result2 = await AliyunHttpdns.resolveHostSync(
  'www.aliyun.com',
  ipType: HttpdnsIpType.v4,
);

// 3. 异步回调解析
await AliyunHttpdns.resolveHostAsync(
  'www.aliyun.com',
  ipType: HttpdnsIpType.auto,
  callback: (result) {
    print('解析结果: $result');
  },
);

参数:

参数名 类型 是否必须 功能
host String 必选参数 要解析的域名
ipType HttpdnsIpType 可选参数 请求IP类型: auto, v4, v6, both
sdnsParams Map<String, String>? 可选参数 SDNS参数
cacheKey String? 可选参数 自定义缓存Key

返回数据结构 (ResolveResult):

字段名 类型 功能
host String 域名
ips List IPv4地址列表
ipv6s List IPv6地址列表
ttl int IPv4 TTL(秒)
v6ttl int IPv6 TTL(秒)

5.4 预解析域名 #

预解析域名, 解析后缓存在SDK中,下次解析时直接从缓存中获取,提高解析速度。

await AliyunHttpdns.setPreResolveHosts(["www.aliyun.com"]);

参数:

参数名 类型 是否必须 功能
hosts List 必选参数 预解析域名列表
ipType HttpdnsIpType 可选参数 请求IP类型: auto, v4, v6, both

5.5 获取SessionId #

获取SessionId, 用于排查追踪问题。

final sessionId = AliyunHttpdns.getSessionId();
print("SessionId = $sessionId");

无需参数,直接返回当前会话ID。

5.6 清除缓存 #

清除DNS解析缓存。

// 清除所有缓存
await AliyunHttpdns.cleanHostCache(null);

// 清除指定域名缓存
await AliyunHttpdns.cleanHostCache(['www.aliyun.com']);

5.7 其他配置说明 #

以下配置均通过 AliyunHttpdnsConfig 在初始化时设置:

持久化缓存

开启后,SDK 会将解析结果保存到本地,App 重启后可以从本地加载缓存,提升首屏加载速度。

final config = AliyunHttpdnsConfig(
  accountId: 您的AccountId,
  persistentCacheEnabled: true,              // 开启持久化缓存
  discardExpiredAfterSeconds: 86400,         // 过期超过1天的缓存丢弃
);

网络变化时自动刷新预解析

final config = AliyunHttpdnsConfig(
  accountId: 您的AccountId,
  preResolveAfterNetworkChanged: true,       // 网络变化时自动刷新
);

IP 优选

开启后,SDK 会对解析返回的 IP 列表进行 TCP 测速并排序,优先返回连接速度最快的 IP。

final config = AliyunHttpdnsConfig(
  accountId: 您的AccountId,
  ipRankingDatasource: {                     // 域名和端口的映射
    'www.aliyun.com': 443,
    'api.example.com': 443,
  },
);

5.8 自定义 TTL #

可以通过设置回调函数来修改解析结果的 TTL。

AliyunHttpdns.setTtlChanger((host, ipType, ttl) {
  // 返回修改后的 TTL
  return ttl * 2;
});
2
likes
135
points
305
downloads

Publisher

unverified uploader

Weekly Downloads

Aliyun HTTPDNS Flutter SDK - Pure Dart implementation for DNS resolution service.

Homepage

Documentation

API reference

License

MIT (license)

Dependencies

connectivity_plus, crypto, encrypt, flutter, hive, hive_flutter

More

Packages that depend on aliyun_httpdns