aliyun_httpdns 2.0.0
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集成:
- 创建自定义的HTTP客户端适配器,拦截网络请求
- 在适配器中调用HTTPDNS SDK解析域名为IP地址
- 使用解析得到的IP地址创建直接的Socket连接
- 对于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;
});