Flutter网络请求框架Dio源码分析以及封装(二)--Cookie管理分析
Flutter网络请求框架Dio源码分析以及封装--Cookie管理分析
- 前言
- 问题
- 如何使用
- CookieJar
- CookieManager
- PersistCookieJar
- 总结
前言
上一篇文章我们简单分析了一下Dio发出请求时的大致工作流程,这个只是Dio最基本的功能,而且我们还没有分析走到httpClientAdapter之后的内容。不过不用着急,这次我们先接着上一次的内容,看一下Dio当中Cookie管理的问题,因为之前在项目中碰到了这个问题,回过头来再从源码的角度去分析一下,算是复盘。
问题
之前碰到的问题是这样的:登录接口在登陆成功后会返回一个Cookie给客户端,包含在Response的
“set-cookie”Headers属性里。之后在客户端这边调用某些接口时后台需要对Cookie进行验证,所以需要把Cookie存到内存甚至硬盘里,在请求头里面带上传给后台,否则会后台会报请求失败。
由于以前的项目基本上是采取token的方式去做接口鉴权,所以这种cookie持久化的方法对我来说有点陌生,不过最后还是能想到利用拦截器去做这个事情。Dio允许我们自定义拦截器,对请求与返回的参数进行调整,我们在onResponse回调方法里,从Response里面的headers拿到Cookie,再在onRequest里面加到RequestOptions里。原理很简单,但是之前自己实现的时候很多细节没有处理好,造成了代码的冗余以及不够健壮。
实际上,Dio提供了配套的Plugin:dio_cookie_manager 来帮助我们进行Cookie的管理,使用起来也非常的方便。那么这次我们就一起来看一下它是怎么处理这些问题的。
如何使用
首先我们需要导入cookie_manager的库,基于项目中用到的2.0.0版本
它又间接依赖cookie_jar3.0.0这个版本
dependencies:dio_cookie_manager: ^2.0.0cookie_jar: ^3.0.0
final dio = Dio();final cookieJar = CookieJar();dio.interceptors.add(CookieManager(cookieJar));
如示例中所示,我们需要构造一个CookieJar的实例,然后将它传入CookieManager的构造方法中,最后添加到Dio的拦截器列表中。
CookieJar
我们先来看看CookieJar这个类:
/// CookieJar is a cookie manager for http requests。
abstract class CookieJar {factory CookieJar({bool ignoreExpires = false}) {return DefaultCookieJar(ignoreExpires: ignoreExpires);}/// Save the cookies for specified uri.Future<void> saveFromResponse(Uri uri, List<Cookie> cookies);/// Load the cookies for specified uri.Future<List<Cookie>> loadForRequest(Uri uri);Future<void> deleteAll();Future<void> delete(Uri uri, [bool withDomainSharedCookie = false]);final bool ignoreExpires = false;
}
CookieJar是抽象类,最终还是调用了DefaultCookieJar这个类的构造方法
/// [DefaultCookieJar] is a default cookie manager which implements the standard
/// cookie policy declared in RFC. [DefaultCookieJar] saves the cookies in RAM, so if the application
/// exit, all cookies will be cleared.
class DefaultCookieJar implements CookieJar {/// [ignoreExpires]: save/load even cookies that have expired.DefaultCookieJar({this.ignoreExpires = false});...}
DefaultCookieJar是CookieJar的一种默认实现,按照http的格式将cookie从Request与Response中解析出来,并保存在Ram中。
CookieManager
接着来看看CookieManager这个类:
/// Don't use this class in Browser environment
class CookieManager extends Interceptor {final CookieJar cookieJar;CookieManager(this.cookieJar);
```、
```dartvoid onRequest(RequestOptions options, RequestInterceptorHandler handler) {cookieJar.loadForRequest(options.uri).then((cookies) {var cookie = getCookies(cookies);if (cookie.isNotEmpty) {options.headers[HttpHeaders.cookieHeader] = cookie;}handler.next(options);}).catchError((e, stackTrace) {var err = DioError(requestOptions: options, error: e);err.stackTrace = stackTrace;handler.reject(err, true);});}
static String getCookies(List<Cookie> cookies) {return cookies.map((cookie) => '${cookie.name}=${cookie.value}').join('; ');}
构造的cookieJar传进来后,在onRequest方法里面调用loadForRequest方法获取格式化后的Cookie,将他们转化为请求需要的格式然后设置在请求头里,之后继续执行下面的拦截器的逻辑。
void onResponse(Response response, ResponseInterceptorHandler handler) {_saveCookies(response).then((_) => handler.next(response)).catchError((e, stackTrace) {var err = DioError(requestOptions: response.requestOptions, error: e);err.stackTrace = stackTrace;handler.reject(err, true);});}
Future<void> _saveCookies(Response response) async {var cookies = response.headers[HttpHeaders.setCookieHeader];if (cookies != null) {await cookieJar.saveFromResponse(response.requestOptions.uri,cookies.map((str) => Cookie.fromSetCookieValue(str)).toList(),);}}
onResponse也类似,拿到Cookie之后,调用saveFromResponse格式化保存起来,之后继续执行下面的拦截器的逻辑。
当然,正常的顺序应该是先在onResponse拿到Cookie之后再在onResponse使用。
PersistCookieJar
如果想对Cookie进行持久化处理,可以考虑使用PersistCookieJar:
final Directory appDocDir = await getApplicationDocumentsDirectory();final String appDocPath = appDocDir.path;final jar = PersistCookieJar(ignoreExpires: true,storage: FileStorage(appDocPath + "/.cookies/"),);dio!.interceptors.add(CookieManager(jar));
/// [PersistCookieJar] is a cookie manager which implements the standard
/// cookie policy declared in RFC. [PersistCookieJar] persists the cookies in files,
/// so if the application exit, the cookies always exist unless call [delete] explicitly.
class PersistCookieJar extends DefaultCookieJar {////// [persistSession]: Whether persisting the cookies that without/// "expires" or "max-age" attribute;/// If false, the session cookies will be discarded;/// otherwise, the session cookies will be persisted.////// [ignoreExpires]: save/load even cookies that have expired.////// [storage]: Defaults to FileStoragePersistCookieJar({this.persistSession = true,bool ignoreExpires = false,Storage? storage}): super(ignoreExpires: ignoreExpires) {this.storage = storage ?? FileStorage();}
为了实现持久化,引入了一个Storage类,默认实现是FileStorage,文件存储,也可以自己实现其他方式。
Future<List<Cookie>> loadForRequest(Uri uri) async {await _checkInitialized();await _load(uri);return super.loadForRequest(uri);}
PersistCookieJar最终也调用了DefaultCookieJar的loadForRequest方法,但是在那之前还执行了两个方法,我们一个一个看:
Future<void> _checkInitialized({bool force = false}) async {if (force || !_initialized) {await storage.init(persistSession, ignoreExpires);// Load domain cookiesvar str = await storage.read(DomainsKey);...}
执行了storage的init与read方法
Future<void> init(bool persistSession, bool ignoreExpires) async {_curDir = dir ?? './.cookies/';if (!_curDir!.endsWith('/')) {_curDir = _curDir! + '/';}_curDir = _curDir! + 'ie${ignoreExpires ? 1 : 0}_ps${persistSession ? 1 : 0}/';await _makeCookieDir();}
Future<void> _makeCookieDir() async {final directory = Directory(_curDir!);if (!directory.existsSync()) {await directory.create(recursive: true);}}
init方法主要是检查传入的保存目录是否存在,不存在就创建。
read方法与load方法分别将文件中数据读取并存入内存中,domainCookies与hostCookies
Future<void> _load(Uri uri) async {final host = uri.host;if (_hostSet.contains(host) && hostCookies[host] == null) {var str = await storage.read(host);
Future<void> saveFromResponse(Uri uri, List<Cookie> cookies) async {await _checkInitialized();if (cookies.isNotEmpty) {await super.saveFromResponse(uri, cookies);if (cookies.every((Cookie e) => e.domain == null)) {await _save(uri);} else {await _save(uri, true);}}}
先检查有无保存目录,再看是否传入cookies,若有,直接调用DefaultCookieJar的saveFromResponse,然后调用_save方法:
Future<void> _save(Uri uri, [bool withDomainSharedCookie = false]) async {final host = uri.host;if (!_hostSet.contains(host)) {_hostSet.add(host);await storage.write(IndexKey, json.encode(_hostSet.toList()));}final cookies = hostCookies[host];if (cookies != null) {await storage.write(host, json.encode(_filter(cookies)));}if (withDomainSharedCookie) {var filterDomainCookies =domainCookies.map((key, value) => MapEntry(key, _filter(value)));await storage.write(DomainsKey, json.encode(filterDomainCookies));}}
根据Cookie层级,依次写入到文件中。
总结
CookieManager与CookieJar的整个工作流程就基本分析完了,可以看到,整个流程非常清晰,代码也很简洁,健壮性也很好,这次源码学习让我对代码编写的理解有深入了一些。那么,下次我们一起来看看如何封装Dio,让我们平时使用起来更便捷。
相关文章:
Flutter网络请求框架Dio源码分析以及封装(二)--Cookie管理分析
Flutter网络请求框架Dio源码分析以及封装--Cookie管理分析 前言问题如何使用CookieJarCookieManagerPersistCookieJar总结 前言 上一篇文章我们简单分析了一下Dio发出请求时的大致工作流程,这个只是Dio最基本的功能,而且我们还没有分析走到httpClientA…...
Unity如何设计一个技能系统
一、技能系统的设计思路 技能系统是游戏中非常重要的一部分,因此在设计技能系统时需要考虑以下几个方面: 对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白,也有一些正在从事游戏开发的技术大佬࿰…...

测试流程体系
目录: 软件测试基本概念软件测试模型软件测试工作流程测试左移和测试右移 1.软件测试基本概念 通过手工或者工具对"被测对象"进行测试验证实际结果与预期结果之间是否存在差异 软件测试作用 通过测试工作可以发现并修复软件当中存在的缺陷ÿ…...
Linux下CentOS KVM 虚拟化
介绍: KVM(Kernel-based Virtual Machine)是一种开源的虚拟化技术,它是基于Linux内核的虚拟化解决方案。KVM可以将一台物理服务器分割成多个虚拟机,每个虚拟机都可以运行不同的操作系统和应用程序,从而实现…...

< vue + ElementUi 组件封装:实现弹窗展示富文本数据,允许全文搜索高亮显示搜索内容 >
实现弹窗展示富文本数据,允许全文搜索高亮显示搜索内容 👉 前言👉 一、效果演示👉 二、实现思路👉 三、实现案例👍 卷王必胜!往期内容 💨 👉 前言 在 Vue elementUi 开…...

MATLAB 之 低层绘图操作和光照及材质处理
这里写目录标题 一、低层绘图操作1. 曲线对象2. 曲面对象3. 文本对象4. 其他核心对象4.1 区域块对象4.2 方框对象 二、光照和材质处理1. 光照处理2. 材质处理2.1 图形对象的反射特性2.2 material 函数 一、低层绘图操作 MATLAB 将曲线、曲面、文本等图形均视为对象,…...

LLM-Client一个轻量级的LLM集成工具
大型语言模型(llm)已经彻底改变了我们与文本交互的方式,OpenAI、Google、AI21、HuggingfaceHub、Anthropic和众多开源模型提供了不同的功能和优势。但是每个模型都有其独特的体系结构、api和兼容性需求,集成这些模型是一项耗时且具有挑战性的任务。 所以…...
leetcode动态数组vector实现杨辉三角
链接: leetcode动态数组vector实现杨辉三角 由题意可易得,从第三行开始,除了开始和末尾的位置上的元素,其余位置上的元素都是由上方的元素以及上方左侧的元素相加得到的,此时就很容易的到从第三行开始状态转移方程为vv[i][j] vv[…...

第二十三章_Redis高性能设计之epoll和IO多路复用深度解析
before 多路复用要解决的问题 并发多客户端连接,在多路复用之前最简单和典型的方案:同步阻塞网络IO模型 这种模式的特点就是用一个进程来处理一个网络连接(一个用户请求),比如一段典型的示例代码如下。 直接调用 recv 函数从一个 socket 上读…...

基于OpenCV-车辆检测项目(简易版)
车辆检测 1.项目介绍2. 读取一段视频3.通过形态学处理识别车辆4.描画轮廓5. 车辆计数并显示 本项目使用的视频地址链接 1.项目介绍 对一个视频进行车辆数量的检测,用到的知识有视频的读取,滤波器,形态学,添加直线、文本ÿ…...
用python获取海康摄像机视频
要调用海康摄像机视频,需要使用海康SDK提供的API。以下是一个简单的示例代码,可以连接到海康摄像机并获取视频流: python import sys from PyQt5.QtWidgets import QApplication, QWidget, QLabel from PyQt5.QtGui import QPixmap from PyQ…...

【Linux】遇事不决,可先点灯,LED驱动的进化之路---2
【Linux】遇事不决,可先点灯,LED驱动的进化之路---2 前言: 一、Pinctrl子系统重要概念 1.1 重要概念 1.1.1 pin controller 1.1.2 client device 1.1.3 补充概念 二、GPIO子系统重要概念 2.1 在设备树指定GPIO引脚 2.2 在驱动代码中…...

【计算机网络】数据链路层--点对点协议PPP
1.概念 2.构成 3.封装成帧 - 帧格式 4.透明传输 4.1字节填充法(面向字节的异步链路) 4.2.比特填充法(面向比特的同步链路) 5.差错检测 6.工作状态 7.小结...

【⑦MySQL】· 一文了解四大子查询
前言 ✨欢迎来到小K的MySQL专栏,本节将为大家带来MySQL标量/单行子查询、列子/表子查询的讲解✨ 目录 前言一、子查询概念二、标量/单行子查询、列子/表子查询三、总结 一、子查询概念 子查询指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从My…...

ValSuite报告可以帮助改善您的验证过程的6种方式
热验证工艺是一项复杂而微妙的工作,但它是确保制药和生物技术产品的安全性和有效性的重要组成部分。同时,管理整个验证过程中产生的数据可能很费时,而且容易出错——这就是ValSuite的意义。 这款直观的验证软件简化了数据分析和报告…...

【机器学习】机器故障的二元分类模型-Kaggle竞赛
竞赛介绍 数据集描述 本次竞赛的数据集(训练和测试)是从根据机器故障预测训练的深度学习模型生成的。特征分布与原始分布接近,但不完全相同。随意使用原始数据集作为本次竞赛的一部分,既可以探索差异,也可以了解在训…...

ADB usage
查看手机设备的信息 获取设备的Android版本号 adb shell getprop ro.build.version.release 获取设备的API版本号 adb shell getprop ro.build.version.sdkAdb 获得 sdk版本 adb shell getprop ro.build.version.sdk27 Adb 获得Android版本 adb shell getprop ro.build.vers…...
利用有限元法(FEM)模拟并通过机器学习进行预测以揭示增材制造过程中热场变化:基于ABAQUS和Python的研究实践
1. 引言 增材制造(Additive Manufacturing,AM)近年来引起了大量的研究关注,这主要是因为它可以提供定制化、复杂结构的零件制造解决方案。在AM过程中,热场的分布和变化直接影响了零件的质量和性能。对此,采…...
Kafka与Flume的对比分析
Kafka与Flume的对比分析 一、Kafka和Flume1. Kafka架构2. Flume架构3. Kafka和Flume异同点 二、Kafka和Flume的性能对比1. 数据处理性能对比2. 大规模数据流处理的性能对比 三、性和稳定性对比1. 高可用集群的搭建KafkaFlume 2. 数据丢失和重复消费的问题处理KafkaFlume 四、适…...
docker启动redis哨兵报错(sentinel.conf is not writable: Permission denied)
Sentinel config file /usr/local/sentinel/sentinel.conf is not writable: Permission denied. Exiting… 用这个命令不报错:docker run --net host -p 6666:6666–name redis-sentinel -v /usr/mcc/redis/conf:/usr/local/sentinel/ -v /usr/mcc/redis/data/sent…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...