390 lines
12 KiB
Dart
390 lines
12 KiB
Dart
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', // 请替æ<C2BF>¢ä¸ºæ‚¨çš„应用 AppId
|
||
primaryServiceHost: 'httpdns-a.example.com', // 请替æ<C2BF>¢ä¸ºä¸»æœ<C3A6>务域å<C5B8>? backupServiceHost: 'httpdns-b.example.com', // å<>¯é€‰ï¼šå¤‡æœ<C3A6>务域å<C5B8>? servicePort: 443,
|
||
secretKey: 'your_sign_secret_here', // å<>¯é€‰ï¼šä»…验ç¾å¼€å<E282AC>¯æ—¶éœ€è¦? );
|
||
await TrustAPPHttpdns.setHttpsRequestEnabled(true);
|
||
await TrustAPPHttpdns.setLogEnabled(true);
|
||
await TrustAPPHttpdns.setPersistentCacheIPEnabled(true);
|
||
await TrustAPPHttpdns.setReuseExpiredIPEnabled(true);
|
||
await TrustAPPHttpdns.build();
|
||
|
||
// å…ˆbuildå†<C3A5>执行解æž<C3A6>相关动ä½?
|
||
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';
|
||
|
||
// 仅首次进入页é<C2B5>¢æ—¶åˆ<C3A5>å§‹åŒ?HTTPDNS
|
||
_initHttpDnsOnce();
|
||
|
||
// å…ˆåˆ<C3A5>始化HTTPDNSå†<C3A5>åˆ<C3A5>始化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 è§£æž<C3A6>当å‰<C3A5> URL çš?host 并显示结æž?
|
||
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 {
|
||
// ç¡®ä¿<C3A4>å<EFBFBD>ªåˆ<C3A5>始化一æ¬?
|
||
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输入�
|
||
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 è§£æž<C3A6>按钮
|
||
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),
|
||
|
||
// ä¿<C3A4>留空白分隔
|
||
const SizedBox(height: 16),
|
||
|
||
// å“<C3A5>应文本显示区域
|
||
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,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|