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
相关产品推荐
相关产品推荐

