Flutter - 解决Connection closed before full header was received
# 一、概述
我们的 App
是集成了 Sentry
进行错误跟踪和性能监控,在最近几个月里经常看到如下错误
ClientException: Connection closed before full header was received, uri=https://xxx.png
加载图片的时候报错了,但是尝试了很多次,自己始终无法复现出来~
# 二、摸索
后面进行搜索找到了 2019
年 12
月的一个 issue
- https://github.com/flutter/flutter/issues/41573
不太一样的是人家是请求接口的时候出现的报错。
提出 issue
的作者当时是通过 Method Channel
的方式由原生端去请求来解决这个问题,即避开了使用 http
这个包。
有人说这种情况,改用 dio
即可~
也有人说这种情况,加个延迟就可以解决~
但我们这种加载图片场景哪有机会加延迟呢,当然用这种方式也不靠谱~
时隔两年后(2021
年 12
月),根据 issue
的种种描述,有人确定了问题,并提了一个 Dart
的 issue
- https://github.com/dart-lang/sdk/issues/47841
大致上就是说 Dart
端拒绝了服务器发起的 TLS
重协商导致。
重协商是指在已经协商好的SSL/TLS TCP连接上重新协商, 用以更换算法、更换数字证书、重新验证对方身份、更新共享密钥等。 --摘自《SSL/TLS攻击介绍--重协商漏洞攻击 (opens new window)》
我想 Dart
不允许 TLS
重协商应该也是出于安全考虑吧~
# 三、解决
提 issue
的作者也为此,于 2022
年 2
月份提了 PR
,给 SecurityContext
添加了 allowLegacyUnsafeRenegotiation
属性以允许重协商,在同年 4
月份得到合并了,但依旧出于安全考虑,默认为关闭状态,是否开启由开发者自己决定。
附上该属性的相关说明:https://api.flutter.dev/flutter/dart-io/SecurityContext/allowLegacyUnsafeRenegotiation.html
以及相关修复:Allow sockets to enable TLS renegotiation. · dart-lang/sdk@c286b76 (github.com) (opens new window)
注:该属性从 Dart 2.17.0
开始存在,对应的 Flutter
版本是 3.0.0
。
加了 allowLegacyUnsafeRenegotiation
属性之后,我们可以按如下代码去使用
void main() async {
final context = SecurityContext.defaultContext;
context.allowLegacyUnsafeRenegotiation = true;
final httpClient = HttpClient(context: context);
final client = IOClient(httpClient);
await client.get(Uri.parse('https://your_uri.net'));
}
代码取自:https://stackoverflow.com/a/73287131/8577739
# 四、调整 CachedNetworkImage
在我们的项目中,所使用的图片加载库为 cached_network_image
,接下来我们就一起来看看如何调整以开启 TLS
重协商功能。
在 CachedNetworkImage
类中有一个 cacheManager
属性,如果我们不设置的话则后续默认使用的是 DefaultCacheManager
实例
class DefaultCacheManager extends CacheManager with ImageCacheManager {
static const key = 'libCachedImageData';
static final DefaultCacheManager _instance = DefaultCacheManager._();
factory DefaultCacheManager() {
return _instance;
}
DefaultCacheManager._() : super(Config(key));
}
可以看到其继承于 CacheManager
class CacheManager implements BaseCacheManager {
....
CacheManager(Config config)
: _config = config,
_store = CacheStore(config) {
_webHelper = WebHelper(_store, config.fileService);
}
...
}
CacheManager
接收一个 Config
对象
abstract class Config {
...
/// [fileService] defines where files are fetched, for example online.
factory Config(
String cacheKey, {
Duration stalePeriod,
int maxNrOfCacheObjects,
CacheInfoRepository repo,
FileSystem fileSystem,
FileService fileService,
}) = impl.Config;
...
FileService get fileService;
}
这里我们就可以利用 fileService
来指定 httpClient
,以此帮我们解决上述报错问题
static HttpClient httpClient() {
final context = SecurityContext.defaultContext;
context.allowLegacyUnsafeRenegotiation = true;
return HttpClient(context: context);
}
// 自定义 CacheManager
class _LXFCachedNetworkImageManager extends CacheManager
with ImageCacheManager {
// 为了不影响之前的缓存,保持使用相同的 key
static String get key => DefaultCacheManager.key;
static final _LXFCachedNetworkImageManager _instance =
_LXFCachedNetworkImageManager._();
factory _LXFCachedNetworkImageManager() {
return _instance;
}
_LXFCachedNetworkImageManager._()
: super(
Config(
key,
// 指定 httpClient
fileService: HttpFileService(
httpClient: IOClient(httpClient()),
),
),
);
}
使用时配置上 cacheManager
即可,如下所示
CachedNetworkImage(
imageUrl: '',
cacheManager: _LXFCachedNetworkImageManager(),
);
但每个地方都这样写实在是太不优雅了,所以大家自行进行封装吧~
本篇到此结束,感谢大家的支持,我们下次再见! 👋
![](https://cdn.jsdelivr.net/gh/FullStackAction/PicBed@resource/image/FSA_QR_bottom.png)
- 01
- Flutter - 升级到3.24后页面还会多次rebuild吗?🧐08-11
- 02
- Flutter - 聊天键盘与面板丝滑切换的强势升级 🍻08-04
- 03
- Android - 云游戏本地悬浮输入框实现07-07