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:new_httpdns/new_httpdns.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @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 createState() => _MyHomePageState(); } enum NetworkLibrary { dio('Dio'), httpClient('HttpClient'), httpPackage('http'); const NetworkLibrary(this.displayName); final String displayName; } class _MyHomePageState extends State { 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 _initHttpDnsOnce() async { if (_httpdnsReady || _httpdnsIniting) return; _httpdnsIniting = true; try { // 使用官方测试参数 await TrustAPPHttpdns.init( appId: 'app1flndpo9', apiUrl: 'https://httpdns.deepwaf.xyz:8445', secretKey: 'ss_67fb8471a45b', ); await TrustAPPHttpdns.setHttpsRequestEnabled(true); await TrustAPPHttpdns.setLogEnabled(true); await TrustAPPHttpdns.setPersistentCacheIPEnabled(true); await TrustAPPHttpdns.setReuseExpiredIPEnabled(true); await TrustAPPHttpdns.build(); // 先 build 再执行解析相关动作 final preResolveHosts = 'demo.cloudxdr.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://demo.cloudxdr.com'; // 仅首次进入页面时初始化 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 _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.trim()); try { final String libraryName = _selectedLibrary.displayName; debugPrint('[$libraryName] Sending request to ${uri.host}:${uri.port}'); int statusCode; Map 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'; }); } } Future _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 { 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() ?? const []; final ipv6 = (res['ipv6'] as List?)?.cast() ?? const []; 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: [ TextField( controller: _urlController, decoration: const InputDecoration( labelText: 'Enter URL', hintText: 'https://demo.cloudxdr.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( value: _selectedLibrary, isExpanded: true, underline: const SizedBox(), icon: const Icon(Icons.arrow_drop_down), items: NetworkLibrary.values.map((library) { return DropdownMenuItem( value: library, child: Text(library.displayName), ); }).toList(), onChanged: _isLoading ? null : (NetworkLibrary? newValue) { if (newValue != null) { setState(() { _selectedLibrary = newValue; }); } }, ), ), ), ], ), const SizedBox(height: 16), 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, ), ), ), ), ), ], ), ), ); } }