You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter 如何调用API获取数据及处理自签名证书报错、参数展示问题

Flutter自签名接口请求适配与响应数据展示实现

问题根源

你遇到的HandshakeException是因为目标接口使用自签名SSL证书,未被系统内置的可信CA列表收录,Flutter默认的TLS握手流程会直接拦截这类证书,触发证书校验失败报错。

注:http包底层同样基于HttpClient实现,之前出现的报错和直接使用HttpClient的报错原因完全一致,并非库本身的兼容问题。

规范的TLS握手适配方案

不要直接全局关闭证书校验(存在中间人攻击风险),按照以下步骤配置:

  • 从目标服务端导出对应的自签名证书文件,存放到项目assets/ssl/目录下,在pubspec.yaml中声明该资源路径
  • 初始化网络客户端时,将该证书加入受信任列表,仅对目标IP的接口放行匹配的自签证书,不影响其他正常HTTPS接口的校验逻辑
  • 配置示例代码:
import 'dart:io';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/services.dart' show rootBundle;

Future<HttpClient> getConfiguredClient() async {
  // 加载本地存放的服务端自签证书
  final certBytes = await rootBundle.load('assets/ssl/server_cert.crt');
  final securityContext = SecurityContext(withTrustedRoots: true);
  securityContext.setTrustedCertificatesBytes(certBytes.buffer.asUint8List());

  final client = HttpClient(context: securityContext);
  // 兜底校验逻辑:仅对目标IP放行匹配证书的请求
  client.badCertificateCallback = (cert, host, port) {
    // 精准匹配目标接口IP,不放开其他请求的证书校验
    if (host == '185.86.145.54') {
      // 校验证书主体信息,确认是目标服务的合法证书
      return cert.subject.toString().contains('cameras_service');
    }
    return false;
  };
  return client;
}

响应数据解析与UI展示流程

已经可以拿到HttpClient的响应流后,按照以下步骤完成数据渲染:

  • 第一步:把响应流转换为结构化数据
    1. 读取响应流时,用utf8解码器将字节流转为字符串
    2. 通过jsonDecode将字符串格式的JSON内容解析为Map/List结构的Dart对象,直接通过key就能取到接口返回的各参数值
      解析示例:
    Future<List<dynamic>> fetchCameras() async {
      final client = await getConfiguredClient();
      final request = await client.getUrl(Uri.parse('https://185.86.145.54/cameras/all'));
      final response = await request.close();
      // 转成字符串
      final respStr = await response.transform(utf8.decoder).join();
      // 解析JSON
      final respJson = jsonDecode(respStr) as Map<String, dynamic>;
      // 取data字段下的摄像机列表
      return respJson['data'] as List<dynamic>;
    }
    
  • 第二步:将解析后的数据绑定到UI组件
    • 推荐配合FutureBuilder处理异步请求的全状态:加载中展示加载动画、请求失败展示错误提示、请求成功后遍历列表渲染内容
    • 生产环境建议把返回的JSON对象映射为强类型的Model类,避免硬编码key带来的空值、字段拼写错误等问题
      展示示例:
    // 在页面的build方法中
    FutureBuilder<List<dynamic>>(
      future: fetchCameras(),
      builder: (context, snapshot) {
        // 加载状态
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Center(child: CircularProgressIndicator());
        }
        // 错误状态
        if (snapshot.hasError) {
          return Center(child: Text('加载失败:${snapshot.error}'));
        }
        // 数据成功状态
        final cameraList = snapshot.data ?? [];
        return ListView.builder(
          itemCount: cameraList.length,
          itemBuilder: (context, index) {
            final camera = cameraList[index];
            return Card(
              child: ListTile(
                title: Text(camera['camera_name']),
                subtitle: Text(camera['is_online'] ? '当前在线' : '当前离线'),
                trailing: Text('ID: ${camera['camera_id']}'),
              ),
            );
          },
        );
      },
    )
    

注意事项

  • 不要在badCertificateCallback中直接写return true的逻辑,这种写法会让整个App的所有HTTPS请求都跳过证书校验,存在严重的安全风险
  • 网络请求不要放在build方法中执行,避免页面rebuild时重复触发无效请求
  • 解析数据时要对所有可能为null的字段做空安全判断,防止运行时出现空指针异常
  • 如果想切回http包使用,只需要把上面配置好的SecurityContext传给自定义的HttpClientAdapter,http包也能正常适配这个自签证书接口,不需要单独维护HttpClient的实例

内容的提问来源于stack exchange,提问作者Pation

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.24 03:27:04