This document describes how to integrate the HTTPDNS Android and iOS software development kits (SDKs) in a Flutter environment.
Overview
Flutter is a mobile user interface (UI) framework from Google. It helps you quickly build high-quality native user interfaces on iOS and Android. Flutter can work with existing code, and developers and organizations worldwide use it. It is completely free and open source. Flutter uses the Dart language, which has its own virtual machine. Dart can be compiled ahead-of-time (AOT) into native code for different platforms. This enables Flutter to communicate directly with the platform without a bridge, making it an efficient cross-platform framework for mobile development. In project development, there are two main scenarios:
Normal scenario
The entire Flutter project is implemented in Dart. You write one codebase that runs on both Android and iOS platforms.
Hybrid development mode
The entire Android or iOS project integrates Flutter as a module. You use a MethodChannel to enable calls between the native and Flutter code.
Dart's underlying HttpClient does not provide an interface for HTTPS certificate validation. Because of this, you cannot make direct HTTPS network requests by directly specifying the host's IP address when you use Dart's HttpClient or third-party network frameworks such as Dio or Http. To enable direct HTTPS connections, set up a local proxy. When a socket connection is established, replace the host that you are accessing with the IP address resolved by the HTTPDNS SDK. This allows direct HTTPS access using the IP address.
For the complete code for integrating the Android and iOS SDKs in a Flutter scenario, see the flutterDNSDemo source code.
Implementation guide
Integrate the native HTTPDNS SDK in the Android part of a Flutter project
Open the Android part of your Flutter project in Android Studio.
Start Android Studio and select File > Open….
Navigate to your Flutter app directory, select the android folder, and click OK. Open MainActivity.java in the Java directory.
Integrate the HTTPDNS Android SDK in your Application class.
For more information about how to integrate the HTTPDNS Android SDK, see the Android SDK Developer Guide.
Define the MethodChannel for communication with Flutter in the Android directory. The channel name must be the same as the channel name in Flutter.
package com.example.flutter;
import android.widget.Toast;
import com.alibaba.pdns.DNSResolver;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.embedding.android.FlutterActivity;
import android.content.Context;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.util.Log; // Import the Log class.
public class MainActivity extends FlutterActivity {
/** Define a TAG for log identification. **/
private static final String TAG = "MainActivity";
private MethodChannel channel;
private Context context;
/** This must be the same as the channel name in Flutter. **/
private static final String CHANNEL = "samples.flutter.io/getIP";
/** Define the channelMethod for the Flutter side to call native Android methods. **/
private static final String channelMethod = "getIP";
/** Parameter passed from Flutter to the Android side. **/
private static final String hostArgument = "host";
/** Initialize the DNSResolver object from the Alibaba Cloud Public DNS Android SDK. **/
private DNSResolver dnsResolver = null;
/** Create a fixed-size thread pool. **/
private ExecutorService executor = Executors.newFixedThreadPool(5);
@Override
public void configureFlutterEngine(FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
// Initialize DNSResolver.
dnsResolver = DNSResolver.getInstance();
channel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
channel.setMethodCallHandler(new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
// This handler receives all method calls. Check if the method is getIP.
if (call.method.equals(channelMethod)) {
if (call.hasArgument(hostArgument)) {
String resolverName = call.argument(hostArgument);
Log.d(TAG, "resolverName: " + resolverName); // Use Log.d to print the log.
executor.execute(() -> {
try {
String[] IPArray = dnsResolver.getIpv4ByHostFromCache(resolverName,true);
String ip = null;
if (IPArray==null || IPArray.length==0){
ip = dnsResolver.getIPV4ByHost(resolverName);
}else {
ip = IPArray[0];
}
String finalIp = ip;
runOnUiThread(() -> {
if (finalIp != null) {
Log.d(TAG, "DNS_RESOLUTION_SUCCESS: " + resolverName + "==" + finalIp); // Use Log.d to print the log.
result.success(finalIp);
Toast.makeText(MainActivity.this, "Successfully called native Android domain name resolution!", Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "Error resolving IP address"); // Use Log.e to print the error log.
result.error("DNS_RESOLUTION_FAILED", "Failed to resolve IP address", null);
}
});
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Error resolving IP address", e); // Use Log.e to print the error log.
runOnUiThread(() -> result.error("DNS_RESOLUTION_FAILED", e.getMessage(), null));
}
});
}
}
}
});
}
}Integrate the native HTTPDNS SDK in the iOS part of a Flutter project
Open the iOS project of your Flutter project in Xcode.
Find the Xcode project file in the iOS directory of your Flutter project and open it with Xcode.
Integrate the HTTPDNS iOS SDK in your Xcode project.
For more information about how to integrate the HTTPDNS iOS SDK, see the iOS SDK Developer Guide.
Define the MethodChannel for communication with Flutter in iOS.
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController; FlutterMethodChannel* getIPChannel = [FlutterMethodChannel methodChannelWithName:@"samples.flutter.io/getIP" binaryMessenger:controller];// This must be the same as the channel name in Flutter. [getIPChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { if ([@"getIP" isEqualToString:call.method]) { NSDictionary *dic = call.arguments; NSString *hostName = dic[@"host"]; NSLog(@"hostName==%@",hostName); NSArray *arr = [[DNSResolver share] getIpv4ByCacheWithDomain:hostName andExpiredIPEnabled:YES]; if (arr != nil && arr.count > 0) { result(arr[0]); }else { [[DNSResolver share] getIpv4DataWithDomain:hostName complete:^(NSArray<NSString *> *dataArray) { NSLog(@"array==%@", dataArray); if (dataArray != nil && dataArray.count > 0) { result(dataArray[0]); } else { // If resolution fails, return the original hostName. result(hostName); } }]; } } else { result(FlutterMethodNotImplemented); } }];
Obtain domain name resolution results in the Flutter project
static const platform = const MethodChannel('samples.flutter.io/getIP'); final String result = await platform.invokeMethod('getIP', {'host': 'The domain name to resolve'});Use a proxy for HTTPS requests in the Flutter project
bool _proxyRunning = false; late CustomHttpsProxy _proxy; int? _proxyPort; // Stores the actual proxy port. @override void initState() { super.initState(); // Start the proxy (only once). _startProxy(); } Future<void> _startProxy() async { if (_proxyRunning) return; _proxy = CustomHttpsProxy(); final port = await _proxy.init(); // Get the actual port. if (port != null) { setState(() { _proxyRunning = true; _proxyPort = port; // Save the port. }); print('Proxy started successfully on port: $port'); } } Future doHttpGetProxy() async{ var httpClient= HttpClient(); // Dynamically use the actual port. httpClient.findProxy = (uri) => 'PROXY localhost:${_proxyPort}'; var request= await httpClient.getUrl(Uri.parse('https://www.taobao.com')); var response= await request.close(); // Read the response body. var responseBody= await response.transform(Utf8Decoder()).join(); print(responseBody); }When a socket connection is established, replace the host that you are accessing with an IP address to enable direct HTTPS access using the IP address.
import 'dart:io'; import 'dart:convert'; import 'package:flutter/services.dart'; class CustomHttpsProxy { ServerSocket? serverSocket; CustomHttpsProxy(); Future<int?> init() async { // Returns the actual port number. try { // Use port 0 to let the system assign a port automatically. serverSocket = await ServerSocket.bind(InternetAddress.anyIPv4, 0); serverSocket!.listen((client) { try { ClientConnectionHandler(client).handle(); } catch (e) { print('ClientConnectionHandler exception $e'); } }); return serverSocket!.port; // Return the actual port. } catch (e) { print('serverSocket handling exception $e'); return null; } } void close() { if (serverSocket != null) { serverSocket?.close(); } } } class ClientConnectionHandler { final RegExp regx = RegExp(r'CONNECT ([^ :]+)(?::([0-9]+))? HTTP/1.1\r\n'); static const platform = const MethodChannel('samples.flutter.io/getIP'); Socket? server; Socket? client; String content = ''; String host = ''; int port = 443; ClientConnectionHandler(this.client); void closeSockets() { // print('socket is going to destroy'); if (server != null) { server?.destroy(); } client?.destroy(); } Future<void> dataHandler(data) async { if (server == null) { content += utf8.decode(data); final m = regx.firstMatch(content); if (m != null) { host = m.group(1) ?? 'unknown'; port = int.tryParse(m.group(2) ?? '') ?? 443; final String? resultIP = await platform.invokeMethod('getIP', {'host': host}); final String realHost = resultIP ?? host; print('~~~~~$resultIP'); try { ServerConnectionHandler(realHost, port, this) .handle() .catchError((e) { print('Server error $e'); closeSockets(); }); } catch (e) { print('Server exception $e'); closeSockets(); } } } else { try { server?.add(data); } catch (e) { print('server has been shut down'); closeSockets(); } } } void errorHandler(error, StackTrace trace) { print('client socket error: $error'); } void doneHandler() { closeSockets(); } void handle() { client?.listen(dataHandler, onError: errorHandler, onDone: doneHandler, cancelOnError: true); } } class ServerConnectionHandler { final String RESPONSE = 'HTTP/1.1 200 Connection Established\r\n\r\n'; final String host; final int port; final ClientConnectionHandler handler; Socket? server; Socket? client; String content = ''; ServerConnectionHandler(this.host, this.port, this.handler) { client = handler.client; } // Receive the message. void dataHandler(data) { try { client?.add(data); } on Exception catch (e) { print('client has been shut down $e'); handler.closeSockets(); } } void errorHandler(error, StackTrace trace) { print('server socket error: $error'); } void doneHandler() { handler.closeSockets(); } Future handle() async { print('Attempting to connect to: $host:$port'); server = await Socket.connect(host, port, timeout: Duration(seconds: 60)); server?.listen(dataHandler, onError: errorHandler, onDone: doneHandler, cancelOnError: true); handler.server = server; client?.write(RESPONSE); } }
This document covers only the use of the HTTPDNS Android and iOS SDKs in a Flutter environment.
For the complete code for integrating the HTTPDNS Android and iOS SDKs in a Flutter scenario, see the flutterDNSDemo source code.