Flutter网络请求框架Dio源码分析以及封装(二)--Cookie管理分析

news/2024/11/24 23:03:54/

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,让我们平时使用起来更便捷。


http://www.ppmy.cn/news/357485.html

相关文章

DI卡件/3503E/TRICONEX

本特利bently3300XL NSv振动和位移前置器常用型号&#xff1a; 330980-50-00 330980-50-CN 330980-51-00 330980-51-CN 330980-70-00 330980-70-CN 330980-71-00 330980-71-CN 常用匹配传感器振动探头和延伸电缆型号如下&#xff1a; 330903-00-03-10-02-00 330903-…

Error: L6220E: Execution region ER_IROM5 size (31436 bytes) exceeds limit (31424 bytes).

kei4.73编译过程中提示错误&#xff1a; ..\..\..\scatterfiles\scatterfile_common.sct: Error: L6220E: Execution region ER_IROM5 size (31436 bytes) exceeds limit (31424 bytes). Region contains 13 bytes of padding and 1260 bytes of veneers (total 1273 bytes of…

keil 下连接错误 Error: L6220E

在keil4下编译程序&#xff0c;提示以下错误&#xff1a; linking... .\rvmdk\xxx.axf: Error: L6220E: Load region LR_IROM size (94576 bytes) exceeds limit (92160 bytes). .\rvmdk\xxx.axf: Error: L6220E: Execution region ER_IROM size (94232 bytes) exceeds limit …

关于 keil 报错:Error: L6220E: Load region LR_IROM1 size.....等解决方式

关于 keil 报错&#xff1a;Error: L6220E: Load region LR_IROM1 size.....等解决方式 简单叙述解决方式最后效果后来测试 后续改正 简单叙述 这两天用keil调试代码&#xff0c;可能是打印printf用得多了&#xff0c;结果报了一个错误。便查了查&#xff0c;调试了一下。 报错…

在DELL笔记本上E6220安装fedora16(1)——分区的陷阱

机器型号&#xff1a;DELL E6220 4GRAM 300GHD 机器是公司的&#xff0c;领来的时候&#xff0c;装的是windows7&#xff0c;没有光驱&#xff0c;因为公司的服务器都是centos系列&#xff0c;所以我选择了fedora16的64位版本进行安装。 噩梦开始&#xff1a; 用easybcd进行硬…

在DELL笔记本上E6220安装fedora16(2)—— 无线网卡

系统装好后&#xff0c;发现一个坑爹的事情&#xff0c;无线网卡不能用&#xff0c;记得ubuntu就没这个问题&#xff0c;只能再想法解决了。网上此类文章无数&#xff0c;好用的感觉只有这一篇&#xff0c;转载一下吧。 另外因为broadcom的网站实在太慢&#xff0c;有时候还访问…

浅析Shazam音乐识别算法:基于Matlab的实现与音乐特征提取的探讨

亲爱的读者们&#xff0c;大家好&#xff01;我非常荣幸有机会与你们分享我在音乐识别算法领域的一些探索。今天我们将会深入研究Shazam音乐识别算法&#xff0c;并以此为基础在Matlab环境中进行实现。这篇文章的目的是帮助你理解Shazam音乐识别算法的基本原理和工作流程&#…

Java精品项目源码第111期小蜜蜂扩音器网上商城系统

Java精品项目源码第111期小蜜蜂扩音器网上商城系统 一、项目简述 用户功能模块&#xff1a; 用户注册&#xff1a; 用户登录&#xff1a;商品模块&#xff1a;订单模块&#xff1b;后台管理系统功能&#xff1a;管理员模块&#xff1a; 商品模块&#xff1a;订单管理模块 &…