feat: sync httpdns sdk/platform updates without large binaries

This commit is contained in:
robin
2026-03-04 17:59:14 +08:00
parent 853897a6f8
commit 532891fad0
700 changed files with 6096 additions and 2712 deletions

View File

@@ -0,0 +1,389 @@
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'net/httpdns_http_client_adapter.dart';
import 'package:TrustAPP_httpdns/TrustAPP_httpdns.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'HTTP Request Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'HTTP Request Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
enum NetworkLibrary {
dio('Dio'),
httpClient('HttpClient'),
httpPackage('http');
const NetworkLibrary(this.displayName);
final String displayName;
}
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController _urlController = TextEditingController();
String _responseText = 'Response will appear here...';
bool _isLoading = false;
late final Dio _dio;
late final HttpClient _httpClient;
late final http.Client _httpPackageClient;
NetworkLibrary _selectedLibrary = NetworkLibrary.dio;
bool _httpdnsReady = false;
bool _httpdnsIniting = false;
Future<void> _initHttpDnsOnce() async {
if (_httpdnsReady || _httpdnsIniting) return;
_httpdnsIniting = true;
try {
await TrustAPPHttpdns.init(
appId: 'app1f1ndpo9', // 请替换为您的应用 AppId
primaryServiceHost: 'httpdns-a.example.com', // 请替换为主服务域<E58AA1><E59F9F>? backupServiceHost: 'httpdns-b.example.com', // 可选备服务域<E58AA1><E59F9F>? servicePort: 443,
secretKey: 'your_sign_secret_here', // 可选仅验签开启时需<E697B6><E99C80>? );
await TrustAPPHttpdns.setHttpsRequestEnabled(true);
await TrustAPPHttpdns.setLogEnabled(true);
await TrustAPPHttpdns.setPersistentCacheIPEnabled(true);
await TrustAPPHttpdns.setReuseExpiredIPEnabled(true);
await TrustAPPHttpdns.build();
// 先build再执行解析相关动<E585B3><E58AA8>?
final preResolveHosts = 'www.TrustAPP.com';
await TrustAPPHttpdns.setPreResolveHosts([preResolveHosts], ipType: 'both');
debugPrint('[httpdns] pre-resolve scheduled for host=$preResolveHosts');
_httpdnsReady = true;
} catch (e) {
debugPrint('[httpdns] init failed: $e');
} finally {
_httpdnsIniting = false;
}
}
@override
void initState() {
super.initState();
// 设置默认的API URL用于演示
_urlController.text = 'https://www.TrustAPP.com';
// 仅首次进入页面时初始<E5889D><E5A78B>?HTTPDNS
_initHttpDnsOnce();
// 先初始化HTTPDNS再初始化Dio
_dio = Dio();
_dio.httpClientAdapter = buildHttpdnsHttpClientAdapter();
_dio.options.headers['Connection'] = 'keep-alive';
_httpClient = buildHttpdnsNativeHttpClient();
_httpPackageClient = buildHttpdnsHttpPackageClient();
}
@override
void dispose() {
_urlController.dispose();
_httpClient.close();
_httpPackageClient.close();
super.dispose();
}
Future<void> _sendHttpRequest() async {
if (_urlController.text.isEmpty) {
setState(() {
_responseText = 'Error: Please enter a URL';
});
return;
}
setState(() {
_isLoading = true;
_responseText = 'Sending request...';
});
final uri = Uri.parse(_urlController.text);
try {
final String libraryName = _selectedLibrary.displayName;
debugPrint('[$libraryName] Sending request to ${uri.host}:${uri.port}');
int statusCode;
Map<String, String> headers;
String body;
switch (_selectedLibrary) {
case NetworkLibrary.dio:
final response = await _dio.getUri(
uri,
options: Options(
responseType: ResponseType.plain,
followRedirects: true,
validateStatus: (_) => true,
),
);
statusCode = response.statusCode ?? 0;
headers = {
for (final e in response.headers.map.entries)
e.key: e.value.join(','),
};
body = response.data is String
? response.data as String
: jsonEncode(response.data);
break;
case NetworkLibrary.httpClient:
final request = await _httpClient.getUrl(uri);
final response = await request.close();
statusCode = response.statusCode;
headers = {};
response.headers.forEach((name, values) {
headers[name] = values.join(',');
});
body = await response.transform(utf8.decoder).join();
break;
case NetworkLibrary.httpPackage:
final response = await _httpPackageClient.get(uri);
statusCode = response.statusCode;
headers = response.headers;
body = response.body;
break;
}
setState(() {
_isLoading = false;
final StringBuffer responseInfo = StringBuffer();
responseInfo.writeln('=== REQUEST ($libraryName) ===');
responseInfo.writeln('uri: ${uri.toString()}');
responseInfo.writeln();
responseInfo.writeln('=== STATUS ===');
responseInfo.writeln('statusCode: $statusCode');
responseInfo.writeln();
responseInfo.writeln('=== HEADERS ===');
headers.forEach((key, value) {
responseInfo.writeln('$key: $value');
});
responseInfo.writeln();
responseInfo.writeln('=== BODY ===');
if (statusCode >= 200 && statusCode < 300) {
try {
final jsonData = json.decode(body);
const encoder = JsonEncoder.withIndent(' ');
responseInfo.write(encoder.convert(jsonData));
} catch (_) {
responseInfo.write(body);
}
} else {
responseInfo.write(body);
}
_responseText = responseInfo.toString();
});
} catch (e) {
setState(() {
_isLoading = false;
_responseText = 'Network Error: $e';
});
}
}
// 使用 HTTPDNS 解析当前 URL <20><>?host 并显示结<E7A4BA><E7BB93>?
Future<void> _testHttpDnsResolve() async {
final text = _urlController.text.trim();
if (text.isEmpty) {
setState(() {
_responseText = 'Error: Please enter a URL';
});
return;
}
final Uri uri;
try {
uri = Uri.parse(text);
} catch (_) {
setState(() {
_responseText = 'Error: Invalid URL';
});
return;
}
setState(() {
_isLoading = true;
_responseText = 'Resolving with HTTPDNS...';
});
try {
// 确保只初始化一<E58C96><E4B880>?
await _initHttpDnsOnce();
final res = await TrustAPPHttpdns.resolveHostSyncNonBlocking(
uri.host,
ipType: 'both',
);
setState(() {
_isLoading = false;
final buf = StringBuffer();
buf.writeln('=== HTTPDNS RESOLVE ===');
buf.writeln('host: ${uri.host}');
final ipv4 = (res['ipv4'] as List?)?.cast<String>() ?? const <String>[];
final ipv6 = (res['ipv6'] as List?)?.cast<String>() ?? const <String>[];
if (ipv4.isNotEmpty) buf.writeln('IPv4 list: ${ipv4.join(', ')}');
if (ipv6.isNotEmpty) buf.writeln('IPv6 list: ${ipv6.join(', ')}');
_responseText = buf.toString();
});
} catch (e) {
setState(() {
_isLoading = false;
_responseText = 'HTTPDNS Error: $e';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// URL输入<E8BE93><E585A5>?
TextField(
controller: _urlController,
decoration: const InputDecoration(
labelText: 'Enter URL',
hintText: 'https://www.TrustAPP.com',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.link),
),
keyboardType: TextInputType.url,
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
flex: 3,
child: ElevatedButton.icon(
onPressed: _isLoading ? null : _sendHttpRequest,
icon: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.send),
label: Text(_isLoading ? 'Sending...' : 'Send Request'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
const SizedBox(width: 12),
Expanded(
flex: 2,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: DropdownButton<NetworkLibrary>(
value: _selectedLibrary,
isExpanded: true,
underline: const SizedBox(),
icon: const Icon(Icons.arrow_drop_down),
items: NetworkLibrary.values.map((library) {
return DropdownMenuItem<NetworkLibrary>(
value: library,
child: Text(library.displayName),
);
}).toList(),
onChanged: _isLoading
? null
: (NetworkLibrary? newValue) {
if (newValue != null) {
setState(() {
_selectedLibrary = newValue;
});
}
},
),
),
),
],
),
const SizedBox(height: 16),
// HTTPDNS 解析按钮
ElevatedButton.icon(
onPressed: _isLoading ? null : _testHttpDnsResolve,
icon: const Icon(Icons.dns),
label: const Text('HTTPDNS Resolve'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
const SizedBox(height: 16),
// 保留空白分隔
const SizedBox(height: 16),
// 响应文本显示区域
Expanded(
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
color: Colors.grey.shade50,
),
child: SingleChildScrollView(
child: Text(
_responseText,
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 12,
),
),
),
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,114 @@
import 'dart:io';
import 'package:dio/io.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';
import 'package:TrustAPP_httpdns/TrustAPP_httpdns.dart';
/* *
* 构建<E69E84><E5BBBA>?HTTPDNS 能力<E883BD><E58A9B>?IOHttpClientAdapter
*
* 本方案由EMAS团队设计实现参考请注明出处<E587BA><E5A484>?
*/
IOHttpClientAdapter buildHttpdnsHttpClientAdapter() {
final HttpClient client = HttpClient();
_configureHttpClient(client);
_configureConnectionFactory(client);
final IOHttpClientAdapter adapter = IOHttpClientAdapter(
createHttpClient: () => client,
)..validateCertificate = (cert, host, port) => true;
return adapter;
}
HttpClient buildHttpdnsNativeHttpClient() {
final HttpClient client = HttpClient();
_configureHttpClient(client);
_configureConnectionFactory(client);
return client;
}
http.Client buildHttpdnsHttpPackageClient() {
final HttpClient httpClient = buildHttpdnsNativeHttpClient();
return IOClient(httpClient);
}
// HttpClient 基础配置
void _configureHttpClient(HttpClient client) {
client.findProxy = (Uri _) => 'DIRECT';
client.idleTimeout = const Duration(seconds: 90);
client.maxConnectionsPerHost = 8;
}
// 配置基于 HTTPDNS 的连接工<E68EA5><E5B7A5>?
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再 TLSSNI=域名),并保持可取消
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);
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 列表IPv4 优先;失败则返回空列表(上层回退系统 DNS<4E><53>?
Future<List<InternetAddress>> _resolveTargets(String domain) async {
try {
final res = await TrustAPPHttpdns.resolveHostSyncNonBlocking(
domain,
ipType: 'both',
);
final List<String> ipv4 =
(res['ipv4'] as List?)?.cast<String>() ?? const <String>[];
final List<String> ipv6 =
(res['ipv6'] as List?)?.cast<String>() ?? const <String>[];
final List<InternetAddress> targets = [
...ipv4.map(InternetAddress.tryParse).whereType<InternetAddress>(),
...ipv6.map(InternetAddress.tryParse).whereType<InternetAddress>(),
];
if (targets.isEmpty) {
debugPrint('[HTTPDNS] no result for $domain, fallback to system DNS');
} else {
debugPrint('[HTTPDNS] resolved $domain -> ${targets.first.address}');
}
return targets;
} catch (e) {
debugPrint('[HTTPDNS] resolve failed: $e, fallback to system DNS');
return const <InternetAddress>[];
}
}