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

Flutter移动端HTTPS Server证书文件访问异常问题排查

移动设备本地运行HTTPS Server时证书文件无法访问的问题解决

问题说明

在移动设备本地运行HTTPS Server并通过WebView访问时,出现证书文件无法找到的错误,报错提示文件路径不存在。

原代码

import 'dart:io';

import 'package:flutter/material.dart' hide Router;
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart';
import 'package:shelf_router/src/router.dart';
import 'package:webview_flutter/webview_flutter.dart';

Future<void> main() async {
  await startServer();
  runApp(const MyApp());
}

// Configure routes.
final _router = Router()
  ..get('/', _rootHandler)
  ..get('/www/<message>', _echoHandler);

Response _rootHandler(Request req) {
  return Response.ok('Hello, World!\n');
}

Response _echoHandler(Request request) {
  final message = request.params['message'];
  return Response.ok('$message\n');
}

SecurityContext getSecurityContext() {
  // Bind with a secure HTTPS connection
  final chain =
      Platform.script.resolve('certificates/server_chain.pem').toFilePath();
  final key =
      Platform.script.resolve('certificates/server_key.pem').toFilePath();

  return SecurityContext()
    ..useCertificateChain(chain)
    ..usePrivateKey(key, password: 'dartdart');
}

startServer() async {
  final ip = InternetAddress.anyIPv4;

  // Configure a pipeline that logs requests.
  final _handler = Pipeline().addMiddleware(logRequests()).addHandler(_router);

  // For running in containers, we respect the PORT environment variable.
  final port = int.parse(Platform.environment['PORT'] ?? '443');
  final server =
      await serve(_handler, ip, port, securityContext: getSecurityContext());
  print('Server listening on port ${server.port}');
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Server Bind Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Server Bind'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  void initState() {
    super.initState();
    if (Platform.isAndroid) WebView.platform = AndroidWebView();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: WebView(
          initialUrl:
              'http://127.0.0.1:8888', //'http://${server.address.host}:${server.port}/index.html',
        ),
      ),
    );
  }
}

报错信息

[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: FileSystemException: Cannot open file, path = '/certificates/server_chain.pem' (OS Error: No such file or directory, errno = 2)
#0      _File.throwIfError (dart:io/file_impl.dart:635:7)
#1      _File.openSync (dart:io/file_impl.dart:479:5)
#2      _File.readAsBytesSync (dart:io/file_impl.dart:539:18)
#3      _SecurityContext.useCertificateChain (dart:io-patch/secure_socket_patch.dart:236:40)
#4      getSecurityContext (package:server_demo/main.dart:34:5)
#5      startServer (package:server_demo/main.dart:49:67)
#6      main (package:server_demo/main.dart:11:9)
#7      _runMainZoned.<anonymous closure>.<anonymous closure> (dart:ui/hooks.dart:145:25)
#8      _rootRun (dart:async/zone.dart:1428:13)
#9      _CustomZone.run (dart:async/zone.dart:1328:19)
#10     _runZoned (dart:async/zone.dart:1863:10)
#11     runZonedGuarded (dart:async/zone.dart:1851:12)
#12     _runMainZoned.<anonymous closure> (dart:ui/hooks.dart:141:5)
#13     _delayEntrypointInv<…>

解决方案

1. 配置资源文件

Flutter项目中的本地资源需要在pubspec.yaml中声明,否则打包后无法被应用访问:

  • 将certificates文件夹放置在项目根目录的assets文件夹下(若无assets文件夹则新建)
  • 在pubspec.yaml中添加资源声明:
flutter:
  assets:
    - certificates/server_chain.pem
    - certificates/server_key.pem

2. 修正证书文件加载方式

Platform.script在移动设备上指向应用执行路径,无法正确访问打包后的资源文件,需通过rootBundle读取asset资源的字节流:

  • 导入flutter/services.dart包
  • 修改getSecurityContext方法为异步方法,使用字节流加载证书:
import 'package:flutter/services.dart';

Future<SecurityContext> getSecurityContext() async {
  // 从asset中读取证书和密钥字节数据
  final chainBytes = await rootBundle.load('certificates/server_chain.pem');
  final keyBytes = await rootBundle.load('certificates/server_key.pem');

  return SecurityContext()
    ..useCertificateChainBytes(chainBytes.buffer.asUint8List())
    ..usePrivateKeyBytes(keyBytes.buffer.asUint8List(), password: 'dartdart');
}
  • 同步修改startServer方法,等待SecurityContext初始化完成:
startServer() async {
  final ip = InternetAddress.anyIPv4;
  final _handler = Pipeline().addMiddleware(logRequests()).addHandler(_router);
  final port = int.parse(Platform.environment['PORT'] ?? '443');
  // 等待SecurityContext加载完成
  final securityContext = await getSecurityContext();
  final server = await serve(_handler, ip, port, securityContext: securityContext);
  print('Server listening on port ${server.port}');
}

3. 修正WebView访问配置

  • 调整WebView的initialUrl为HTTPS协议,匹配Server监听的端口:
WebView(
  initialUrl: 'https://127.0.0.1:443',
)
  • Android WebView默认不信任自签名证书,调试阶段可添加信任配置(生产环境需使用正规证书):
class _MyHomePageState extends State<MyHomePage> {
  late WebViewController _webViewController;

  void initState() {
    super.initState();
    if (Platform.isAndroid) {
      WebView.platform = AndroidWebView();
      // 允许调试模式下访问自签名证书
      AndroidWebView.enableDebugging(true);
      _webViewController = WebViewController()
        ..setNavigationDelegate(NavigationDelegate())
        ..loadRequest(Uri.parse('https://127.0.0.1:443'));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: WebViewWidget(controller: _webViewController),
      ),
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 09:18:27