写点什么

iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求

  • 2019-12-09
  • 本文字数:3078 字

    阅读完需:约 10 分钟

iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求

这篇文章会提供一种在 Cocoa 层拦截所有 HTTP 请求的方法,其实标题已经说明了拦截 HTTP 请求需要的了解的就是 NSURLProtocol


由于文章的内容较长,会分成两部分,这篇文章介绍 NSURLProtocol 拦截 HTTP 请求的原理,另一篇文章如何进行 HTTP Mock 介绍这个原理在 OHHTTPStubs 中的应用,它是如何 Mock(伪造)某个 HTTP 请求对应的响应的。

NSURLProtocol

NSURLProtocol 是苹果为我们提供的 URL Loading System 的一部分,这是一张从官方文档贴过来的图片:



官方文档对 NSURLProtocol 的描述是这样的:


An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports.


在每一个 HTTP 请求开始时,URL 加载系统创建一个合适的 NSURLProtocol 对象处理对应的 URL 请求,而我们需要做的就是写一个继承自 NSURLProtocol 的类,并通过 - registerClass: 方法注册我们的协议类,然后 URL 加载系统就会在请求发出时使用我们创建的协议对象对该请求进行处理。


这样,我们需要解决的核心问题就变成了如何使用 NSURLProtocol 来处理所有的网络请求,这里使用苹果官方文档中的 CustomHTTPProtocol 进行介绍,你可以点击这里下载源代码。


在这个工程中 CustomHTTPProtocol.m 是需要重点关注的文件,CustomHTTPProtocol 就是 NSURLProtocol 的子类:


Objective-C


@interface CustomHTTPProtocol : NSURLProtocol
...
@end
复制代码


现在重新回到需要解决的问题,也就是 如何使用 NSURLProtocol 拦截 HTTP 请求?,有这个么几个问题需要去解决:


  • 如何决定哪些请求需要当前协议对象处理?

  • 对当前的请求对象需要进行哪些处理?

  • NSURLProtocol 如何实例化?

  • 如何发出 HTTP 请求并且将响应传递给调用者?


上面的这几个问题其实都可以通过 NSURLProtocol 为我们提供的 API 来解决,决定请求是否需要当前协议对象处理的方法是:+ canInitWithRequest


Objective-C


+ (BOOL)canInitWithRequest:(NSURLRequest *)request {  BOOL shouldAccept;  NSURL *url;  NSString *scheme;
shouldAccept = (request != nil); if (shouldAccept) { url = [request URL]; shouldAccept = (url != nil); } return shouldAccept;}
复制代码


因为项目中的这个方法是大约有 60 多行,在这里只粘贴了其中的一部分,只为了说明该方法的作用:每一次请求都会有一个 NSURLRequest 实例,上述方法会拿到所有的请求对象,我们就可以根据对应的请求选择是否处理该对象;而上面的代码只会处理所有 URL 不为空的请求。


请求经过 + canInitWithRequest: 方法过滤之后,我们得到了所有要处理的请求,接下来需要对请求进行一定的操作,而这都会在 + canonicalRequestForRequest: 中进行,虽然它与 + canInitWithRequest: 方法传入的 request 对象都是一个,但是最好不要在 + canInitWithRequest: 中操作对象,可能会有语义上的问题;所以,我们需要覆写 + canonicalRequestForRequest: 方法提供一个标准的请求对象:


Objective-C


+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {  return request;}
复制代码


这里对请求不做任何修改,直接返回,当然你也可以给这个请求加个 header,只要最后返回一个 NSURLRequest 对象就可以。


在得到了需要的请求对象之后,就可以初始化一个 NSURLProtocol 对象了:


Objective-C


- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id <NSURLProtocolClient>)client {  return [super initWithRequest:request cachedResponse:cachedResponse client:client];}
复制代码


在这里直接调用 super 的指定构造器方法,实例化一个对象,然后就进入了发送网络请求,获取数据并返回的阶段了:


Objective-C


- (void)startLoading {  NSURLSession *session = [NSURLSession sessionWithConfiguration:[[NSURLSessionConfiguration alloc] init] delegate:self delegateQueue:nil];  NSURLSessionDataTask *task = [session dataTaskWithRequest:self.request];  [task resume];}
复制代码


这里使用简化了 CustomHTTPClient 中的项目代码,可以达到几乎相同的效果。


你可以在 - startLoading 中使用任何方法来对协议对象持有的 request 进行转发,包括 NSURLSessionNSURLConnection 甚至使用 AFNetworking 等网络库,只要你能在回调方法中把数据传回 client,帮助其正确渲染就可以,比如这样:


Objective-C


- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {  [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
completionHandler(NSURLSessionResponseAllow);}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [[self client] URLProtocol:self didLoadData:data];}
复制代码


当然这里省略后的代码只会保证大多数情况下的正确执行,只是给你一个对获取响应数据粗略的认知,如果你需要更加详细的代码,我觉得最好还是查看一下 CustomHTTPProtocol 中对 HTTP 响应处理的代码,也就是 NSURLSessionDelegate 协议实现的部分。


client 你可以理解为当前网络请求的发起者,所有的 client 都实现了 NSURLProtocolClient 协议,协议的作用就是在 HTTP 请求发出以及接受响应时向其它对象传输数据:


Objective-C


@protocol NSURLProtocolClient <NSObject>...- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;
- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;
- (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;...@end
复制代码


当然这个协议中还有很多其他的方法,比如 HTTPS 验证、重定向以及响应缓存相关的方法,你需要在合适的时候调用这些代理方法,对信息进行传递。


如果你只是继承了 NSURLProtocol 并且实现了上述方法,依然不能达到预期的效果,完成对 HTTP 请求的拦截,你还需要在 URL 加载系统中注册当前类:


Objective-C


[NSURLProtocol registerClass:self];
复制代码


需要注意的是 NSURLProtocol 只能拦截 UIURLConnectionNSURLSessionUIWebView 中的请求,对于 WKWebView 中发出的网络请求也无能为力,如果真的要拦截来自 WKWebView 中的请求,还是需要实现 WKWebView 对应的 WKNavigationDelegate,并在代理方法中获取请求。

无论是 NSURLProtocolNSURLConnection 还是 NSURLSession 都会走底层的 socket,但是 WKWebView 可能由于基于 WebKit,并不会执行 C socket 相关的函数对 HTTP 请求进行处理,具体会执行什么代码暂时不是很清楚,如果对此有兴趣的读者,可以联系笔者一起讨论。

总结

如果你只想了解如何对 HTTP 请求进行拦截,其实看到这里就可以了,不过如果你想应用文章中的内容或者希望了解如何伪造 HTTP 响应,可以看下一篇文章如何进行 HTTP Mock

References


Github Repo:iOS-Source-Code-Analyze


Source: https://draveness.me/intercept


本文转载自 Draveness 技术博客。


原文链接:https://draveness.me/intercept


2019-12-09 15:541249

评论

发布
暂无评论
发现更多内容

HoudahSpot最新中文版+补丁安装教程

胖墩儿不胖y

Mac软件 文件搜索 搜索工具 搜索软件

【中危】Spring Kafka 反序列化漏洞 (CVE-2023-34040)

墨菲安全

网络安全 漏洞情报

Microsoft Office LTSC 2021 for Mac v16.77 beta中文版

mac

办公软件 苹果mac Windows软件 office2021

Sentieon | 应用教程: TNscope®使用机器学习模型进行有匹配正常样本的体细胞变异发现

INSVAST

基因测序 应用教程 TNscope

火山引擎边缘云,助你沉浸式回忆童年

火山引擎边缘云

CDN CDN加速 内容分发 CDN技术

ARTS 打卡第二周

直须

个人成长 前端 ARTS 打卡计划

Paper推荐|「隐私集合求交PSI系列」奖励升级

隐语SecretFlow

大数据 数据安全 隐私计算 开源社区 论文推荐

7个用于机器学习和数据科学的基本 Python 库

3D建模设计

Python AI人工智能

WiFi7 is coming -IPQ9574-QCN9274-speeds up to 30Gbits per second-quaclomm-chipset

wifi6-yiyi

WiFi7 30g

盘点主流的11款管理测试用例的工具

爱吃小舅的鱼

测试工具 PingCode 测试用例管理工具

量化智能机器人开发,炒币机器人功能部署搭建

V\TG【ch3nguang】

量化交易机器人开发 炒币机器人

文心一言 VS 讯飞星火 VS chatgpt (81)-- 算法导论7.4 6题

福大大架构师每日一题

福大大架构师每日一题

ARTS 打卡第二周

直须

个人成长 前端 ARTS 打卡计划

五种重要的 AI 编程语言

3D建模设计

人工智能 AI

推荐前 6 名 JavaScript 和 HTML5 游戏引擎

3D建模设计

游戏引擎

StarRocks 存算分离技术探索活动回顾

StarRocks

数据库 数据仓库 OLAP 存算分离

实力!云起无垠获“互联网+”市赛一等奖

云起无垠

mysql基础——认识索引

树上有只程序猿

MySQL 索引

PyTorch 提高生产力的技巧

3D建模设计

机器学习 PyTorch

6种限流实现,附代码![通俗易懂]

王磊

Java Java面试题

开源微服务如何选型?Spring Cloud、Dubbo、gRPC、Istio 详细对比

阿里巴巴云原生

阿里云 云原生 dubbo

交易所系统开发/秒合约交易所/锁仓挖矿/前端uinapp

V\TG【ch3nguang】

交易所开发 秒合约

高效数据传输与管理利器:镭速传输方案助力企业提升效率与安全

镭速

数据传输 分发数据 管理数据 镭速数据传输

如何在浏览器中启用 WebGL 以使用 HTML5 3D 查看器

3D建模设计

html5 WebGL 3D 查看器

隐语纵向联邦 SecureBoost Benchmark白皮书

隐语SecretFlow

大数据 AI 数据安全 隐私计算 开源社区

BetterZip 5中文下载+BetterZip 5注册码

mac大玩家j

Mac软件 解压缩工具 压缩软件

一文讲透低代码开发的优势

高端章鱼哥

软件开发 低代码 应用开发 JNPF

动态规划-编辑距离-两字符串集合重排序

alexgaoyh

重排序 动态规划 编辑距离 文本对齐 编辑距离变化过程

Node.js 的 Buffer 是什么?面向开发者的指南

Liam

JavaScript node.js 程序员 后端 buffer

【高危】Kubernetes Windows节点kubernetes-csi-proxy提权漏洞 (CVE-2023-3893)

墨菲安全

网络安全 漏洞情报

Tableau Desktop 2019 for Mac(全能数据分析工具) v2019.1.0激活版

mac

数据分析工具 Tableau Desktop 2019 苹果mac Windows软件

iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求_语言 & 开发_Draveness_InfoQ精选文章