Flutter接入移动解析HTTPDNS Android/iOS SDK实践方案

重要

本文中含有需要您注意的重要提示信息,忽略该信息可能对您的业务造成影响,请务必仔细阅读。

本文档介绍了在Flutter场景下移动解析HTTPDNS Android/iOS SDK的接入方式。

概述

Flutter是谷歌的移动UI框架,可以快速在iOSAndroid上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。Flutter使用Dart语言开发,有单独的Dart虚拟机,Dart可以被编译(AOT)成不同平台的本地代码,让Flutter可以直接和平台通讯而不需要一个中间的桥接过程,可见Flutter是一个高效的移动端跨平台框架。在实际的项目开发中主要有以下2种实践场景:

  • 普通场景

    整个Flutter项目全部由Dart实现,写一份代码可以运行在AndroidiOS平台。

  • 混合开发模式

    整个Android/iOS项目是将Flutter作为module集成在项目中,通过MethodChannel实现两端的相互调用。

警告

由于Dart的底层HttpClient对外没有提供HTTPS的证书验证接口,通过Dart自带的HttpClient或使用第三方的Dio、Http网络框架,用户不能直接通过设置HostIP直连方式进行HTTPS网络访问。因此要实现HTTPS的直连方案需要在本地搭建一个代理,在Socket建连的时候将用户访问的HOST替换成通过移动解析HTTPDNS SDK解析出的IP以实现HTTPSIP直连访问。

Flutter场景下接入移动解析HTTPDNS Android、iOSSDK实践完整代码请参考flutterDNSDemo源码

实践方案

Flutter工程中Android端集成原生移动解析HTTPDNS SDK

  1. Android Studio中打开您的Flutter项目的Android部分:

    启动 Android Studio,选择 File > Open…

    定位到您 Flutter app目录, 然后选择里面的android文件夹,点击OK。在Java目录下打开MainActivity.java

  2. Application里集成移动解析HTTPDNS DNS Android SDK:

    如何集成移动解析HTTPDNS DNS Android SDK请参考Android SDK开发指南文档。

  3. Android目录中定义和Flutter通信的MethodChannel: 需要注意的是通道的名字要跟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;

public class MainActivity extends FlutterActivity {
    private MethodChannel channel;
    private Context context;

    //这个跟flutter的通道名称保持一直
    private static final String CHANNEL = "samples.flutter.io/getIP";
    //定义flutter端调用Android原生方法的channelMethod
    private static final String channelMethod = "getIP";
    //flutter对Android端的传参
    private static final String hostArgument = "host";
    //初始化阿里云公共DNS Android SDK中提供的DNSResolver对象
    private DNSResolver dnsResolver = null;

    @Override
    public void configureFlutterEngine(FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        //初始化DNSResolver
        dnsResolver = DNSResolver.getInstance();
        channel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
        channel.setMethodCallHandler(new MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall call, Result result) {
                //这里是判断是不是调getIP方法,因为所有的方法都走这里
                if (call.method.equals(channelMethod)) {
                    if (call.hasArgument(hostArgument)) {
                        try{
                            //取出从flutter端传递的host参数
                            String resolverName = call.argument(hostArgument);
                            String ip = dnsResolver.getIPV4ByHost(resolverName);
                            if (ip != null) {
                                result.success(ip);
                                Toast.makeText(MainActivity.this, "调用Android原生域名解析成功!", Toast.LENGTH_SHORT).show();
                            } else {
                                result.success(resolverName);
                            }
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
    }
}

Flutter工程中iOS端集成原生移动解析HTTPDNS SDK

  1. Xcode中打开您的Flutter项目的iOS工程:

    Flutter项目中找到iOS目录下的Xcode工程文件,使用Xcode打开。

  2. Xcode工程里集成移动解析HTTPDNS iOS SDK:

    如何集成移动解析HTTPDNS iOS SDK请参考iOS SDK开发指南文档。

  3. iOS中定义和Flutter通信的MethodChannel:

    FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
    FlutterMethodChannel* getIPChannel = [FlutterMethodChannel
                                                methodChannelWithName:@"samples.flutter.io/getIP"
                                                binaryMessenger:controller];//跟flutter的通道名称保持一直
    [getIPChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
            if ([@"getIP" isEqualToString:call.method]) {
                NSDictionary *argumentDic = call.arguments;
                result([self getIPWithHostName:argumentDic[@"host"]]); // 解析结果
            } else {
                result(FlutterMethodNotImplemented);
            }
    }];

    用户自定义获取域名解析结果IP的方法

    - (NSString *)getIPWithHostName:(NSString *)hostName {//建议用户自行增加相应的容错处理,如果没有IP那么返回域名保证程序正常运行
        NSArray *array = [[DNSResolver share] getIpv4ByCacheWithDomain:hostName andExpiredIPEnabled:YES];
        if (array != NULL && array.count > 0) {
            return array[0];
        }else{
            return hostName;
        }
    }
  • Flutter工程中通过methodChannel和自定义方法获取域名解析结果

    static const platform = const MethodChannel('samples.flutter.io/getIP');
    final String result = await platform.invokeMethod('getIP', {'host': '您要解析的域名'});
  • Flutter工程中HTTPS网络请求使用IP直连

    void runPoxyRequest() async {
        var proxy = CustomHttpsProxy();
        final result = await proxy.init();
        print('proxy established: ${result != null}');
        await doHttpGet();
        proxy.close();
    }
    Future doHttpGet() async{
      var httpClient= HttpClient();
      httpClient.findProxy= (uri) => 'PROXY localhost:4041';
      var request= await httpClient.getUrl(Uri.parse('https://www.taobao.com'));
      var response= await request.close();
      //读取响应内容
      var responseBody= await response.transform(Utf8Decoder()).join();
      print(responseBody);
    }

    Socket建连的时候将用户访问的HOST替换成IP以实现HTTPSIP直连访问

    import 'dart:io';
    import 'dart:convert';
    
    import 'package:flutter/services.dart';
    
    class CustomHttpsProxy {
      final int port = 4041;
      ServerSocket? serverSocket;
    
      CustomHttpsProxy();
    
      Future init() async {
        await ServerSocket.bind(InternetAddress.anyIPv4, port).then((serverSocket) {
          this.serverSocket = serverSocket;
          serverSocket.listen((client) {
            try {
              ClientConnectionHandler(client).handle();
            } catch (e) {
              print('ClientConnectionHandler exception $e');
            }
          });
        }).catchError((e) {
          print('serverSocket 处理异常$e');
        });
        return serverSocket;
      }
    
      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('sever 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;
      }
    
      //接收报文
      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('尝试建立连接: $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);
      }
    }
说明
  1. 当前文档只针对Flutter场景下移动解析HTTPDNS Android/iOS SDK的使用。

  2. 开发者在Flutter场景下接入移动解析HTTPDNS Android/iOS SDK实践完整代码请参考flutterDNSDemo源码