写点什么

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:542448

评论

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

百度网盘基于Flink的实时计算实践

百度Geek说

KCP协议应用详解:为速度而生的可靠传输协议

京东科技开发者

23款主流广告类SDK适配鸿蒙5,助力开发者抢占流量新蓝海

新消费日报

国民认证科技旗下SDK适配鸿蒙5,应用安全认证体验再升级

新消费日报

KCP协议应用详解:为速度而生的可靠传输协议

京东科技开发者

在 macOS 下升级 Python 几种常见的方法

okokabcd

YashanDB数据库在快速迭代开发中的角色

数据库砖家

阶跃星辰开源端到端语音模型 Step-Audio 2 mini:理解、推理与生成统一建模;苹果发布可在浏览器运行的视觉模型丨日报

RTE开发者社区

深度探讨基础软件驱动下的智驾进化之道 |《AI 进化论》第四期

阿里云基础软件

操作系统 智算 阿里云 PAI-TurboX AI进化论

YashanDB数据库在旅游行业的应用场景探讨

数据库砖家

能效提升 × 碳减排双驱动:MyEMS 开源能源管理的双重价值实践

开源能源管理系统

开源 能源管理系统

【CDH国产化替代】全面简化架构,降低成本,大幅提升数据处理效率

星环科技

CAD看图绘测一键全搞定,告别多软件切换!

在路上

cad

新闻数据分析如何提升媒体公信力与用户黏性?

沃观Wovision

数据分析 数据 沃观Wovision 舆情监测系统

“操作系统开源与 AI 进化”分论坛全议程来啦 | 2025 云栖大会

OpenAnolis小助手

AI 操作系统 云栖大会 龙蜥社区

摆摊神器小程序系统

微擎应用市场

小程序 摆摊 微擎

线上活动丨Ask me anything!向阶跃星辰语音算法负责人提问

RTE开发者社区

Coze教程 第2章:Coze开发环境搭建与配置

测试人

品牌出海时,“文化差异”是海外舆情爆发的原因吗?

沃观Wovision

沃观Wovision 舆情监测系统 海外舆情监测

解码大模型:技术篇《1.1-基础架构概念》

京东科技开发者

深度探讨基础软件驱动下的智驾进化之道 |《AI 进化论》第四期

OpenAnolis小助手

操作系统 智算 AI进化论

质量安全管控如何实现事前预防?

禅道项目管理

DevOps 安全 软件开发 禅道项目管理软件 质量安全

DeepSeek大模型Prompt工程深度实践(开发者空间Notebook版)

华为云开发者联盟

Prompt 华为开发者空间

变更管理的关键,借助数字化优势重塑财务规划

智达方通

数字化转型 企业管理 全面预算管理 财务规划

海外APP上线Apple App Store的流程

北京木奇移动技术有限公司

软件外包公司 APP开发公司 海外APP

YashanDB数据库在金融行业的应用及优势

数据库砖家

中东地区开发APP的流程

北京木奇移动技术有限公司

软件外包公司 APP外包公司 海外APP

国内低代码平台哪个好?低代码定制开发平台选型指南:10款平台测评对比

优秀

低代码 低代码开发 低代码开发平台

详解CAD批量打印PDF的黑白模式设置,快速搞定批量输出

在路上

cad cad看图 CAD看图软件 CAD看图王

解码大模型:技术篇《1.1-基础架构概念》

京东科技开发者

创客空间园区物业活动小程序介绍

微擎应用市场

创客 园区运营

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